Front: Pretty and Lint (#225)
This commit is contained in:
4
.github/workflows/CI.yml
vendored
4
.github/workflows/CI.yml
vendored
@@ -45,6 +45,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Type Check
|
- name: Type Check
|
||||||
run: yarn tsc
|
run: yarn tsc
|
||||||
|
- name: Check Prettier
|
||||||
|
run: yarn pretty:check .
|
||||||
|
- name: Run Linter
|
||||||
|
run: yarn lint
|
||||||
|
|
||||||
- name: 🏗 Setup Expo
|
- name: 🏗 Setup Expo
|
||||||
uses: expo/expo-github-action@v7
|
uses: expo/expo-github-action@v7
|
||||||
|
|||||||
24
front/.eslintrc.json
Normal file
24
front/.eslintrc.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
5
front/.prettierignore
Normal file
5
front/.prettierignore
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.expo
|
||||||
|
.expo-shared/
|
||||||
|
dist/
|
||||||
|
.vscode/
|
||||||
|
.storybook/
|
||||||
13
front/.prettierrc.json
Normal file
13
front/.prettierrc.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
246
front/API.ts
246
front/API.ts
@@ -1,21 +1,21 @@
|
|||||||
import Artist from "./models/Artist";
|
import Artist from './models/Artist';
|
||||||
import Album from "./models/Album";
|
import Album from './models/Album';
|
||||||
import AuthToken from "./models/AuthToken";
|
import AuthToken from './models/AuthToken';
|
||||||
import Chapter from "./models/Chapter";
|
import Chapter from './models/Chapter';
|
||||||
import Lesson from "./models/Lesson";
|
import Lesson from './models/Lesson';
|
||||||
import Genre from "./models/Genre";
|
import Genre from './models/Genre';
|
||||||
import LessonHistory from "./models/LessonHistory";
|
import LessonHistory from './models/LessonHistory';
|
||||||
import Song from "./models/Song";
|
import Song from './models/Song';
|
||||||
import SongHistory from "./models/SongHistory";
|
import SongHistory from './models/SongHistory';
|
||||||
import User from "./models/User";
|
import User from './models/User';
|
||||||
import Constants from "expo-constants";
|
import Constants from 'expo-constants';
|
||||||
import store from "./state/Store";
|
import store from './state/Store';
|
||||||
import { Platform } from "react-native";
|
import { Platform } from 'react-native';
|
||||||
import { en } from "./i18n/Translations";
|
import { en } from './i18n/Translations';
|
||||||
import { useQuery, QueryClient } from "react-query";
|
import { QueryClient } from 'react-query';
|
||||||
import UserSettings from "./models/UserSettings";
|
import UserSettings from './models/UserSettings';
|
||||||
import { PartialDeep } from "type-fest";
|
import { PartialDeep } from 'type-fest';
|
||||||
import SearchHistory from "./models/SearchHistory";
|
import SearchHistory from './models/SearchHistory';
|
||||||
|
|
||||||
type AuthenticationInput = { username: string; password: string };
|
type AuthenticationInput = { username: string; password: string };
|
||||||
type RegistrationInput = AuthenticationInput & { email: string };
|
type RegistrationInput = AuthenticationInput & { email: string };
|
||||||
@@ -24,8 +24,8 @@ export type AccessToken = string;
|
|||||||
|
|
||||||
type FetchParams = {
|
type FetchParams = {
|
||||||
route: string;
|
route: string;
|
||||||
body?: Object;
|
body?: object;
|
||||||
method?: "GET" | "POST" | "DELETE" | "PATCH" | "PUT";
|
method?: 'GET' | 'POST' | 'DELETE' | 'PATCH' | 'PUT';
|
||||||
// If true, No JSON parsing is done, the raw response's content is returned
|
// If true, No JSON parsing is done, the raw response's content is returned
|
||||||
raw?: true;
|
raw?: true;
|
||||||
};
|
};
|
||||||
@@ -40,7 +40,7 @@ export class APIError extends Error {
|
|||||||
public status: number,
|
public status: number,
|
||||||
// Set the message to the correct error this is a placeholder
|
// Set the message to the correct error this is a placeholder
|
||||||
// when the error is only used internally (middleman)
|
// when the error is only used internally (middleman)
|
||||||
public userMessage: keyof typeof en = "unknownError"
|
public userMessage: keyof typeof en = 'unknownError'
|
||||||
) {
|
) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
@@ -48,24 +48,22 @@ export class APIError extends Error {
|
|||||||
|
|
||||||
// we will need the same thing for the scorometer API url
|
// we will need the same thing for the scorometer API url
|
||||||
const baseAPIUrl =
|
const baseAPIUrl =
|
||||||
process.env.NODE_ENV != "development" && Platform.OS === "web"
|
process.env.NODE_ENV != 'development' && Platform.OS === 'web'
|
||||||
? "/api"
|
? '/api'
|
||||||
: Constants.manifest?.extra?.apiUrl;
|
: Constants.manifest?.extra?.apiUrl;
|
||||||
|
|
||||||
export default class API {
|
export default class API {
|
||||||
public static async fetch(params: FetchParams) {
|
public static async fetch(params: FetchParams) {
|
||||||
const jwtToken = store.getState().user.accessToken;
|
const jwtToken = store.getState().user.accessToken;
|
||||||
const header = {
|
const header = {
|
||||||
"Content-Type": "application/json",
|
'Content-Type': 'application/json',
|
||||||
};
|
};
|
||||||
const response = await fetch(`${baseAPIUrl}${params.route}`, {
|
const response = await fetch(`${baseAPIUrl}${params.route}`, {
|
||||||
headers:
|
headers: (jwtToken && { ...header, Authorization: `Bearer ${jwtToken}` }) || header,
|
||||||
(jwtToken && { ...header, Authorization: `Bearer ${jwtToken}` }) ||
|
|
||||||
header,
|
|
||||||
body: JSON.stringify(params.body),
|
body: JSON.stringify(params.body),
|
||||||
method: params.method ?? "GET",
|
method: params.method ?? 'GET',
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
throw new Error("Error while fetching API: " + baseAPIUrl);
|
throw new Error('Error while fetching API: ' + baseAPIUrl);
|
||||||
});
|
});
|
||||||
if (params.raw) {
|
if (params.raw) {
|
||||||
return response.arrayBuffer();
|
return response.arrayBuffer();
|
||||||
@@ -74,15 +72,11 @@ export default class API {
|
|||||||
try {
|
try {
|
||||||
const jsonResponse = body.length != 0 ? JSON.parse(body) : {};
|
const jsonResponse = body.length != 0 ? JSON.parse(body) : {};
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new APIError(
|
throw new APIError(jsonResponse ?? response.statusText, response.status);
|
||||||
jsonResponse ?? response.statusText,
|
|
||||||
response.status
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return jsonResponse;
|
return jsonResponse;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof SyntaxError)
|
if (e instanceof SyntaxError) throw new Error("Error while parsing Server's response");
|
||||||
throw new Error("Error while parsing Server's response");
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,16 +85,16 @@ export default class API {
|
|||||||
authenticationInput: AuthenticationInput
|
authenticationInput: AuthenticationInput
|
||||||
): Promise<AccessToken> {
|
): Promise<AccessToken> {
|
||||||
return API.fetch({
|
return API.fetch({
|
||||||
route: "/auth/login",
|
route: '/auth/login',
|
||||||
body: authenticationInput,
|
body: authenticationInput,
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
})
|
})
|
||||||
.then((responseBody) => responseBody.access_token)
|
.then((responseBody) => responseBody.access_token)
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
if (!(e instanceof APIError)) throw e;
|
if (!(e instanceof APIError)) throw e;
|
||||||
|
|
||||||
if (e.status == 401)
|
if (e.status == 401)
|
||||||
throw new APIError("invalidCredentials", 401, "invalidCredentials");
|
throw new APIError('invalidCredentials', 401, 'invalidCredentials');
|
||||||
throw e;
|
throw e;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -109,13 +103,11 @@ export default class API {
|
|||||||
* @param registrationInput the credentials to create a new profile
|
* @param registrationInput the credentials to create a new profile
|
||||||
* @returns A Promise. On success, will be resolved into an instance of the API wrapper
|
* @returns A Promise. On success, will be resolved into an instance of the API wrapper
|
||||||
*/
|
*/
|
||||||
public static async createAccount(
|
public static async createAccount(registrationInput: RegistrationInput): Promise<AccessToken> {
|
||||||
registrationInput: RegistrationInput
|
|
||||||
): Promise<AccessToken> {
|
|
||||||
await API.fetch({
|
await API.fetch({
|
||||||
route: "/auth/register",
|
route: '/auth/register',
|
||||||
body: registrationInput,
|
body: registrationInput,
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
});
|
});
|
||||||
// In the Future we should move autheticate out of this function
|
// In the Future we should move autheticate out of this function
|
||||||
// and maybe create a new function to create and login in one go
|
// 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<AccessToken> {
|
public static async createAndGetGuestAccount(): Promise<AccessToken> {
|
||||||
let response = await API.fetch({
|
const response = await API.fetch({
|
||||||
route: "/auth/guest",
|
route: '/auth/guest',
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
});
|
});
|
||||||
if (!response.access_token)
|
if (!response.access_token) throw new APIError('No access token', response.status);
|
||||||
throw new APIError("No access token", response.status);
|
|
||||||
return response.access_token;
|
return response.access_token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async transformGuestToUser(
|
public static async transformGuestToUser(registrationInput: RegistrationInput): Promise<void> {
|
||||||
registrationInput: RegistrationInput
|
|
||||||
): Promise<void> {
|
|
||||||
await API.fetch({
|
await API.fetch({
|
||||||
route: "/auth/me",
|
route: '/auth/me',
|
||||||
body: registrationInput,
|
body: registrationInput,
|
||||||
method: "PUT",
|
method: 'PUT',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,8 +138,8 @@ export default class API {
|
|||||||
* Retrieve information of the currently authentified user
|
* Retrieve information of the currently authentified user
|
||||||
*/
|
*/
|
||||||
public static async getUserInfo(): Promise<User> {
|
public static async getUserInfo(): Promise<User> {
|
||||||
let user = await API.fetch({
|
const user = await API.fetch({
|
||||||
route: "/auth/me",
|
route: '/auth/me',
|
||||||
});
|
});
|
||||||
|
|
||||||
// this a dummy settings object, we will need to fetch the real one from the API
|
// 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: {
|
data: {
|
||||||
gamesPlayed: user.partyPlayed as number,
|
gamesPlayed: user.partyPlayed as number,
|
||||||
xp: 0,
|
xp: 0,
|
||||||
createdAt: new Date("2023-04-09T00:00:00.000Z"),
|
createdAt: new Date('2023-04-09T00:00:00.000Z'),
|
||||||
avatar:
|
avatar: 'https://imgs.search.brave.com/RnQpFhmAFvuQsN_xTw7V-CN61VeHDBg2tkEXnKRYHAE/rs:fit:768:512:1/g:ce/aHR0cHM6Ly96b29h/c3Ryby5jb20vd3At/Y29udGVudC91cGxv/YWRzLzIwMjEvMDIv/Q2FzdG9yLTc2OHg1/MTIuanBn',
|
||||||
"https://imgs.search.brave.com/RnQpFhmAFvuQsN_xTw7V-CN61VeHDBg2tkEXnKRYHAE/rs:fit:768:512:1/g:ce/aHR0cHM6Ly96b29h/c3Ryby5jb20vd3At/Y29udGVudC91cGxv/YWRzLzIwMjEvMDIv/Q2FzdG9yLTc2OHg1/MTIuanBn",
|
|
||||||
},
|
},
|
||||||
} as User;
|
} as User;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getUserSettings(): Promise<UserSettings> {
|
public static async getUserSettings(): Promise<UserSettings> {
|
||||||
const settings = await API.fetch({
|
const settings = await API.fetch({
|
||||||
route: "/auth/me/settings",
|
route: '/auth/me/settings',
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -189,9 +177,7 @@ export default class API {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async updateUserSettings(
|
public static async updateUserSettings(settings: PartialDeep<UserSettings>): Promise<void> {
|
||||||
settings: PartialDeep<UserSettings>
|
|
||||||
): Promise<void> {
|
|
||||||
const dto = {
|
const dto = {
|
||||||
pushNotification: settings.notifications?.pushNotif,
|
pushNotification: settings.notifications?.pushNotif,
|
||||||
emailNotification: settings.notifications?.emailNotif,
|
emailNotification: settings.notifications?.emailNotif,
|
||||||
@@ -203,8 +189,8 @@ export default class API {
|
|||||||
showActivity: settings.showActivity,
|
showActivity: settings.showActivity,
|
||||||
};
|
};
|
||||||
return API.fetch({
|
return API.fetch({
|
||||||
method: "PATCH",
|
method: 'PATCH',
|
||||||
route: "/auth/me/settings",
|
route: '/auth/me/settings',
|
||||||
body: dto,
|
body: dto,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -225,27 +211,29 @@ export default class API {
|
|||||||
*/
|
*/
|
||||||
public static async authWithGoogle(): Promise<AuthToken> {
|
public static async authWithGoogle(): Promise<AuthToken> {
|
||||||
//TODO
|
//TODO
|
||||||
return "11111";
|
return '11111';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getAllSongs(): Promise<Song[]> {
|
public static async getAllSongs(): Promise<Song[]> {
|
||||||
let songs = await API.fetch({
|
const songs = await API.fetch({
|
||||||
route: "/song",
|
route: '/song',
|
||||||
});
|
});
|
||||||
|
|
||||||
// this is a dummy illustration, we will need to fetch the real one from the API
|
// this is a dummy illustration, we will need to fetch the real one from the API
|
||||||
return songs.data.map(
|
return songs.data.map(
|
||||||
|
// To be fixed with #168
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(song: any) =>
|
(song: any) =>
|
||||||
({
|
({
|
||||||
id: song.id as number,
|
id: song.id as number,
|
||||||
name: song.name as string,
|
name: song.name as string,
|
||||||
artistId: song.artistId as number,
|
artistId: song.artistId as number,
|
||||||
albumId: song.albumId as number,
|
albumId: song.albumId as number,
|
||||||
genreId: song.genreId as number,
|
genreId: song.genreId as number,
|
||||||
details: song.difficulties,
|
details: song.difficulties,
|
||||||
cover: `${baseAPIUrl}/song/${song.id}/illustration`,
|
cover: `${baseAPIUrl}/song/${song.id}/illustration`,
|
||||||
metrics: {},
|
metrics: {},
|
||||||
} as Song)
|
} as Song)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,7 +242,7 @@ export default class API {
|
|||||||
* @param songId the id to find the song
|
* @param songId the id to find the song
|
||||||
*/
|
*/
|
||||||
public static async getSong(songId: number): Promise<Song> {
|
public static async getSong(songId: number): Promise<Song> {
|
||||||
let song = await API.fetch({
|
const song = await API.fetch({
|
||||||
route: `/song/${songId}`,
|
route: `/song/${songId}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -326,8 +314,8 @@ export default class API {
|
|||||||
end: 100 * value,
|
end: 100 * value,
|
||||||
songId: songId,
|
songId: songId,
|
||||||
name: `Chapter ${value}`,
|
name: `Chapter ${value}`,
|
||||||
type: "chorus",
|
type: 'chorus',
|
||||||
key_aspect: "rhythm",
|
key_aspect: 'rhythm',
|
||||||
difficulty: value,
|
difficulty: value,
|
||||||
id: value * 10,
|
id: value * 10,
|
||||||
}));
|
}));
|
||||||
@@ -369,23 +357,26 @@ export default class API {
|
|||||||
* Search Album by name
|
* Search Album by name
|
||||||
* @param query the string used to find the album
|
* @param query the string used to find the album
|
||||||
*/
|
*/
|
||||||
public static async searchAlbum(query?: string): Promise<Album[]> {
|
public static async searchAlbum(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
query?: string
|
||||||
|
): Promise<Album[]> {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "Super Trooper",
|
name: 'Super Trooper',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: "Kingdom Heart 365/2 OST",
|
name: 'Kingdom Heart 365/2 OST',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
name: "The Legend Of Zelda Ocarina Of Time OST",
|
name: 'The Legend Of Zelda Ocarina Of Time OST',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 4,
|
id: 4,
|
||||||
name: "Random Access Memories",
|
name: 'Random Access Memories',
|
||||||
},
|
},
|
||||||
] as Album[];
|
] as Album[];
|
||||||
}
|
}
|
||||||
@@ -405,10 +396,10 @@ export default class API {
|
|||||||
*/
|
*/
|
||||||
public static async getLesson(lessonId: number): Promise<Lesson> {
|
public static async getLesson(lessonId: number): Promise<Lesson> {
|
||||||
return {
|
return {
|
||||||
title: "Song",
|
title: 'Song',
|
||||||
description: "A song",
|
description: 'A song',
|
||||||
requiredLevel: 1,
|
requiredLevel: 1,
|
||||||
mainSkill: "lead-head-change",
|
mainSkill: 'lead-head-change',
|
||||||
id: lessonId,
|
id: lessonId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -419,24 +410,26 @@ export default class API {
|
|||||||
* @param take how much do we take to return
|
* @param take how much do we take to return
|
||||||
* @returns Returns an array of history entries (temporary type any)
|
* @returns Returns an array of history entries (temporary type any)
|
||||||
*/
|
*/
|
||||||
public static async getSearchHistory(
|
public static async getSearchHistory(skip?: number, take?: number): Promise<SearchHistory[]> {
|
||||||
skip?: number,
|
|
||||||
take?: number
|
|
||||||
): Promise<SearchHistory[]> {
|
|
||||||
return (
|
return (
|
||||||
await API.fetch({
|
(
|
||||||
route: `/history/search?skip=${skip ?? 0}&take=${take ?? 5}`,
|
await API.fetch({
|
||||||
method: "GET",
|
route: `/history/search?skip=${skip ?? 0}&take=${take ?? 5}`,
|
||||||
})
|
method: 'GET',
|
||||||
).map((e: any) => {
|
})
|
||||||
return {
|
)
|
||||||
id: e.id,
|
// To be fixed with #168
|
||||||
query: e.query,
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
type: e.type,
|
.map((e: any) => {
|
||||||
userId: e.userId,
|
return {
|
||||||
timestamp: new Date(e.searchDate),
|
id: e.id,
|
||||||
} as SearchHistory;
|
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
|
* @param timestamp the date it's been issued
|
||||||
* @returns nothing
|
* @returns nothing
|
||||||
*/
|
*/
|
||||||
public static async createSearchHistoryEntry(
|
public static async createSearchHistoryEntry(query: string, type: string): Promise<void> {
|
||||||
query: string,
|
|
||||||
type: string,
|
|
||||||
timestamp: number
|
|
||||||
): Promise<void> {
|
|
||||||
return await API.fetch({
|
return await API.fetch({
|
||||||
route: `/history/search`,
|
route: `/history/search`,
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
body: {
|
body: {
|
||||||
query: query,
|
query: query,
|
||||||
type: type,
|
type: type,
|
||||||
@@ -467,7 +456,7 @@ export default class API {
|
|||||||
*/
|
*/
|
||||||
public static async getSongSuggestions(): Promise<Song[]> {
|
public static async getSongSuggestions(): Promise<Song[]> {
|
||||||
const queryClient = new QueryClient();
|
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<SongHistory[]> {
|
public static async getUserPlayHistory(): Promise<SongHistory[]> {
|
||||||
return this.fetch({
|
return this.fetch({
|
||||||
route: "/history",
|
route: '/history',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,9 +473,7 @@ export default class API {
|
|||||||
* Retrieve a lesson's history
|
* Retrieve a lesson's history
|
||||||
* @param lessonId the id to find the lesson
|
* @param lessonId the id to find the lesson
|
||||||
*/
|
*/
|
||||||
public static async getLessonHistory(
|
public static async getLessonHistory(lessonId: number): Promise<LessonHistory[]> {
|
||||||
lessonId: number
|
|
||||||
): Promise<LessonHistory[]> {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
lessonId,
|
lessonId,
|
||||||
@@ -497,50 +484,51 @@ export default class API {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a partition images
|
* 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
|
* This API may be merged with the fetch song in the future
|
||||||
*/
|
*/
|
||||||
public static async getPartitionRessources(
|
public static async getPartitionRessources(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
songId: number
|
songId: number
|
||||||
): Promise<[string, number, number][]> {
|
): Promise<[string, number, number][]> {
|
||||||
return [
|
return [
|
||||||
[
|
[
|
||||||
"https://media.discordapp.net/attachments/717080637038788731/1067469560426545222/vivaldi_split_1.png",
|
'https://media.discordapp.net/attachments/717080637038788731/1067469560426545222/vivaldi_split_1.png',
|
||||||
1868,
|
1868,
|
||||||
400,
|
400,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"https://media.discordapp.net/attachments/717080637038788731/1067469560900505660/vivaldi_split_2.png",
|
'https://media.discordapp.net/attachments/717080637038788731/1067469560900505660/vivaldi_split_2.png',
|
||||||
1868,
|
1868,
|
||||||
400,
|
400,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"https://media.discordapp.net/attachments/717080637038788731/1067469561261203506/vivaldi_split_3.png",
|
'https://media.discordapp.net/attachments/717080637038788731/1067469561261203506/vivaldi_split_3.png',
|
||||||
1868,
|
1868,
|
||||||
400,
|
400,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"https://media.discordapp.net/attachments/717080637038788731/1067469561546424381/vivaldi_split_4.png",
|
'https://media.discordapp.net/attachments/717080637038788731/1067469561546424381/vivaldi_split_4.png',
|
||||||
1868,
|
1868,
|
||||||
400,
|
400,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"https://media.discordapp.net/attachments/717080637038788731/1067469562058133564/vivaldi_split_5.png",
|
'https://media.discordapp.net/attachments/717080637038788731/1067469562058133564/vivaldi_split_5.png',
|
||||||
1868,
|
1868,
|
||||||
400,
|
400,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"https://media.discordapp.net/attachments/717080637038788731/1067469562347528202/vivaldi_split_6.png",
|
'https://media.discordapp.net/attachments/717080637038788731/1067469562347528202/vivaldi_split_6.png',
|
||||||
1868,
|
1868,
|
||||||
400,
|
400,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"https://media.discordapp.net/attachments/717080637038788731/1067469562792136815/vivaldi_split_7.png",
|
'https://media.discordapp.net/attachments/717080637038788731/1067469562792136815/vivaldi_split_7.png',
|
||||||
1868,
|
1868,
|
||||||
400,
|
400,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"https://media.discordapp.net/attachments/717080637038788731/1067469563073142804/vivaldi_split_8.png",
|
'https://media.discordapp.net/attachments/717080637038788731/1067469563073142804/vivaldi_split_8.png',
|
||||||
1868,
|
1868,
|
||||||
400,
|
400,
|
||||||
],
|
],
|
||||||
@@ -549,8 +537,8 @@ export default class API {
|
|||||||
|
|
||||||
public static async updateUserEmail(newEmail: string): Promise<User> {
|
public static async updateUserEmail(newEmail: string): Promise<User> {
|
||||||
const rep = await API.fetch({
|
const rep = await API.fetch({
|
||||||
route: "/auth/me",
|
route: '/auth/me',
|
||||||
method: "PUT",
|
method: 'PUT',
|
||||||
body: {
|
body: {
|
||||||
email: newEmail,
|
email: newEmail,
|
||||||
},
|
},
|
||||||
@@ -567,8 +555,8 @@ export default class API {
|
|||||||
newPassword: string
|
newPassword: string
|
||||||
): Promise<User> {
|
): Promise<User> {
|
||||||
const rep = await API.fetch({
|
const rep = await API.fetch({
|
||||||
route: "/auth/me",
|
route: '/auth/me',
|
||||||
method: "PUT",
|
method: 'PUT',
|
||||||
body: {
|
body: {
|
||||||
oldPassword: oldPassword,
|
oldPassword: oldPassword,
|
||||||
password: newPassword,
|
password: newPassword,
|
||||||
|
|||||||
@@ -5,21 +5,20 @@ import store, { persistor } from './state/Store';
|
|||||||
import { Router } from './Navigation';
|
import { Router } from './Navigation';
|
||||||
import './i18n/i18n';
|
import './i18n/i18n';
|
||||||
import * as SplashScreen from 'expo-splash-screen';
|
import * as SplashScreen from 'expo-splash-screen';
|
||||||
import { PersistGate } from "redux-persist/integration/react";
|
import { PersistGate } from 'redux-persist/integration/react';
|
||||||
import LanguageGate from "./i18n/LanguageGate";
|
import LanguageGate from './i18n/LanguageGate';
|
||||||
import ThemeProvider, { ColorSchemeProvider } from './Theme';
|
import ThemeProvider, { ColorSchemeProvider } from './Theme';
|
||||||
import 'react-native-url-polyfill/auto';
|
import 'react-native-url-polyfill/auto';
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
queries: {
|
queries: {
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
|
|
||||||
SplashScreen.preventAutoHideAsync();
|
SplashScreen.preventAutoHideAsync();
|
||||||
setTimeout(SplashScreen.hideAsync, 500);
|
setTimeout(SplashScreen.hideAsync, 500);
|
||||||
|
|
||||||
@@ -30,7 +29,7 @@ export default function App() {
|
|||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<ColorSchemeProvider>
|
<ColorSchemeProvider>
|
||||||
<LanguageGate>
|
<LanguageGate>
|
||||||
<Router/>
|
<Router />
|
||||||
</LanguageGate>
|
</LanguageGate>
|
||||||
</ColorSchemeProvider>
|
</ColorSchemeProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/ban-types */
|
||||||
import { NativeStackScreenProps, createNativeStackNavigator } from '@react-navigation/native-stack';
|
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 React, { useEffect } from 'react';
|
||||||
import { DarkTheme, DefaultTheme, NavigationContainer } from '@react-navigation/native';
|
import { DarkTheme, DefaultTheme, NavigationContainer } from '@react-navigation/native';
|
||||||
import { RootState, useSelector } from './state/Store';
|
import { RootState, useSelector } from './state/Store';
|
||||||
@@ -23,34 +28,39 @@ import { Button, Center, VStack } from 'native-base';
|
|||||||
import { unsetAccessToken } from './state/UserSlice';
|
import { unsetAccessToken } from './state/UserSlice';
|
||||||
import TextButton from './components/TextButton';
|
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 = () => ({
|
const publicRoutes = () =>
|
||||||
Home: { component: HomeView, options: { title: translate('welcome'), headerLeft: null } },
|
({
|
||||||
Play: { component: PlayView, options: { title: translate('play') } },
|
Start: { component: StartPageView, options: { title: 'Chromacase', headerShown: false } },
|
||||||
Settings: { component: SetttingsNavigator, options: { title: 'Settings' } },
|
Login: { component: AuthenticationView, options: { title: translate('signInBtn') } },
|
||||||
Song: { component: SongLobbyView, options: { title: translate('play') } },
|
Oops: { component: ProfileErrorView, options: { title: 'Oops', headerShown: false } },
|
||||||
Artist: { component: ArtistDetailsView, options: { title: translate('artistFilter') } },
|
} as const);
|
||||||
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;
|
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
type Route<Props = any> = {
|
type Route<Props = any> = {
|
||||||
component: (arg: RouteProps<Props>) => JSX.Element | (() => JSX.Element),
|
component: (arg: RouteProps<Props>) => JSX.Element | (() => JSX.Element);
|
||||||
options: any
|
options: object;
|
||||||
}
|
};
|
||||||
|
|
||||||
type OmitOrUndefined<T, K extends string> = T extends undefined ? T : Omit<T, K>
|
type OmitOrUndefined<T, K extends string> = T extends undefined ? T : Omit<T, K>;
|
||||||
|
|
||||||
type RouteParams<Routes extends Record<string, Route>> = {
|
type RouteParams<Routes extends Record<string, Route>> = {
|
||||||
[RouteName in keyof Routes]: OmitOrUndefined<Parameters<Routes[RouteName]['component']>[0], keyof NativeStackScreenProps<{}>>;
|
[RouteName in keyof Routes]: OmitOrUndefined<
|
||||||
}
|
Parameters<Routes[RouteName]['component']>[0],
|
||||||
|
keyof NativeStackScreenProps<{}>
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
|
||||||
type PrivateRoutesParams = RouteParams<ReturnType<typeof protectedRoutes>>;
|
type PrivateRoutesParams = RouteParams<ReturnType<typeof protectedRoutes>>;
|
||||||
type PublicRoutesParams = RouteParams<ReturnType<typeof publicRoutes>>;
|
type PublicRoutesParams = RouteParams<ReturnType<typeof publicRoutes>>;
|
||||||
@@ -58,34 +68,47 @@ type AppRouteParams = PrivateRoutesParams & PublicRoutesParams;
|
|||||||
|
|
||||||
const Stack = createNativeStackNavigator<AppRouteParams & { Loading: never }>();
|
const Stack = createNativeStackNavigator<AppRouteParams & { Loading: never }>();
|
||||||
|
|
||||||
const RouteToScreen = <T extends {}, >(component: Route<T>['component']) => (props: NativeStackScreenProps<T & ParamListBase>) =>
|
const RouteToScreen =
|
||||||
<>
|
<T extends {}>(component: Route<T>['component']) =>
|
||||||
{component({ ...props.route.params, route: props.route } as Parameters<Route<T>['component']>[0])}
|
// eslint-disable-next-line react/display-name
|
||||||
</>
|
(props: NativeStackScreenProps<T & ParamListBase>) =>
|
||||||
|
(
|
||||||
|
<>
|
||||||
|
{component({ ...props.route.params, route: props.route } as Parameters<
|
||||||
|
Route<T>['component']
|
||||||
|
>[0])}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
const routesToScreens = (routes: Partial<Record<keyof AppRouteParams, Route>>) => Object.entries(routes)
|
const routesToScreens = (routes: Partial<Record<keyof AppRouteParams, Route>>) =>
|
||||||
.map(([name, route], routeIndex) => (
|
Object.entries(routes).map(([name, route], routeIndex) => (
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
key={'route-' + routeIndex}
|
key={'route-' + routeIndex}
|
||||||
name={name as keyof AppRouteParams}
|
name={name as keyof AppRouteParams}
|
||||||
options={route.options}
|
options={route.options}
|
||||||
component={RouteToScreen(route.component)}
|
component={RouteToScreen(route.component)}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
|
|
||||||
const ProfileErrorView = (props: { onTryAgain: () => any }) => {
|
const ProfileErrorView = (props: { onTryAgain: () => void }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
return <Center style={{ flexGrow: 1 }}>
|
return (
|
||||||
<VStack space={3}>
|
<Center style={{ flexGrow: 1 }}>
|
||||||
<Translate translationKey='userProfileFetchError'/>
|
<VStack space={3}>
|
||||||
<Button onPress={props.onTryAgain}><Translate translationKey='tryAgain'/></Button>
|
<Translate translationKey="userProfileFetchError" />
|
||||||
<TextButton onPress={() => dispatch(unsetAccessToken())}
|
<Button onPress={props.onTryAgain}>
|
||||||
colorScheme="error" variant='outline'
|
<Translate translationKey="tryAgain" />
|
||||||
translate={{ translationKey: 'signOutBtn' }}
|
</Button>
|
||||||
/>
|
<TextButton
|
||||||
</VStack>
|
onPress={() => dispatch(unsetAccessToken())}
|
||||||
</Center>
|
colorScheme="error"
|
||||||
}
|
variant="outline"
|
||||||
|
translate={{ translationKey: 'signOutBtn' }}
|
||||||
|
/>
|
||||||
|
</VStack>
|
||||||
|
</Center>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const Router = () => {
|
export const Router = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@@ -108,25 +131,27 @@ export const Router = () => {
|
|||||||
}, [accessToken]);
|
}, [accessToken]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationContainer theme={colorScheme == 'light'
|
<NavigationContainer theme={colorScheme == 'light' ? DefaultTheme : DarkTheme}>
|
||||||
? DefaultTheme
|
|
||||||
: DarkTheme
|
|
||||||
}>
|
|
||||||
<Stack.Navigator>
|
<Stack.Navigator>
|
||||||
{ userProfile.isError && accessToken && !userProfile.isLoading
|
{userProfile.isError && accessToken && !userProfile.isLoading ? (
|
||||||
? <Stack.Screen name="Oops" component={RouteToScreen(() => <ProfileErrorView onTryAgain={() => userProfile.refetch()}/>)}/>
|
<Stack.Screen
|
||||||
: userProfile.isLoading && !userProfile.data ?
|
name="Oops"
|
||||||
<Stack.Screen name="Loading" component={RouteToScreen(LoadingView)}/>
|
component={RouteToScreen(() => (
|
||||||
: routesToScreens(userProfile.isSuccess && accessToken
|
<ProfileErrorView onTryAgain={() => userProfile.refetch()} />
|
||||||
? protectedRoutes()
|
))}
|
||||||
: publicRoutes())
|
/>
|
||||||
}
|
) : userProfile.isLoading && !userProfile.data ? (
|
||||||
|
<Stack.Screen name="Loading" component={RouteToScreen(LoadingView)} />
|
||||||
|
) : (
|
||||||
|
routesToScreens(
|
||||||
|
userProfile.isSuccess && accessToken ? protectedRoutes() : publicRoutes()
|
||||||
|
)
|
||||||
|
)}
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export type RouteProps<T> = T & Pick<NativeStackScreenProps<T & ParamListBase>, 'route'>;
|
export type RouteProps<T> = T & Pick<NativeStackScreenProps<T & ParamListBase>, 'route'>;
|
||||||
|
|
||||||
|
export const useNavigation = () => navigationHook<NavigationProp<AppRouteParams>>();
|
||||||
export const useNavigation = () => navigationHook<NavigationProp<AppRouteParams>>();
|
|
||||||
|
|||||||
149
front/Theme.tsx
149
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 useColorScheme from './hooks/colorScheme';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
const ThemeProvider = ({ children }: { children: JSX.Element }) => {
|
const ThemeProvider = ({ children }: { children: JSX.Element }) => {
|
||||||
const colorScheme = useColorScheme();
|
const colorScheme = useColorScheme();
|
||||||
|
|
||||||
return <NativeBaseProvider theme={extendTheme({
|
return (
|
||||||
config: {
|
<NativeBaseProvider
|
||||||
useSystemColorMode: false,
|
theme={extendTheme({
|
||||||
initialColorMode: colorScheme
|
config: {
|
||||||
},
|
useSystemColorMode: false,
|
||||||
colors: {
|
initialColorMode: colorScheme,
|
||||||
primary: {
|
},
|
||||||
50: '#e6faea',
|
colors: {
|
||||||
100: '#c8e7d0',
|
primary: {
|
||||||
200: '#a7d6b5',
|
50: '#e6faea',
|
||||||
300: '#86c498',
|
100: '#c8e7d0',
|
||||||
400: '#65b47c',
|
200: '#a7d6b5',
|
||||||
500: '#4b9a62',
|
300: '#86c498',
|
||||||
600: '#3a784b',
|
400: '#65b47c',
|
||||||
700: '#275635',
|
500: '#4b9a62',
|
||||||
800: '#14341f',
|
600: '#3a784b',
|
||||||
900: '#001405',
|
700: '#275635',
|
||||||
},
|
800: '#14341f',
|
||||||
secondary: {
|
900: '#001405',
|
||||||
50: '#d8ffff',
|
},
|
||||||
100: '#acffff',
|
secondary: {
|
||||||
200: '#7dffff',
|
50: '#d8ffff',
|
||||||
300: '#4dffff',
|
100: '#acffff',
|
||||||
400: '#28ffff',
|
200: '#7dffff',
|
||||||
500: '#18e5e6',
|
300: '#4dffff',
|
||||||
600: '#00b2b3',
|
400: '#28ffff',
|
||||||
700: '#007f80',
|
500: '#18e5e6',
|
||||||
800: '#004d4e',
|
600: '#00b2b3',
|
||||||
900: '#001b1d',
|
700: '#007f80',
|
||||||
},
|
800: '#004d4e',
|
||||||
error: {
|
900: '#001b1d',
|
||||||
50: '#ffe2e9',
|
},
|
||||||
100: '#ffb1bf',
|
error: {
|
||||||
200: '#ff7f97',
|
50: '#ffe2e9',
|
||||||
300: '#ff4d6d',
|
100: '#ffb1bf',
|
||||||
400: '#fe1d43',
|
200: '#ff7f97',
|
||||||
500: '#e5062b',
|
300: '#ff4d6d',
|
||||||
600: '#b30020',
|
400: '#fe1d43',
|
||||||
700: '#810017',
|
500: '#e5062b',
|
||||||
800: '#4f000c',
|
600: '#b30020',
|
||||||
900: '#200004',
|
700: '#810017',
|
||||||
},
|
800: '#4f000c',
|
||||||
notification: {
|
900: '#200004',
|
||||||
50: '#ffe1e1',
|
},
|
||||||
100: '#ffb1b1',
|
notification: {
|
||||||
200: '#ff7f7f',
|
50: '#ffe1e1',
|
||||||
300: '#ff4c4c',
|
100: '#ffb1b1',
|
||||||
400: '#ff1a1a',
|
200: '#ff7f7f',
|
||||||
500: '#e60000',
|
300: '#ff4c4c',
|
||||||
600: '#b40000',
|
400: '#ff1a1a',
|
||||||
700: '#810000',
|
500: '#e60000',
|
||||||
800: '#500000',
|
600: '#b40000',
|
||||||
900: '#210000',
|
700: '#810000',
|
||||||
}
|
800: '#500000',
|
||||||
},
|
900: '#210000',
|
||||||
components: {
|
},
|
||||||
Button: {
|
},
|
||||||
variants: {
|
components: {
|
||||||
solid: () => ({
|
Button: {
|
||||||
rounded: 'full',
|
variants: {
|
||||||
})
|
solid: () => ({
|
||||||
}
|
rounded: 'full',
|
||||||
}
|
}),
|
||||||
}
|
},
|
||||||
})}>
|
},
|
||||||
{ children }
|
},
|
||||||
</NativeBaseProvider>;
|
})}
|
||||||
}
|
>
|
||||||
|
{children}
|
||||||
const ColorSchemeProvider = (props: { children: any }) => {
|
</NativeBaseProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ColorSchemeProvider = (props: { children: JSX.Element }) => {
|
||||||
const colorScheme = useColorScheme();
|
const colorScheme = useColorScheme();
|
||||||
const colorMode = useColorMode();
|
const colorMode = useColorMode();
|
||||||
|
|
||||||
@@ -83,7 +86,7 @@ const ColorSchemeProvider = (props: { children: any }) => {
|
|||||||
colorMode.setColorMode(colorScheme);
|
colorMode.setColorMode(colorScheme);
|
||||||
}, [colorScheme]);
|
}, [colorScheme]);
|
||||||
return props.children;
|
return props.children;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default ThemeProvider;
|
export default ThemeProvider;
|
||||||
export { ColorSchemeProvider };
|
export { ColorSchemeProvider };
|
||||||
|
|||||||
@@ -1,41 +1,39 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
"name": "Chromacase",
|
name: 'Chromacase',
|
||||||
"slug": "Chromacase",
|
slug: 'Chromacase',
|
||||||
"version": "1.0.0",
|
version: '1.0.0',
|
||||||
"orientation": "portrait",
|
orientation: 'portrait',
|
||||||
"icon": "./assets/icon.png",
|
icon: './assets/icon.png',
|
||||||
"userInterfaceStyle": "light",
|
userInterfaceStyle: 'light',
|
||||||
"splash": {
|
splash: {
|
||||||
"image": "./assets/splashLogo.png",
|
image: './assets/splashLogo.png',
|
||||||
"resizeMode": "contain",
|
resizeMode: 'contain',
|
||||||
"backgroundColor": "#ffffff"
|
backgroundColor: '#ffffff',
|
||||||
},
|
},
|
||||||
"updates": {
|
updates: {
|
||||||
"fallbackToCacheTimeout": 0
|
fallbackToCacheTimeout: 0,
|
||||||
},
|
},
|
||||||
"assetBundlePatterns": [
|
assetBundlePatterns: ['**/*'],
|
||||||
"**/*"
|
ios: {
|
||||||
],
|
supportsTablet: true,
|
||||||
"ios": {
|
},
|
||||||
"supportsTablet": true
|
android: {
|
||||||
},
|
adaptiveIcon: {
|
||||||
"android": {
|
foregroundImage: './assets/adaptive-icon.png',
|
||||||
"adaptiveIcon": {
|
backgroundColor: '#FFFFFF',
|
||||||
"foregroundImage": "./assets/adaptive-icon.png",
|
package: 'com.chromacase.chromacase',
|
||||||
"backgroundColor": "#FFFFFF",
|
versionCode: 1,
|
||||||
"package": "com.chromacase.chromacase",
|
},
|
||||||
"versionCode": 1
|
package: 'build.apk',
|
||||||
},
|
},
|
||||||
"package": "build.apk"
|
web: {
|
||||||
},
|
favicon: './assets/favicon.png',
|
||||||
"web": {
|
},
|
||||||
"favicon": "./assets/favicon.png"
|
extra: {
|
||||||
},
|
apiUrl: process.env.API_URL,
|
||||||
"extra": {
|
scoroUrl: process.env.SCORO_URL,
|
||||||
apiUrl: process.env.API_URL,
|
eas: {
|
||||||
scoroUrl: process.env.SCORO_URL,
|
projectId: 'dade8e5e-3e2c-49f7-98c5-cf8834c7ebb2',
|
||||||
"eas": {
|
},
|
||||||
"projectId": "dade8e5e-3e2c-49f7-98c5-cf8834c7ebb2"
|
},
|
||||||
}
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,39 +1,37 @@
|
|||||||
{
|
{
|
||||||
"expo": {
|
"expo": {
|
||||||
"name": "Chromacase",
|
"name": "Chromacase",
|
||||||
"slug": "Chromacase",
|
"slug": "Chromacase",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"orientation": "portrait",
|
"orientation": "portrait",
|
||||||
"icon": "./assets/icon.png",
|
"icon": "./assets/icon.png",
|
||||||
"userInterfaceStyle": "light",
|
"userInterfaceStyle": "light",
|
||||||
"splash": {
|
"splash": {
|
||||||
"image": "./assets/splashLogo.png",
|
"image": "./assets/splashLogo.png",
|
||||||
"resizeMode": "cover",
|
"resizeMode": "cover",
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#ffffff"
|
||||||
},
|
},
|
||||||
"updates": {
|
"updates": {
|
||||||
"fallbackToCacheTimeout": 0
|
"fallbackToCacheTimeout": 0
|
||||||
},
|
},
|
||||||
"assetBundlePatterns": [
|
"assetBundlePatterns": ["**/*"],
|
||||||
"**/*"
|
"ios": {
|
||||||
],
|
"supportsTablet": true
|
||||||
"ios": {
|
},
|
||||||
"supportsTablet": true
|
"android": {
|
||||||
},
|
"adaptiveIcon": {
|
||||||
"android": {
|
"foregroundImage": "./assets/adaptive-icon.png",
|
||||||
"adaptiveIcon": {
|
"backgroundColor": "#FFFFFF"
|
||||||
"foregroundImage": "./assets/adaptive-icon.png",
|
},
|
||||||
"backgroundColor": "#FFFFFF"
|
"package": "build.apk"
|
||||||
},
|
},
|
||||||
"package": "build.apk"
|
"web": {
|
||||||
},
|
"favicon": "./assets/favicon.png"
|
||||||
"web": {
|
},
|
||||||
"favicon": "./assets/favicon.png"
|
"extra": {
|
||||||
},
|
"eas": {
|
||||||
"extra": {
|
"projectId": "dade8e5e-3e2c-49f7-98c5-cf8834c7ebb2"
|
||||||
"eas": {
|
}
|
||||||
"projectId": "dade8e5e-3e2c-49f7-98c5-cf8834c7ebb2"
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
module.exports = function (api) {
|
module.exports = function (api) {
|
||||||
api.cache(true);
|
api.cache(true);
|
||||||
return {
|
return {
|
||||||
presets: ["babel-preset-expo"],
|
presets: ['babel-preset-expo'],
|
||||||
plugins: [
|
plugins: ['@babel/plugin-proposal-export-namespace-from', 'react-native-reanimated/plugin'],
|
||||||
"@babel/plugin-proposal-export-namespace-from",
|
|
||||||
"react-native-reanimated/plugin",
|
|
||||||
],
|
|
||||||
env: {
|
env: {
|
||||||
production: {
|
production: {
|
||||||
plugins: ["react-native-paper/babel"],
|
plugins: ['react-native-paper/babel'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import Card, { CardBorderRadius } from "./Card";
|
import Card, { CardBorderRadius } from './Card';
|
||||||
import { VStack, Text, Image } from "native-base";
|
import { VStack, Text, Image } from 'native-base';
|
||||||
import API from "../API";
|
|
||||||
|
|
||||||
type ArtistCardProps = {
|
type ArtistCardProps = {
|
||||||
image: string;
|
image: string;
|
||||||
@@ -11,7 +10,7 @@ type ArtistCardProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ArtistCard = (props: ArtistCardProps) => {
|
const ArtistCard = (props: ArtistCardProps) => {
|
||||||
const { image, name, id } = props;
|
const { image, name } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card shadow={3} onPress={props.onPress}>
|
<Card shadow={3} onPress={props.onPress}>
|
||||||
@@ -32,10 +31,10 @@ const ArtistCard = (props: ArtistCardProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ArtistCard.defaultProps = {
|
ArtistCard.defaultProps = {
|
||||||
image: "https://picsum.photos/200",
|
image: 'https://picsum.photos/200',
|
||||||
name: "Artist",
|
name: 'Artist',
|
||||||
id: 0,
|
id: 0,
|
||||||
onPress: () => { },
|
onPress: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ArtistCard;
|
export default ArtistCard;
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Center,
|
|
||||||
Heading,
|
Heading,
|
||||||
View,
|
|
||||||
Image,
|
Image,
|
||||||
Text,
|
Text,
|
||||||
Pressable,
|
Pressable,
|
||||||
@@ -11,9 +9,9 @@ import {
|
|||||||
Icon,
|
Icon,
|
||||||
Row,
|
Row,
|
||||||
PresenceTransition,
|
PresenceTransition,
|
||||||
} from "native-base";
|
} from 'native-base';
|
||||||
import { StyleProp, ViewStyle } from "react-native";
|
import { StyleProp, ViewStyle } from 'react-native';
|
||||||
import useColorScheme from "../hooks/colorScheme";
|
import useColorScheme from '../hooks/colorScheme';
|
||||||
|
|
||||||
type BigActionButtonProps = {
|
type BigActionButtonProps = {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -21,6 +19,8 @@ type BigActionButtonProps = {
|
|||||||
image: string;
|
image: string;
|
||||||
style?: StyleProp<ViewStyle>;
|
style?: StyleProp<ViewStyle>;
|
||||||
iconName?: string;
|
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;
|
iconProvider?: any;
|
||||||
onPress: () => void;
|
onPress: () => void;
|
||||||
};
|
};
|
||||||
@@ -34,27 +34,27 @@ const BigActionButton = ({
|
|||||||
iconProvider,
|
iconProvider,
|
||||||
onPress,
|
onPress,
|
||||||
}: BigActionButtonProps) => {
|
}: BigActionButtonProps) => {
|
||||||
const screenSize = useBreakpointValue({ base: "small", md: "big" });
|
const screenSize = useBreakpointValue({ base: 'small', md: 'big' });
|
||||||
const colorScheme = useColorScheme();
|
const colorScheme = useColorScheme();
|
||||||
const isDark = colorScheme === "dark";
|
const isDark = colorScheme === 'dark';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable onPress={onPress} style={style}>
|
<Pressable onPress={onPress} style={style}>
|
||||||
{({ isHovered, isPressed }) => {
|
{({ isHovered }) => {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: '100%',
|
||||||
height: "100%",
|
height: '100%',
|
||||||
position: "relative",
|
position: 'relative',
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
overflow: "hidden",
|
overflow: 'hidden',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PresenceTransition
|
<PresenceTransition
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: '100%',
|
||||||
height: "100%",
|
height: '100%',
|
||||||
}}
|
}}
|
||||||
visible={isHovered}
|
visible={isHovered}
|
||||||
initial={{
|
initial={{
|
||||||
@@ -68,15 +68,15 @@ const BigActionButton = ({
|
|||||||
source={{ uri: image }}
|
source={{ uri: image }}
|
||||||
alt="image"
|
alt="image"
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: '100%',
|
||||||
height: "100%",
|
height: '100%',
|
||||||
resizeMode: "cover",
|
resizeMode: 'cover',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</PresenceTransition>
|
</PresenceTransition>
|
||||||
<PresenceTransition
|
<PresenceTransition
|
||||||
style={{
|
style={{
|
||||||
height: "100%",
|
height: '100%',
|
||||||
}}
|
}}
|
||||||
visible={isHovered}
|
visible={isHovered}
|
||||||
initial={{
|
initial={{
|
||||||
@@ -90,24 +90,24 @@ const BigActionButton = ({
|
|||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: 'absolute',
|
||||||
left: "0",
|
left: '0',
|
||||||
width: "100%",
|
width: '100%',
|
||||||
height: "100%",
|
height: '100%',
|
||||||
backgroundColor: isDark ? "black" : "white",
|
backgroundColor: isDark ? 'black' : 'white',
|
||||||
padding: "10px",
|
padding: '10px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Row>
|
<Row>
|
||||||
<Icon
|
<Icon
|
||||||
as={iconProvider}
|
as={iconProvider}
|
||||||
name={iconName}
|
name={iconName}
|
||||||
size={screenSize === "small" ? "sm" : "md"}
|
size={screenSize === 'small' ? 'sm' : 'md'}
|
||||||
color={isDark ? "white" : "black"}
|
color={isDark ? 'white' : 'black'}
|
||||||
marginRight="10px"
|
marginRight="10px"
|
||||||
/>
|
/>
|
||||||
<Heading
|
<Heading
|
||||||
fontSize={screenSize === "small" ? "md" : "xl"}
|
fontSize={screenSize === 'small' ? 'md' : 'xl'}
|
||||||
isTruncated
|
isTruncated
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
@@ -126,7 +126,7 @@ const BigActionButton = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
fontSize={screenSize === "small" ? "sm" : "md"}
|
fontSize={screenSize === 'small' ? 'sm' : 'md'}
|
||||||
isTruncated
|
isTruncated
|
||||||
noOfLines={2}
|
noOfLines={2}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -9,31 +9,39 @@ export const CardBorderRadius = 10;
|
|||||||
const cardBorder = (theme: ReturnType<typeof useTheme>) => ({
|
const cardBorder = (theme: ReturnType<typeof useTheme>) => ({
|
||||||
borderColor: theme.colors.text[100],
|
borderColor: theme.colors.text[100],
|
||||||
borderRadius: CardBorderRadius,
|
borderRadius: CardBorderRadius,
|
||||||
borderWidth: 1
|
borderWidth: 1,
|
||||||
})
|
});
|
||||||
|
|
||||||
type CardProps = Parameters<typeof Box>[0] & {
|
type CardProps = Parameters<typeof Box>[0] & {
|
||||||
onPress: () => void
|
onPress: () => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
const Card = (props: CardProps) => {
|
const Card = (props: CardProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const colorScheme = useSelector((state: RootState) => state.settings.local.colorScheme);
|
const colorScheme = useSelector((state: RootState) => state.settings.local.colorScheme);
|
||||||
const systemColorMode = useColorScheme();
|
const systemColorMode = useColorScheme();
|
||||||
|
|
||||||
return <Pressable onPress={props.onPress}>
|
return (
|
||||||
{({ isHovered, isPressed }) => (
|
<Pressable onPress={props.onPress}>
|
||||||
<Box {...props} style={[props.style, cardBorder(theme)]}
|
{({ isHovered, isPressed }) => (
|
||||||
bg={(colorScheme == 'system' ? systemColorMode : colorScheme) == 'dark'
|
<Box
|
||||||
? (isHovered || isPressed) ? 'gray.800' : undefined
|
{...props}
|
||||||
: (isHovered || isPressed) ? 'coolGray.200' : undefined
|
style={[props.style, cardBorder(theme)]}
|
||||||
}
|
bg={
|
||||||
>
|
(colorScheme == 'system' ? systemColorMode : colorScheme) == 'dark'
|
||||||
{ props.children }
|
? isHovered || isPressed
|
||||||
</Box>
|
? 'gray.800'
|
||||||
)}
|
: undefined
|
||||||
</Pressable>
|
: isHovered || isPressed
|
||||||
|
? 'coolGray.200'
|
||||||
}
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Pressable>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default Card;
|
export default Card;
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import React from 'react';
|
|||||||
import { FlatGrid } from 'react-native-super-grid';
|
import { FlatGrid } from 'react-native-super-grid';
|
||||||
import { Heading, VStack } from 'native-base';
|
import { Heading, VStack } from 'native-base';
|
||||||
|
|
||||||
|
|
||||||
type CardGridCustomProps<T> = {
|
type CardGridCustomProps<T> = {
|
||||||
content: T[];
|
content: T[];
|
||||||
heading?: JSX.Element;
|
heading?: JSX.Element;
|
||||||
@@ -11,7 +10,7 @@ type CardGridCustomProps<T> = {
|
|||||||
cardComponent: React.ComponentType<T>;
|
cardComponent: React.ComponentType<T>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CardGridCustom = <T extends Record<string, any>>(props: CardGridCustomProps<T>) => {
|
const CardGridCustom = <T extends Record<string, unknown>>(props: CardGridCustomProps<T>) => {
|
||||||
const { content, heading, maxItemsPerRow, style, cardComponent: CardComponent } = props;
|
const { content, heading, maxItemsPerRow, style, cardComponent: CardComponent } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -28,4 +27,4 @@ const CardGridCustom = <T extends Record<string, any>>(props: CardGridCustomProp
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CardGridCustom;
|
export default CardGridCustom;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useNavigation } from "../Navigation";
|
import { useNavigation } from '../Navigation';
|
||||||
import { HStack, VStack, Text, Progress } from "native-base";
|
import { HStack, VStack, Text, Progress } from 'native-base';
|
||||||
import { translate } from "../i18n/i18n";
|
import { translate } from '../i18n/i18n';
|
||||||
import Card from './Card';
|
import Card from './Card';
|
||||||
|
|
||||||
type CompetenciesTableProps = {
|
type CompetenciesTableProps = {
|
||||||
@@ -10,26 +10,32 @@ type CompetenciesTableProps = {
|
|||||||
accuracyCompetency: number;
|
accuracyCompetency: number;
|
||||||
arpegeCompetency: number;
|
arpegeCompetency: number;
|
||||||
chordsCompetency: number;
|
chordsCompetency: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
const CompetenciesTable = (props: CompetenciesTableProps) => {
|
const CompetenciesTable = (props: CompetenciesTableProps) => {
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
return (
|
return (
|
||||||
<Card padding={5} onPress={() => navigation.navigate('User')} shadow={3}>
|
<Card padding={5} onPress={() => navigation.navigate('User')} shadow={3}>
|
||||||
<HStack space={5} flex={1}>
|
<HStack space={5} flex={1}>
|
||||||
<VStack space={5}>
|
<VStack space={5}>
|
||||||
{ Object.keys(props).map((competencyName, i) => (
|
{Object.keys(props).map((competencyName, i) => (
|
||||||
<Text bold key={i}>{translate(competencyName as keyof CompetenciesTableProps)}</Text>
|
<Text bold key={i}>
|
||||||
))}
|
{translate(competencyName as keyof CompetenciesTableProps)}
|
||||||
|
</Text>
|
||||||
|
))}
|
||||||
</VStack>
|
</VStack>
|
||||||
<VStack space={5} flex={1}>
|
<VStack space={5} flex={1}>
|
||||||
{ Object.keys(props).map((competencyName, i) => (
|
{Object.keys(props).map((competencyName, i) => (
|
||||||
<Progress key={i} flex={1} value={props[competencyName as keyof CompetenciesTableProps]} />
|
<Progress
|
||||||
))}
|
key={i}
|
||||||
|
flex={1}
|
||||||
|
value={props[competencyName as keyof CompetenciesTableProps]}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</VStack>
|
</VStack>
|
||||||
</HStack>
|
</HStack>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default CompetenciesTable
|
export default CompetenciesTable;
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import Card from "./Card";
|
import Card from './Card';
|
||||||
import { VStack, Text, Box, Icon, Image } from "native-base";
|
import { VStack, Text, Box, Image } from 'native-base';
|
||||||
import { useTheme } from "native-base";
|
import { useTheme } from 'native-base';
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
|
||||||
import API from "../API";
|
|
||||||
type GenreCardProps = {
|
type GenreCardProps = {
|
||||||
image: string;
|
image: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -12,7 +11,7 @@ type GenreCardProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const GenreCard = (props: GenreCardProps) => {
|
const GenreCard = (props: GenreCardProps) => {
|
||||||
const { image, name, id } = props;
|
const { image, name } = props;
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -45,9 +44,9 @@ const GenreCard = (props: GenreCardProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
GenreCard.defaultProps = {
|
GenreCard.defaultProps = {
|
||||||
icon: "https://picsum.photos/200",
|
icon: 'https://picsum.photos/200',
|
||||||
name: "Genre",
|
name: 'Genre',
|
||||||
onPress: () => { },
|
onPress: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default GenreCard;
|
export default GenreCard;
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import { ElementProps } from "./ElementTypes";
|
import { ElementProps } from './ElementTypes';
|
||||||
import { RawElement } from "./RawElement";
|
import { RawElement } from './RawElement';
|
||||||
import { Pressable, IPressableProps } from "native-base";
|
import { Pressable, IPressableProps } from 'native-base';
|
||||||
import { ElementTextProps, ElementToggleProps } from './ElementTypes';
|
|
||||||
|
|
||||||
export const Element = <T extends ElementProps,>(props: T) => {
|
export const Element = <T extends ElementProps>(props: T) => {
|
||||||
let actionFunction: IPressableProps['onPress'] = null;
|
let actionFunction: IPressableProps['onPress'] = null;
|
||||||
|
|
||||||
switch (props.type) {
|
switch (props.type) {
|
||||||
case "text":
|
case 'text':
|
||||||
actionFunction = props.data?.onPress;
|
actionFunction = props.data?.onPress;
|
||||||
break;
|
break;
|
||||||
case "toggle":
|
case 'toggle':
|
||||||
actionFunction = props.data?.onToggle;
|
actionFunction = props.data?.onToggle;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import { StyleProp, ViewStyle } from "react-native";
|
import { StyleProp, ViewStyle } from 'react-native';
|
||||||
import { Element } from "./Element";
|
import { Element } from './Element';
|
||||||
import useColorScheme from "../../hooks/colorScheme";
|
import useColorScheme from '../../hooks/colorScheme';
|
||||||
import { ElementProps } from "./ElementTypes";
|
import { ElementProps } from './ElementTypes';
|
||||||
|
|
||||||
import {
|
import { Box, Column, Divider } from 'native-base';
|
||||||
Box,
|
|
||||||
Column,
|
|
||||||
Divider,
|
|
||||||
} from "native-base";
|
|
||||||
|
|
||||||
type ElementListProps = {
|
type ElementListProps = {
|
||||||
elements: ElementProps[];
|
elements: ElementProps[];
|
||||||
@@ -17,23 +13,21 @@ type ElementListProps = {
|
|||||||
|
|
||||||
const ElementList = ({ elements, style }: ElementListProps) => {
|
const ElementList = ({ elements, style }: ElementListProps) => {
|
||||||
const colorScheme = useColorScheme();
|
const colorScheme = useColorScheme();
|
||||||
const isDark = colorScheme === "dark";
|
const isDark = colorScheme === 'dark';
|
||||||
const elementStyle = {
|
const elementStyle = {
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
boxShadow: isDark
|
boxShadow: isDark
|
||||||
? "0px 0px 3px 0px rgba(255,255,255,0.6)"
|
? '0px 0px 3px 0px rgba(255,255,255,0.6)'
|
||||||
: "0px 0px 3px 0px rgba(0,0,0,0.4)",
|
: '0px 0px 3px 0px rgba(0,0,0,0.4)',
|
||||||
overflow: "hidden",
|
overflow: 'hidden',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column style={[style, elementStyle]}>
|
<Column style={[style, elementStyle]}>
|
||||||
{elements.map((element, index, __) => (
|
{elements.map((element, index) => (
|
||||||
<Box key={element.title}>
|
<Box key={element.title}>
|
||||||
<Element {...element} />
|
<Element {...element} />
|
||||||
{ index < elements.length - 1 &&
|
{index < elements.length - 1 && <Divider />}
|
||||||
<Divider />
|
|
||||||
}
|
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
</Column>
|
</Column>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Select, Switch, Text, Icon, Row, Slider } from "native-base";
|
import { Select, Switch, Text, Icon, Row, Slider } from 'native-base';
|
||||||
import { MaterialIcons } from "@expo/vector-icons";
|
import { MaterialIcons } from '@expo/vector-icons';
|
||||||
|
|
||||||
export type ElementProps = {
|
export type ElementProps = {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -8,11 +8,11 @@ export type ElementProps = {
|
|||||||
description?: string;
|
description?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
} & (
|
} & (
|
||||||
{ type: 'text', data : ElementTextProps } |
|
| { type: 'text'; data: ElementTextProps }
|
||||||
{ type: 'toggle', data : ElementToggleProps } |
|
| { type: 'toggle'; data: ElementToggleProps }
|
||||||
{ type: 'dropdown', data : ElementDropdownProps } |
|
| { type: 'dropdown'; data: ElementDropdownProps }
|
||||||
{ type: 'range', data : ElementRangeProps } |
|
| { type: 'range'; data: ElementRangeProps }
|
||||||
{ type: 'custom', data : React.ReactNode }
|
| { type: 'custom'; data: React.ReactNode }
|
||||||
);
|
);
|
||||||
|
|
||||||
export type DropdownOption = {
|
export type DropdownOption = {
|
||||||
@@ -47,14 +47,11 @@ export type ElementRangeProps = {
|
|||||||
step?: number;
|
step?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getElementTextNode = (
|
export const getElementTextNode = ({ text, onPress }: ElementTextProps, disabled: boolean) => {
|
||||||
{ text, onPress }: ElementTextProps,
|
|
||||||
disabled: boolean
|
|
||||||
) => {
|
|
||||||
return (
|
return (
|
||||||
<Row
|
<Row
|
||||||
style={{
|
style={{
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
@@ -79,7 +76,7 @@ export const getElementTextNode = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getElementToggleNode = (
|
export const getElementToggleNode = (
|
||||||
{ onToggle, value, defaultValue }: ElementToggleProps,
|
{ value, defaultValue }: ElementToggleProps,
|
||||||
disabled: boolean
|
disabled: boolean
|
||||||
) => {
|
) => {
|
||||||
return (
|
return (
|
||||||
@@ -105,18 +102,14 @@ export const getElementDropdownNode = (
|
|||||||
isDisabled={disabled}
|
isDisabled={disabled}
|
||||||
>
|
>
|
||||||
{options.map((option) => (
|
{options.map((option) => (
|
||||||
<Select.Item
|
<Select.Item key={option.label} label={option.label} value={option.value} />
|
||||||
key={option.label}
|
|
||||||
label={option.label}
|
|
||||||
value={option.value}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getElementRangeNode = (
|
export const getElementRangeNode = (
|
||||||
{ onChange, value, defaultValue, min, max, step }: ElementRangeProps,
|
{ onChange, value, min, max, step }: ElementRangeProps,
|
||||||
disabled: boolean,
|
disabled: boolean,
|
||||||
title: string
|
title: string
|
||||||
) => {
|
) => {
|
||||||
|
|||||||
@@ -1,28 +1,14 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import {
|
import { Box, Button, Column, Icon, Popover, Row, Text, useBreakpointValue } from 'native-base';
|
||||||
Box,
|
import useColorScheme from '../../hooks/colorScheme';
|
||||||
Button,
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
Column,
|
import { ElementProps } from './ElementTypes';
|
||||||
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 {
|
import {
|
||||||
getElementDropdownNode,
|
getElementDropdownNode,
|
||||||
getElementTextNode,
|
getElementTextNode,
|
||||||
getElementToggleNode,
|
getElementToggleNode,
|
||||||
getElementRangeNode,
|
getElementRangeNode,
|
||||||
ElementDropdownProps,
|
} from './ElementTypes';
|
||||||
ElementTextProps,
|
|
||||||
ElementToggleProps,
|
|
||||||
ElementRangeProps,
|
|
||||||
} from "./ElementTypes";
|
|
||||||
|
|
||||||
type RawElementProps = {
|
type RawElementProps = {
|
||||||
element: ElementProps;
|
element: ElementProps;
|
||||||
@@ -30,25 +16,24 @@ type RawElementProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const RawElement = ({ element, isHovered }: RawElementProps) => {
|
export const RawElement = ({ element, isHovered }: RawElementProps) => {
|
||||||
const { title, icon, type, helperText, description, disabled, data } =
|
const { title, icon, type, helperText, description, disabled, data } = element;
|
||||||
element;
|
|
||||||
const colorScheme = useColorScheme();
|
const colorScheme = useColorScheme();
|
||||||
const isDark = colorScheme === "dark";
|
const isDark = colorScheme === 'dark';
|
||||||
const screenSize = useBreakpointValue({ base: "small", md: "big" });
|
const screenSize = useBreakpointValue({ base: 'small', md: 'big' });
|
||||||
const isSmallScreen = screenSize === "small";
|
const isSmallScreen = screenSize === 'small';
|
||||||
return (
|
return (
|
||||||
<Row
|
<Row
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: '100%',
|
||||||
height: 45,
|
height: 45,
|
||||||
padding: 15,
|
padding: 15,
|
||||||
justifyContent: "space-between",
|
justifyContent: 'space-between',
|
||||||
alignContent: "stretch",
|
alignContent: 'stretch',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
backgroundColor: isHovered
|
backgroundColor: isHovered
|
||||||
? isDark
|
? isDark
|
||||||
? "rgba(255, 255, 255, 0.1)"
|
? 'rgba(255, 255, 255, 0.1)'
|
||||||
: "rgba(0, 0, 0, 0.05)"
|
: 'rgba(0, 0, 0, 0.05)'
|
||||||
: undefined,
|
: undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -60,14 +45,14 @@ export const RawElement = ({ element, isHovered }: RawElementProps) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
<Column maxW={"90%"}>
|
<Column maxW={'90%'}>
|
||||||
<Text isTruncated maxW={"100%"}>
|
<Text isTruncated maxW={'100%'}>
|
||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
{description && (
|
{description && (
|
||||||
<Text
|
<Text
|
||||||
isTruncated
|
isTruncated
|
||||||
maxW={"100%"}
|
maxW={'100%'}
|
||||||
style={{
|
style={{
|
||||||
opacity: 0.6,
|
opacity: 0.6,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
@@ -86,7 +71,7 @@ export const RawElement = ({ element, isHovered }: RawElementProps) => {
|
|||||||
>
|
>
|
||||||
<Row
|
<Row
|
||||||
style={{
|
style={{
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
marginRight: 3,
|
marginRight: 3,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -99,7 +84,7 @@ export const RawElement = ({ element, isHovered }: RawElementProps) => {
|
|||||||
leftIcon={
|
leftIcon={
|
||||||
<Icon
|
<Icon
|
||||||
as={Ionicons}
|
as={Ionicons}
|
||||||
size={"md"}
|
size={'md'}
|
||||||
name="help-circle-outline"
|
name="help-circle-outline"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@@ -110,7 +95,7 @@ export const RawElement = ({ element, isHovered }: RawElementProps) => {
|
|||||||
<Popover.Content
|
<Popover.Content
|
||||||
accessibilityLabel={`Additionnal information for ${title}`}
|
accessibilityLabel={`Additionnal information for ${title}`}
|
||||||
style={{
|
style={{
|
||||||
maxWidth: isSmallScreen ? "90vw" : "20vw",
|
maxWidth: isSmallScreen ? '90vw' : '20vw',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Popover.Arrow />
|
<Popover.Arrow />
|
||||||
@@ -120,15 +105,15 @@ export const RawElement = ({ element, isHovered }: RawElementProps) => {
|
|||||||
)}
|
)}
|
||||||
{(() => {
|
{(() => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "text":
|
case 'text':
|
||||||
return getElementTextNode(data, disabled ?? false);
|
return getElementTextNode(data, disabled ?? false);
|
||||||
case "toggle":
|
case 'toggle':
|
||||||
return getElementToggleNode(data, disabled ?? false);
|
return getElementToggleNode(data, disabled ?? false);
|
||||||
case "dropdown":
|
case 'dropdown':
|
||||||
return getElementDropdownNode(data, disabled ?? false);
|
return getElementDropdownNode(data, disabled ?? false);
|
||||||
case "range":
|
case 'range':
|
||||||
return getElementRangeNode(data, disabled ?? false, title);
|
return getElementRangeNode(data, disabled ?? false, title);
|
||||||
case "custom":
|
case 'custom':
|
||||||
return data;
|
return data;
|
||||||
default:
|
default:
|
||||||
return <Text>Unknown type</Text>;
|
return <Text>Unknown type</Text>;
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ type SearchHistoryCardProps = {
|
|||||||
timestamp?: string;
|
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 { query, type, timestamp, onPress } = props;
|
||||||
|
|
||||||
const handlePress = () => {
|
const handlePress = () => {
|
||||||
@@ -18,18 +20,18 @@ const SearchHistoryCard = (props: SearchHistoryCardProps & { onPress: (query: st
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card shadow={2} onPress={handlePress} >
|
<Card shadow={2} onPress={handlePress}>
|
||||||
<VStack m={1.5} space={3}>
|
<VStack m={1.5} space={3}>
|
||||||
<Text fontSize="lg" fontWeight="bold">
|
<Text fontSize="lg" fontWeight="bold">
|
||||||
{query ?? "query"}
|
{query ?? 'query'}
|
||||||
</Text>
|
</Text>
|
||||||
<Text fontSize="lg" fontWeight="semibold">
|
<Text fontSize="lg" fontWeight="semibold">
|
||||||
{type ?? "type"}
|
{type ?? 'type'}
|
||||||
</Text>
|
</Text>
|
||||||
<Text color="gray.500">{timestamp ?? "timestamp"}</Text>
|
<Text color="gray.500">{timestamp ?? 'timestamp'}</Text>
|
||||||
</VStack>
|
</VStack>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SearchHistoryCard;
|
export default SearchHistoryCard;
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import { Box, Button } from "native-base";
|
import { Box, Button } from 'native-base';
|
||||||
|
|
||||||
type IconButtonProps = {
|
type IconButtonProps = {
|
||||||
icon: Parameters<typeof Button>[0]['leftIcon']
|
icon: Parameters<typeof Button>[0]['leftIcon'];
|
||||||
} & Omit<Parameters<typeof Button>[0], 'leftIcon' | 'rightIcon'>;
|
} & Omit<Parameters<typeof Button>[0], 'leftIcon' | 'rightIcon'>;
|
||||||
|
|
||||||
// Wrapper around Button for IconButton as Native's one sucks <3
|
// Wrapper around Button for IconButton as Native's one sucks <3
|
||||||
const IconButton = (props: IconButtonProps) => {
|
const IconButton = (props: IconButtonProps) => {
|
||||||
return <Box><Button {...props} leftIcon={props.icon} width='fit-content' rounded='sm'/></Box>
|
return (
|
||||||
}
|
<Box>
|
||||||
|
<Button {...props} leftIcon={props.icon} width="fit-content" rounded="sm" />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default IconButton;
|
export default IconButton;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||||
import Loading from "./Loading";
|
import Loading from './Loading';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "Loading",
|
title: 'Loading',
|
||||||
component: Loading,
|
component: Loading,
|
||||||
} as ComponentMeta<typeof Loading>;
|
} as ComponentMeta<typeof Loading>;
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
import { useTheme } from "native-base";
|
import { useTheme } from 'native-base';
|
||||||
import { Center, Spinner } from "native-base";
|
import { Center, Spinner } from 'native-base';
|
||||||
const LoadingComponent = () => {
|
const LoadingComponent = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return <Spinner color={theme.colors.primary[500]}/>
|
return <Spinner color={theme.colors.primary[500]} />;
|
||||||
}
|
};
|
||||||
|
|
||||||
const LoadingView = () => {
|
const LoadingView = () => {
|
||||||
return <Center style={{ flexGrow: 1 }}>
|
return (
|
||||||
<LoadingComponent/>
|
<Center style={{ flexGrow: 1 }}>
|
||||||
</Center>
|
<LoadingComponent />
|
||||||
}
|
</Center>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default LoadingComponent;
|
export default LoadingComponent;
|
||||||
export { LoadingView }
|
export { LoadingView };
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
|
/* eslint-disable no-mixed-spaces-and-tabs */
|
||||||
// Inspired from OSMD example project
|
// Inspired from OSMD example project
|
||||||
// https://github.com/opensheetmusicdisplay/react-opensheetmusicdisplay/blob/master/src/lib/OpenSheetMusicDisplay.jsx
|
// https://github.com/opensheetmusicdisplay/react-opensheetmusicdisplay/blob/master/src/lib/OpenSheetMusicDisplay.jsx
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { CursorType, Fraction, OpenSheetMusicDisplay as OSMD, IOSMDOptions, Note, Pitch } from 'opensheetmusicdisplay';
|
import {
|
||||||
|
CursorType,
|
||||||
|
Fraction,
|
||||||
|
OpenSheetMusicDisplay as OSMD,
|
||||||
|
IOSMDOptions,
|
||||||
|
Note,
|
||||||
|
} from 'opensheetmusicdisplay';
|
||||||
import useColorScheme from '../hooks/colorScheme';
|
import useColorScheme from '../hooks/colorScheme';
|
||||||
import { useWindowDimensions } from 'react-native';
|
import { useWindowDimensions } from 'react-native';
|
||||||
import SoundFont from 'soundfont-player';
|
import SoundFont from 'soundfont-player';
|
||||||
@@ -12,9 +19,9 @@ type PartitionViewProps = {
|
|||||||
file: string;
|
file: string;
|
||||||
onPartitionReady: () => void;
|
onPartitionReady: () => void;
|
||||||
onEndReached: () => void;
|
onEndReached: () => void;
|
||||||
// Timestamp of the play session, in milisecond
|
// Timestamp of the play session, in milisecond
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
const PartitionView = (props: PartitionViewProps) => {
|
const PartitionView = (props: PartitionViewProps) => {
|
||||||
const [osmd, setOsmd] = useState<OSMD>();
|
const [osmd, setOsmd] = useState<OSMD>();
|
||||||
@@ -34,15 +41,15 @@ const PartitionView = (props: PartitionViewProps) => {
|
|||||||
renderSingleHorizontalStaffline: true,
|
renderSingleHorizontalStaffline: true,
|
||||||
cursorsOptions: [{ type: CursorType.Standard, color: 'green', alpha: 0.5, follow: false }],
|
cursorsOptions: [{ type: CursorType.Standard, color: 'green', alpha: 0.5, follow: false }],
|
||||||
autoResize: false,
|
autoResize: false,
|
||||||
}
|
};
|
||||||
// Turns note.Length or timestamp in ms
|
// Turns note.Length or timestamp in ms
|
||||||
const timestampToMs = (timestamp: Fraction) => {
|
const timestampToMs = (timestamp: Fraction) => {
|
||||||
return timestamp.RealValue * wholeNoteLength;
|
return timestamp.RealValue * wholeNoteLength;
|
||||||
}
|
};
|
||||||
const getActualNoteLength = (note: Note) => {
|
const getActualNoteLength = (note: Note) => {
|
||||||
let duration = timestampToMs(note.Length)
|
let duration = timestampToMs(note.Length);
|
||||||
if (note.NoteTie) {
|
if (note.NoteTie) {
|
||||||
const firstNote = note.NoteTie.Notes.at(1)
|
const firstNote = note.NoteTie.Notes.at(1);
|
||||||
if (Object.is(note.NoteTie.StartNote, note) && firstNote) {
|
if (Object.is(note.NoteTie.StartNote, note) && firstNote) {
|
||||||
duration += timestampToMs(firstNote.Length);
|
duration += timestampToMs(firstNote.Length);
|
||||||
} else {
|
} else {
|
||||||
@@ -50,44 +57,52 @@ const PartitionView = (props: PartitionViewProps) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return duration;
|
return duration;
|
||||||
}
|
};
|
||||||
|
|
||||||
const playNotesUnderCursor = () => {
|
const playNotesUnderCursor = () => {
|
||||||
osmd!.cursor.NotesUnderCursor()
|
osmd!.cursor
|
||||||
|
.NotesUnderCursor()
|
||||||
.filter((note) => note.isRest() == false)
|
.filter((note) => note.isRest() == false)
|
||||||
.filter((note) => note.Pitch) // Pitch Can be null, avoiding them
|
.filter((note) => note.Pitch) // Pitch Can be null, avoiding them
|
||||||
.forEach((note) => {
|
.forEach((note) => {
|
||||||
// Put your hands together for https://github.com/jimutt/osmd-audio-player/blob/master/src/internals/noteHelpers.ts
|
// Put your hands together for https://github.com/jimutt/osmd-audio-player/blob/master/src/internals/noteHelpers.ts
|
||||||
const fixedKey = note.ParentVoiceEntry.ParentVoice.Parent.SubInstruments.at(0)?.fixedKey ?? 0;
|
const fixedKey =
|
||||||
|
note.ParentVoiceEntry.ParentVoice.Parent.SubInstruments.at(0)?.fixedKey ?? 0;
|
||||||
const midiNumber = note.halfTone - fixedKey * 12;
|
const midiNumber = note.halfTone - fixedKey * 12;
|
||||||
// console.log('Expecting midi ' + midiNumber);
|
// console.log('Expecting midi ' + midiNumber);
|
||||||
let duration = getActualNoteLength(note);
|
const duration = getActualNoteLength(note);
|
||||||
const gain = note.ParentVoiceEntry.ParentVoice.Volume;
|
const gain = note.ParentVoiceEntry.ParentVoice.Volume;
|
||||||
soundPlayer!.play(midiNumber.toString(), audioContext.currentTime, { duration, gain })
|
soundPlayer!.play(midiNumber.toString(), audioContext.currentTime, {
|
||||||
|
duration,
|
||||||
|
gain,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
const getShortedNoteUnderCursor = () => {
|
const getShortedNoteUnderCursor = () => {
|
||||||
return osmd!.cursor.NotesUnderCursor().sort((n1, n2) => n1.Length.CompareTo(n2.Length)).at(0);
|
return osmd!.cursor
|
||||||
}
|
.NotesUnderCursor()
|
||||||
|
.sort((n1, n2) => n1.Length.CompareTo(n2.Length))
|
||||||
|
.at(0);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const _osmd = new OSMD(OSMD_DIV_ID, options);
|
const _osmd = new OSMD(OSMD_DIV_ID, options);
|
||||||
Promise.all([
|
Promise.all([
|
||||||
SoundFont.instrument(audioContext as unknown as AudioContext, 'electric_piano_1'),
|
SoundFont.instrument(audioContext as unknown as AudioContext, 'electric_piano_1'),
|
||||||
_osmd.load(props.file)
|
_osmd.load(props.file),
|
||||||
]).then(([player, __]) => {
|
]).then(([player]) => {
|
||||||
setSoundPlayer(player);
|
setSoundPlayer(player);
|
||||||
_osmd.render();
|
_osmd.render();
|
||||||
_osmd.cursor.hide();
|
_osmd.cursor.hide();
|
||||||
// Ty https://github.com/jimutt/osmd-audio-player/blob/ec205a6e46ee50002c1fa8f5999389447bba7bbf/src/PlaybackEngine.ts#LL77C12-L77C63
|
// Ty https://github.com/jimutt/osmd-audio-player/blob/ec205a6e46ee50002c1fa8f5999389447bba7bbf/src/PlaybackEngine.ts#LL77C12-L77C63
|
||||||
const bpm = _osmd.Sheet.HasBPMInfo ? _osmd.Sheet.getExpressionsStartTempoInBPM() : 60;
|
const bpm = _osmd.Sheet.HasBPMInfo ? _osmd.Sheet.getExpressionsStartTempoInBPM() : 60;
|
||||||
setWholeNoteLength(Math.round((60 / bpm) * 4000))
|
setWholeNoteLength(Math.round((60 / bpm) * 4000));
|
||||||
props.onPartitionReady();
|
props.onPartitionReady();
|
||||||
// Do not show cursor before actuall start
|
// Do not show cursor before actuall start
|
||||||
});
|
});
|
||||||
setOsmd(_osmd);
|
setOsmd(_osmd);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Re-render manually (otherwise done by 'autoResize' option), to fix disappearing cursor
|
// Re-render manually (otherwise done by 'autoResize' option), to fix disappearing cursor
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (osmd && osmd.IsReadyToRender()) {
|
if (osmd && osmd.IsReadyToRender()) {
|
||||||
@@ -96,7 +111,7 @@ const PartitionView = (props: PartitionViewProps) => {
|
|||||||
osmd.cursor.show();
|
osmd.cursor.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [dimensions])
|
}, [dimensions]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!osmd || !soundPlayer) {
|
if (!osmd || !soundPlayer) {
|
||||||
@@ -110,10 +125,14 @@ const PartitionView = (props: PartitionViewProps) => {
|
|||||||
let previousCursorPosition = -1;
|
let previousCursorPosition = -1;
|
||||||
let currentCursorPosition = osmd.cursor.cursorElement.offsetLeft;
|
let currentCursorPosition = osmd.cursor.cursorElement.offsetLeft;
|
||||||
let shortestNote = getShortedNoteUnderCursor();
|
let shortestNote = getShortedNoteUnderCursor();
|
||||||
while(!osmd.cursor.iterator.EndReached && (shortestNote?.isRest
|
while (
|
||||||
? timestampToMs(shortestNote?.getAbsoluteTimestamp() ?? new Fraction(-1)) +
|
!osmd.cursor.iterator.EndReached &&
|
||||||
timestampToMs(shortestNote?.Length ?? new Fraction(-1)) < props.timestamp
|
(shortestNote?.isRest
|
||||||
: timestampToMs(shortestNote?.getAbsoluteTimestamp() ?? new Fraction(-1)) < props.timestamp)
|
? timestampToMs(shortestNote?.getAbsoluteTimestamp() ?? new Fraction(-1)) +
|
||||||
|
timestampToMs(shortestNote?.Length ?? new Fraction(-1)) <
|
||||||
|
props.timestamp
|
||||||
|
: timestampToMs(shortestNote?.getAbsoluteTimestamp() ?? new Fraction(-1)) <
|
||||||
|
props.timestamp)
|
||||||
) {
|
) {
|
||||||
previousCursorPosition = currentCursorPosition;
|
previousCursorPosition = currentCursorPosition;
|
||||||
osmd.cursor.next();
|
osmd.cursor.next();
|
||||||
@@ -125,13 +144,15 @@ const PartitionView = (props: PartitionViewProps) => {
|
|||||||
// Shamelessly stolen from https://github.com/jimutt/osmd-audio-player/blob/ec205a6e46ee50002c1fa8f5999389447bba7bbf/src/PlaybackEngine.ts#LL223C7-L224C1
|
// Shamelessly stolen from https://github.com/jimutt/osmd-audio-player/blob/ec205a6e46ee50002c1fa8f5999389447bba7bbf/src/PlaybackEngine.ts#LL223C7-L224C1
|
||||||
playNotesUnderCursor();
|
playNotesUnderCursor();
|
||||||
currentCursorPosition = osmd.cursor.cursorElement.offsetLeft;
|
currentCursorPosition = osmd.cursor.cursorElement.offsetLeft;
|
||||||
document.getElementById(OSMD_DIV_ID)?.scrollBy(currentCursorPosition - previousCursorPosition, 0)
|
document
|
||||||
|
.getElementById(OSMD_DIV_ID)
|
||||||
|
?.scrollBy(currentCursorPosition - previousCursorPosition, 0);
|
||||||
shortestNote = getShortedNoteUnderCursor();
|
shortestNote = getShortedNoteUnderCursor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [props.timestamp]);
|
}, [props.timestamp]);
|
||||||
|
|
||||||
return (<div id={OSMD_DIV_ID} style={{ width: '100%', overflow: 'hidden' }} />);
|
return <div id={OSMD_DIV_ID} style={{ width: '100%', overflow: 'hidden' }} />;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default PartitionView;
|
export default PartitionView;
|
||||||
|
|||||||
@@ -1,15 +1,6 @@
|
|||||||
import { useTheme, Box, Center } from "native-base";
|
import React from 'react';
|
||||||
import React from "react";
|
|
||||||
import { useQuery } from "react-query";
|
|
||||||
import LoadingComponent, { LoadingView } from "../Loading";
|
|
||||||
import SlideView from "./SlideView";
|
|
||||||
import API from "../../API";
|
|
||||||
|
|
||||||
type PartitionVisualizerProps = {
|
const PartitionVisualizer = () => {
|
||||||
songId: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const PartitionVisualizer = ({ songId }: PartitionVisualizerProps) => {
|
|
||||||
return <></>;
|
return <></>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,9 @@
|
|||||||
import {
|
import { Box, Image, Row, Column, Button, Icon } from 'native-base';
|
||||||
useTheme,
|
import IconButton from '../IconButton';
|
||||||
Box,
|
import { MotiView, useDynamicAnimation } from 'moti';
|
||||||
Image,
|
import { Easing } from 'react-native-reanimated';
|
||||||
Row,
|
import React from 'react';
|
||||||
Column,
|
import { FontAwesome5, MaterialCommunityIcons } from '@expo/vector-icons';
|
||||||
ZStack,
|
|
||||||
Button,
|
|
||||||
Icon,
|
|
||||||
} from "native-base";
|
|
||||||
import IconButton from "../IconButton";
|
|
||||||
import { MotiView, useDynamicAnimation } from "moti";
|
|
||||||
import { abs, Easing } from "react-native-reanimated";
|
|
||||||
import React from "react";
|
|
||||||
import { FontAwesome5, MaterialCommunityIcons } from "@expo/vector-icons";
|
|
||||||
|
|
||||||
type ImgSlideViewProps = {
|
type ImgSlideViewProps = {
|
||||||
sources: [url: string, width: number, height: number][];
|
sources: [url: string, width: number, height: number][];
|
||||||
@@ -31,6 +22,8 @@ const range = (start: number, end: number, step: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SlideView = ({ sources, speed, startAt }: ImgSlideViewProps) => {
|
const SlideView = ({ sources, speed, startAt }: ImgSlideViewProps) => {
|
||||||
|
// To get the width, we have to ditch the fst of the tuple
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const totalWidth = sources.reduce((acc, [_, width]) => acc + width, 0);
|
const totalWidth = sources.reduce((acc, [_, width]) => acc + width, 0);
|
||||||
const stepSize = speed / 2;
|
const stepSize = speed / 2;
|
||||||
const stepDuration = 1000 / 2;
|
const stepDuration = 1000 / 2;
|
||||||
@@ -45,7 +38,7 @@ const SlideView = ({ sources, speed, startAt }: ImgSlideViewProps) => {
|
|||||||
animation.animateTo({
|
animation.animateTo({
|
||||||
translateX: -nbPixelsToSkip,
|
translateX: -nbPixelsToSkip,
|
||||||
transition: {
|
transition: {
|
||||||
type: "timing",
|
type: 'timing',
|
||||||
delay: 0,
|
delay: 0,
|
||||||
easing: Easing.linear,
|
easing: Easing.linear,
|
||||||
},
|
},
|
||||||
@@ -69,16 +62,11 @@ const SlideView = ({ sources, speed, startAt }: ImgSlideViewProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
<Box overflow={"hidden"}>
|
<Box overflow={'hidden'}>
|
||||||
<MotiView
|
<MotiView
|
||||||
state={animation}
|
state={animation}
|
||||||
onDidAnimate={(
|
onDidAnimate={(styleProp, didAnimationFinish) => {
|
||||||
styleProp,
|
if (styleProp === 'translateX' && didAnimationFinish) {
|
||||||
didAnimationFinish,
|
|
||||||
_maybeValue,
|
|
||||||
{ attemptedValue }
|
|
||||||
) => {
|
|
||||||
if (styleProp === "translateX" && didAnimationFinish) {
|
|
||||||
stepCount++;
|
stepCount++;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -102,11 +90,9 @@ const SlideView = ({ sources, speed, startAt }: ImgSlideViewProps) => {
|
|||||||
icon={<Icon as={FontAwesome5} name="play" size="sm" />}
|
icon={<Icon as={FontAwesome5} name="play" size="sm" />}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
animation.animateTo({
|
animation.animateTo({
|
||||||
translateX: range(-totalWidth, 0, stepSize)
|
translateX: range(-totalWidth, 0, stepSize).reverse().slice(stepCount),
|
||||||
.reverse()
|
|
||||||
.slice(stepCount),
|
|
||||||
transition: {
|
transition: {
|
||||||
type: "timing",
|
type: 'timing',
|
||||||
easing: Easing.linear,
|
easing: Easing.linear,
|
||||||
duration: stepDuration,
|
duration: stepDuration,
|
||||||
},
|
},
|
||||||
@@ -120,19 +106,11 @@ const SlideView = ({ sources, speed, startAt }: ImgSlideViewProps) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={
|
icon={<Icon as={MaterialCommunityIcons} name="rewind-10" size="sm" />}
|
||||||
<Icon as={MaterialCommunityIcons} name="rewind-10" size="sm" />
|
|
||||||
}
|
|
||||||
onPress={() => jumpAt(-200, false)}
|
onPress={() => jumpAt(-200, false)}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={
|
icon={<Icon as={MaterialCommunityIcons} name="fast-forward-10" size="sm" />}
|
||||||
<Icon
|
|
||||||
as={MaterialCommunityIcons}
|
|
||||||
name="fast-forward-10"
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
onPress={() => jumpAt(200, false)}
|
onPress={() => jumpAt(200, false)}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|||||||
@@ -1,36 +1,44 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import { translate } from "../i18n/i18n";
|
import { translate } from '../i18n/i18n';
|
||||||
import { Box, useBreakpointValue, Text, VStack, Progress, Stack, AspectRatio } from 'native-base';
|
import { Box, Text, VStack, Progress, Stack, AspectRatio } from 'native-base';
|
||||||
import { useNavigation } from "../Navigation";
|
import { useNavigation } from '../Navigation';
|
||||||
import { Pressable, Image } from "native-base";
|
import { Image } from 'native-base';
|
||||||
import Card from "../components/Card";
|
import Card from '../components/Card';
|
||||||
|
|
||||||
const ProgressBar = ({ xp }: { xp: number}) => {
|
const ProgressBar = ({ xp }: { xp: number }) => {
|
||||||
const level = Math.floor(xp / 1000);
|
const level = Math.floor(xp / 1000);
|
||||||
const nextLevel = level + 1;
|
const nextLevel = level + 1;
|
||||||
const nextLevelThreshold = nextLevel * 1000;
|
const nextLevelThreshold = nextLevel * 1000;
|
||||||
const progessValue = 100 * xp / nextLevelThreshold;
|
const progessValue = (100 * xp) / nextLevelThreshold;
|
||||||
|
|
||||||
const nav = useNavigation();
|
const nav = useNavigation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card w="100%" onPress={() => nav.navigate('User')} >
|
<Card w="100%" onPress={() => nav.navigate('User')}>
|
||||||
<Stack padding={4} space={2} direction="row">
|
<Stack padding={4} space={2} direction="row">
|
||||||
<AspectRatio ratio={1}>
|
<AspectRatio ratio={1}>
|
||||||
<Image position="relative" borderRadius={100} source={{
|
<Image
|
||||||
uri: "https://wallpaperaccess.com/full/317501.jpg" // TODO : put the actual profile pic
|
position="relative"
|
||||||
}} alt="Profile picture" zIndex={0}/>
|
borderRadius={100}
|
||||||
|
source={{
|
||||||
|
uri: 'https://wallpaperaccess.com/full/317501.jpg', // TODO : put the actual profile pic
|
||||||
|
}}
|
||||||
|
alt="Profile picture"
|
||||||
|
zIndex={0}
|
||||||
|
/>
|
||||||
</AspectRatio>
|
</AspectRatio>
|
||||||
<VStack alignItems={'center'} flexGrow={1} space={2}>
|
<VStack alignItems={'center'} flexGrow={1} space={2}>
|
||||||
<Text>{`${translate('level')} ${level}`}</Text>
|
<Text>{`${translate('level')} ${level}`}</Text>
|
||||||
<Box w="100%">
|
<Box w="100%">
|
||||||
<Progress value={progessValue} mx="4" />
|
<Progress value={progessValue} mx="4" />
|
||||||
</Box>
|
</Box>
|
||||||
<Text>{xp} / {nextLevelThreshold} {translate('levelProgress')}</Text>
|
<Text>
|
||||||
|
{xp} / {nextLevelThreshold} {translate('levelProgress')}
|
||||||
|
</Text>
|
||||||
</VStack>
|
</VStack>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default ProgressBar;
|
export default ProgressBar;
|
||||||
|
|||||||
@@ -1,19 +1,11 @@
|
|||||||
import {
|
import { Icon, Input, Button, Flex } from 'native-base';
|
||||||
Icon,
|
import React from 'react';
|
||||||
Input,
|
import { MaterialIcons } from '@expo/vector-icons';
|
||||||
Button,
|
import { translate } from '../i18n/i18n';
|
||||||
Flex} from "native-base";
|
import { SearchContext } from '../views/SearchView';
|
||||||
import React from "react";
|
|
||||||
import { MaterialIcons } from "@expo/vector-icons";
|
|
||||||
import { translate } from "../i18n/i18n";
|
|
||||||
import { SearchContext } from "../views/SearchView";
|
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
|
|
||||||
export type Filter = "artist" | "song" | "genre" | "all";
|
export type Filter = 'artist' | 'song' | 'genre' | 'all';
|
||||||
|
|
||||||
type SearchBarProps = {
|
|
||||||
onChangeText?: any;
|
|
||||||
};
|
|
||||||
|
|
||||||
type FilterButton = {
|
type FilterButton = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -21,9 +13,9 @@ type FilterButton = {
|
|||||||
id: Filter;
|
id: Filter;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SearchBar = (props: SearchBarProps) => {
|
const SearchBar = () => {
|
||||||
const {filter, updateFilter} = React.useContext(SearchContext);
|
const { filter, updateFilter } = React.useContext(SearchContext);
|
||||||
const {stringQuery, updateStringQuery} = React.useContext(SearchContext);
|
const { stringQuery, updateStringQuery } = React.useContext(SearchContext);
|
||||||
const [barText, updateBarText] = React.useState(stringQuery);
|
const [barText, updateBarText] = React.useState(stringQuery);
|
||||||
|
|
||||||
const debouncedUpdateStringQuery = debounce(updateStringQuery, 500);
|
const debouncedUpdateStringQuery = debounce(updateStringQuery, 500);
|
||||||
@@ -42,13 +34,13 @@ const SearchBar = (props: SearchBarProps) => {
|
|||||||
const handleChangeText = (text: string) => {
|
const handleChangeText = (text: string) => {
|
||||||
debouncedUpdateStringQuery(text);
|
debouncedUpdateStringQuery(text);
|
||||||
updateBarText(text);
|
updateBarText(text);
|
||||||
}
|
};
|
||||||
|
|
||||||
const filters: FilterButton[] = [
|
const filters: FilterButton[] = [
|
||||||
{
|
{
|
||||||
name: translate('allFilter'),
|
name: translate('allFilter'),
|
||||||
callback: () => updateFilter('all'),
|
callback: () => updateFilter('all'),
|
||||||
id: 'all'
|
id: 'all',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: translate('artistFilter'),
|
name: translate('artistFilter'),
|
||||||
@@ -68,37 +60,39 @@ const SearchBar = (props: SearchBarProps) => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex m={3} flexDirection={["column", "row"]}>
|
<Flex m={3} flexDirection={['column', 'row']}>
|
||||||
<Input
|
<Input
|
||||||
onChangeText={(text) => handleChangeText(text)}
|
onChangeText={(text) => handleChangeText(text)}
|
||||||
variant={"rounded"}
|
variant={'rounded'}
|
||||||
value={barText}
|
value={barText}
|
||||||
rounded={"full"}
|
rounded={'full'}
|
||||||
placeholder={translate('search')}
|
placeholder={translate('search')}
|
||||||
width={['100%', '50%']} //responsive array syntax with native-base
|
width={['100%', '50%']} //responsive array syntax with native-base
|
||||||
py={2}
|
py={2}
|
||||||
px={2}
|
px={2}
|
||||||
fontSize={'12'}
|
fontSize={'12'}
|
||||||
InputLeftElement={
|
InputLeftElement={
|
||||||
<Icon
|
<Icon
|
||||||
m={[1, 2]}
|
m={[1, 2]}
|
||||||
ml={[2, 3]}
|
ml={[2, 3]}
|
||||||
size={['4', '6']}
|
size={['4', '6']}
|
||||||
color="gray.400"
|
color="gray.400"
|
||||||
as={<MaterialIcons name="search" />}
|
as={<MaterialIcons name="search" />}
|
||||||
/>
|
/>
|
||||||
|
}
|
||||||
|
InputRightElement={
|
||||||
|
<Icon
|
||||||
|
m={[1, 2]}
|
||||||
|
mr={[2, 3]}
|
||||||
|
size={['4', '6']}
|
||||||
|
color="gray.400"
|
||||||
|
onPress={handleClearQuery}
|
||||||
|
as={<MaterialIcons name="close" />}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
InputRightElement={<Icon
|
|
||||||
m={[1, 2]}
|
|
||||||
mr={[2, 3]}
|
|
||||||
size={['4', '6']}
|
|
||||||
color="gray.400"
|
|
||||||
onPress={handleClearQuery}
|
|
||||||
as={<MaterialIcons name="close" />}
|
|
||||||
/>}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Flex flexDirection={'row'} >
|
<Flex flexDirection={'row'}>
|
||||||
{filters.map((btn) => (
|
{filters.map((btn) => (
|
||||||
<Button
|
<Button
|
||||||
key={btn.name}
|
key={btn.name}
|
||||||
@@ -107,13 +101,14 @@ const SearchBar = (props: SearchBarProps) => {
|
|||||||
mx={[2, 5]}
|
mx={[2, 5]}
|
||||||
my={[1, 0]}
|
my={[1, 0]}
|
||||||
minW={[30, 20]}
|
minW={[30, 20]}
|
||||||
variant={filter === btn.id ? 'solid' : 'outline'}>
|
variant={filter === btn.id ? 'solid' : 'outline'}
|
||||||
|
>
|
||||||
{btn.name}
|
{btn.name}
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default SearchBar;
|
export default SearchBar;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useContext, useEffect, useState } from "react";
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
HStack,
|
HStack,
|
||||||
VStack,
|
VStack,
|
||||||
@@ -12,34 +12,32 @@ import {
|
|||||||
useBreakpointValue,
|
useBreakpointValue,
|
||||||
Column,
|
Column,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
} from "native-base";
|
} from 'native-base';
|
||||||
import { SafeAreaView, useColorScheme } from "react-native";
|
import { SafeAreaView, useColorScheme } from 'react-native';
|
||||||
import { RootState, useSelector } from "../state/Store";
|
import { RootState, useSelector } from '../state/Store';
|
||||||
import { SearchContext } from "../views/SearchView";
|
import { SearchContext } from '../views/SearchView';
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from 'react-query';
|
||||||
import { translate } from "../i18n/i18n";
|
import { translate } from '../i18n/i18n';
|
||||||
import API from "../API";
|
import API from '../API';
|
||||||
import LoadingComponent from "./Loading";
|
import LoadingComponent from './Loading';
|
||||||
import ArtistCard from "./ArtistCard";
|
import ArtistCard from './ArtistCard';
|
||||||
import GenreCard from "./GenreCard";
|
import GenreCard from './GenreCard';
|
||||||
import SongCard from "./SongCard";
|
import SongCard from './SongCard';
|
||||||
import CardGridCustom from "./CardGridCustom";
|
import CardGridCustom from './CardGridCustom';
|
||||||
import TextButton from "./TextButton";
|
import TextButton from './TextButton';
|
||||||
import SearchHistoryCard from "./HistoryCard";
|
import SearchHistoryCard from './HistoryCard';
|
||||||
import Song, { SongWithArtist } from "../models/Song";
|
import Song, { SongWithArtist } from '../models/Song';
|
||||||
import { getSongWArtistSuggestions } from "./utils/api";
|
import { getSongWArtistSuggestions } from './utils/api';
|
||||||
import { useNavigation } from "../Navigation";
|
import { useNavigation } from '../Navigation';
|
||||||
|
|
||||||
const swaToSongCardProps = (song: SongWithArtist) => ({
|
const swaToSongCardProps = (song: SongWithArtist) => ({
|
||||||
songId: song.id,
|
songId: song.id,
|
||||||
name: song.name,
|
name: song.name,
|
||||||
artistName: song.artist.name,
|
artistName: song.artist.name,
|
||||||
cover: song.cover ?? "https://picsum.photos/200",
|
cover: song.cover ?? 'https://picsum.photos/200',
|
||||||
});
|
});
|
||||||
|
|
||||||
const RowCustom = (
|
const RowCustom = (props: Parameters<typeof Box>[0] & { onPress?: () => void }) => {
|
||||||
props: Parameters<typeof Box>[0] & { onPress?: () => void }
|
|
||||||
) => {
|
|
||||||
const settings = useSelector((state: RootState) => state.settings.local);
|
const settings = useSelector((state: RootState) => state.settings.local);
|
||||||
const systemColorMode = useColorScheme();
|
const systemColorMode = useColorScheme();
|
||||||
const colorScheme = settings.colorScheme;
|
const colorScheme = settings.colorScheme;
|
||||||
@@ -52,13 +50,13 @@ const RowCustom = (
|
|||||||
py={3}
|
py={3}
|
||||||
my={1}
|
my={1}
|
||||||
bg={
|
bg={
|
||||||
(colorScheme == "system" ? systemColorMode : colorScheme) == "dark"
|
(colorScheme == 'system' ? systemColorMode : colorScheme) == 'dark'
|
||||||
? isHovered || isPressed
|
? isHovered || isPressed
|
||||||
? "gray.800"
|
? 'gray.800'
|
||||||
: undefined
|
: undefined
|
||||||
: isHovered || isPressed
|
: isHovered || isPressed
|
||||||
? "coolGray.200"
|
? 'coolGray.200'
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
@@ -75,8 +73,8 @@ type SongRowProps = {
|
|||||||
|
|
||||||
const SongRow = ({ song, onPress }: SongRowProps) => {
|
const SongRow = ({ song, onPress }: SongRowProps) => {
|
||||||
return (
|
return (
|
||||||
<RowCustom width={"100%"}>
|
<RowCustom width={'100%'}>
|
||||||
<HStack px={2} space={5} justifyContent={"space-between"}>
|
<HStack px={2} space={5} justifyContent={'space-between'}>
|
||||||
<Image
|
<Image
|
||||||
flexShrink={0}
|
flexShrink={0}
|
||||||
flexGrow={0}
|
flexGrow={0}
|
||||||
@@ -87,11 +85,11 @@ const SongRow = ({ song, onPress }: SongRowProps) => {
|
|||||||
/>
|
/>
|
||||||
<HStack
|
<HStack
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexShrink: 1,
|
flexShrink: 1,
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
justifyContent: "flex-start",
|
justifyContent: 'flex-start',
|
||||||
}}
|
}}
|
||||||
space={6}
|
space={6}
|
||||||
>
|
>
|
||||||
@@ -101,7 +99,7 @@ const SongRow = ({ song, onPress }: SongRowProps) => {
|
|||||||
}}
|
}}
|
||||||
isTruncated
|
isTruncated
|
||||||
pl={10}
|
pl={10}
|
||||||
maxW={"100%"}
|
maxW={'100%'}
|
||||||
bold
|
bold
|
||||||
fontSize="md"
|
fontSize="md"
|
||||||
>
|
>
|
||||||
@@ -111,17 +109,17 @@ const SongRow = ({ song, onPress }: SongRowProps) => {
|
|||||||
style={{
|
style={{
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
}}
|
}}
|
||||||
fontSize={"sm"}
|
fontSize={'sm'}
|
||||||
>
|
>
|
||||||
{song.artistId ?? "artist"}
|
{song.artistId ?? 'artist'}
|
||||||
</Text>
|
</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
<TextButton
|
<TextButton
|
||||||
flexShrink={0}
|
flexShrink={0}
|
||||||
flexGrow={0}
|
flexGrow={0}
|
||||||
translate={{ translationKey: "playBtn" }}
|
translate={{ translationKey: 'playBtn' }}
|
||||||
colorScheme="primary"
|
colorScheme="primary"
|
||||||
variant={"outline"}
|
variant={'outline'}
|
||||||
size="sm"
|
size="sm"
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
/>
|
/>
|
||||||
@@ -131,26 +129,29 @@ const SongRow = ({ song, onPress }: SongRowProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
SongRow.defaultProps = {
|
SongRow.defaultProps = {
|
||||||
onPress: () => { },
|
onPress: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const HomeSearchComponent = () => {
|
const HomeSearchComponent = () => {
|
||||||
const { stringQuery, updateStringQuery } = React.useContext(SearchContext);
|
const { updateStringQuery } = React.useContext(SearchContext);
|
||||||
const { isLoading: isLoadingHistory, data: historyData = [] } = useQuery(
|
const { isLoading: isLoadingHistory, data: historyData = [] } = useQuery(
|
||||||
"history",
|
'history',
|
||||||
() => API.getSearchHistory(0, 12),
|
() => API.getSearchHistory(0, 12),
|
||||||
{ enabled: true }
|
{ enabled: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
const { isLoading: isLoadingSuggestions, data: suggestionsData = [] } =
|
const { isLoading: isLoadingSuggestions, data: suggestionsData = [] } = useQuery(
|
||||||
useQuery("suggestions", () => getSongWArtistSuggestions(), {
|
'suggestions',
|
||||||
|
() => getSongWArtistSuggestions(),
|
||||||
|
{
|
||||||
enabled: true,
|
enabled: true,
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack mt="5" style={{ overflow: "hidden" }}>
|
<VStack mt="5" style={{ overflow: 'hidden' }}>
|
||||||
<Card shadow={3} mb={5}>
|
<Card shadow={3} mb={5}>
|
||||||
<Heading margin={5}>{translate("lastSearched")}</Heading>
|
<Heading margin={5}>{translate('lastSearched')}</Heading>
|
||||||
{isLoadingHistory ? (
|
{isLoadingHistory ? (
|
||||||
<LoadingComponent />
|
<LoadingComponent />
|
||||||
) : (
|
) : (
|
||||||
@@ -169,7 +170,7 @@ const HomeSearchComponent = () => {
|
|||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
<Card shadow={3} mt={5} mb={5}>
|
<Card shadow={3} mt={5} mb={5}>
|
||||||
<Heading margin={5}>{translate("songsToGetBetter")}</Heading>
|
<Heading margin={5}>{translate('songsToGetBetter')}</Heading>
|
||||||
{isLoadingSuggestions ? (
|
{isLoadingSuggestions ? (
|
||||||
<LoadingComponent />
|
<LoadingComponent />
|
||||||
) : (
|
) : (
|
||||||
@@ -183,14 +184,18 @@ const HomeSearchComponent = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SongsSearchComponent = (props: any) => {
|
type SongsSearchComponentProps = {
|
||||||
|
maxRows?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SongsSearchComponent = (props: SongsSearchComponentProps) => {
|
||||||
const { songData } = React.useContext(SearchContext);
|
const { songData } = React.useContext(SearchContext);
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
<Text fontSize="xl" fontWeight="bold" mt={4}>
|
<Text fontSize="xl" fontWeight="bold" mt={4}>
|
||||||
{translate("songsFilter")}
|
{translate('songsFilter')}
|
||||||
</Text>
|
</Text>
|
||||||
<Box>
|
<Box>
|
||||||
{songData?.length ? (
|
{songData?.length ? (
|
||||||
@@ -199,94 +204,94 @@ const SongsSearchComponent = (props: any) => {
|
|||||||
key={index}
|
key={index}
|
||||||
song={comp}
|
song={comp}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
API.createSearchHistoryEntry(comp.name, "song", Date.now());
|
API.createSearchHistoryEntry(comp.name, 'song');
|
||||||
navigation.navigate("Song", { songId: comp.id });
|
navigation.navigate('Song', { songId: comp.id });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<Text>{translate("errNoResults")}</Text>
|
<Text>{translate('errNoResults')}</Text>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ArtistSearchComponent = (props: any) => {
|
type ItemSearchComponentProps = {
|
||||||
|
maxItems?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ArtistSearchComponent = (props: ItemSearchComponentProps) => {
|
||||||
const { artistData } = React.useContext(SearchContext);
|
const { artistData } = React.useContext(SearchContext);
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Text fontSize="xl" fontWeight="bold" mt={4}>
|
<Text fontSize="xl" fontWeight="bold" mt={4}>
|
||||||
{translate("artistFilter")}
|
{translate('artistFilter')}
|
||||||
</Text>
|
</Text>
|
||||||
{artistData?.length ? (
|
{artistData?.length ? (
|
||||||
<CardGridCustom
|
<CardGridCustom
|
||||||
content={artistData
|
content={artistData.slice(0, props.maxItems ?? artistData.length).map((a) => ({
|
||||||
.slice(0, props?.maxItems ?? artistData.length)
|
image: API.getArtistIllustration(a.id),
|
||||||
.map((a) => ({
|
name: a.name,
|
||||||
image: API.getArtistIllustration(a.id),
|
id: a.id,
|
||||||
name: a.name,
|
onPress: () => {
|
||||||
id: a.id,
|
API.createSearchHistoryEntry(a.name, 'artist');
|
||||||
onPress: () => {
|
navigation.navigate('Artist', { artistId: a.id });
|
||||||
API.createSearchHistoryEntry(a.name, "artist", Date.now());
|
},
|
||||||
navigation.navigate("Artist", { artistId: a.id });
|
}))}
|
||||||
},
|
|
||||||
}))}
|
|
||||||
cardComponent={ArtistCard}
|
cardComponent={ArtistCard}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Text>{translate("errNoResults")}</Text>
|
<Text>{translate('errNoResults')}</Text>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const GenreSearchComponent = (props: any) => {
|
const GenreSearchComponent = (props: ItemSearchComponentProps) => {
|
||||||
const { genreData } = React.useContext(SearchContext);
|
const { genreData } = React.useContext(SearchContext);
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Text fontSize="xl" fontWeight="bold" mt={4}>
|
<Text fontSize="xl" fontWeight="bold" mt={4}>
|
||||||
{translate("genreFilter")}
|
{translate('genreFilter')}
|
||||||
</Text>
|
</Text>
|
||||||
{genreData?.length ? (
|
{genreData?.length ? (
|
||||||
<CardGridCustom
|
<CardGridCustom
|
||||||
content={genreData
|
content={genreData.slice(0, props.maxItems ?? genreData.length).map((g) => ({
|
||||||
.slice(0, props?.maxItems ?? genreData.length)
|
image: API.getGenreIllustration(g.id),
|
||||||
.map((g) => ({
|
name: g.name,
|
||||||
image: API.getGenreIllustration(g.id),
|
id: g.id,
|
||||||
name: g.name,
|
onPress: () => {
|
||||||
id: g.id,
|
API.createSearchHistoryEntry(g.name, 'genre');
|
||||||
onPress: () => {
|
navigation.navigate('Home');
|
||||||
API.createSearchHistoryEntry(g.name, "genre", Date.now());
|
},
|
||||||
navigation.navigate("Home");
|
}))}
|
||||||
},
|
|
||||||
}))}
|
|
||||||
cardComponent={GenreCard}
|
cardComponent={GenreCard}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Text>{translate("errNoResults")}</Text>
|
<Text>{translate('errNoResults')}</Text>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const AllComponent = () => {
|
const AllComponent = () => {
|
||||||
const screenSize = useBreakpointValue({ base: "small", md: "big" });
|
const screenSize = useBreakpointValue({ base: 'small', md: 'big' });
|
||||||
const isMobileView = screenSize == "small";
|
const isMobileView = screenSize == 'small';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView>
|
<SafeAreaView>
|
||||||
<Flex
|
<Flex
|
||||||
flexWrap="wrap"
|
flexWrap="wrap"
|
||||||
direction={isMobileView ? "column" : "row"}
|
direction={isMobileView ? 'column' : 'row'}
|
||||||
justifyContent={["flex-start"]}
|
justifyContent={['flex-start']}
|
||||||
mt={4}
|
mt={4}
|
||||||
>
|
>
|
||||||
<Column w={isMobileView ? "100%" : "50%"}>
|
<Column w={isMobileView ? '100%' : '50%'}>
|
||||||
<Box minH={isMobileView ? 100 : 200}>
|
<Box minH={isMobileView ? 100 : 200}>
|
||||||
<ArtistSearchComponent maxItems={6} />
|
<ArtistSearchComponent maxItems={6} />
|
||||||
</Box>
|
</Box>
|
||||||
@@ -294,7 +299,7 @@ const AllComponent = () => {
|
|||||||
<GenreSearchComponent maxItems={6} />
|
<GenreSearchComponent maxItems={6} />
|
||||||
</Box>
|
</Box>
|
||||||
</Column>
|
</Column>
|
||||||
<Box w={isMobileView ? "100%" : "50%"}>
|
<Box w={isMobileView ? '100%' : '50%'}>
|
||||||
<SongsSearchComponent maxRows={9} />
|
<SongsSearchComponent maxRows={9} />
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -311,22 +316,21 @@ const FilterSwitch = () => {
|
|||||||
}, [filter]);
|
}, [filter]);
|
||||||
|
|
||||||
switch (currentFilter) {
|
switch (currentFilter) {
|
||||||
case "all":
|
case 'all':
|
||||||
return <AllComponent />;
|
return <AllComponent />;
|
||||||
case "song":
|
case 'song':
|
||||||
return <SongsSearchComponent />;
|
return <SongsSearchComponent />;
|
||||||
case "artist":
|
case 'artist':
|
||||||
return <ArtistSearchComponent />;
|
return <ArtistSearchComponent />;
|
||||||
case "genre":
|
case 'genre':
|
||||||
return <GenreSearchComponent />;
|
return <GenreSearchComponent />;
|
||||||
default:
|
default:
|
||||||
return <Text>Something very bad happened: {currentFilter}</Text>;
|
return <Text>Something very bad happened: {currentFilter}</Text>;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SearchResultComponent = (props: any) => {
|
export const SearchResultComponent = () => {
|
||||||
const [searchString, setSearchString] = useState<string>("");
|
const { stringQuery } = React.useContext(SearchContext);
|
||||||
const { stringQuery, updateStringQuery } = React.useContext(SearchContext);
|
|
||||||
const shouldOutput = !!stringQuery.trim();
|
const shouldOutput = !!stringQuery.trim();
|
||||||
|
|
||||||
return shouldOutput ? (
|
return shouldOutput ? (
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import Card, { CardBorderRadius } from "./Card";
|
import Card, { CardBorderRadius } from './Card';
|
||||||
import { VStack, Text, Image } from "native-base";
|
import { VStack, Text, Image } from 'native-base';
|
||||||
import { useNavigation } from "../Navigation";
|
import { useNavigation } from '../Navigation';
|
||||||
import API from "../API";
|
|
||||||
type SongCardProps = {
|
type SongCardProps = {
|
||||||
cover: string;
|
cover: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -14,12 +13,12 @@ const SongCard = (props: SongCardProps) => {
|
|||||||
const { cover, name, artistName, songId } = props;
|
const { cover, name, artistName, songId } = props;
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
return (
|
return (
|
||||||
<Card shadow={3} onPress={() => navigation.navigate("Song", { songId })}>
|
<Card shadow={3} onPress={() => navigation.navigate('Song', { songId })}>
|
||||||
<VStack m={1.5} space={3}>
|
<VStack m={1.5} space={3}>
|
||||||
<Image
|
<Image
|
||||||
style={{ zIndex: 0, aspectRatio: 1, borderRadius: CardBorderRadius }}
|
style={{ zIndex: 0, aspectRatio: 1, borderRadius: CardBorderRadius }}
|
||||||
source={{ uri: cover }}
|
source={{ uri: cover }}
|
||||||
alt={[props.name, props.artistName].join("-")}
|
alt={[props.name, props.artistName].join('-')}
|
||||||
/>
|
/>
|
||||||
<VStack>
|
<VStack>
|
||||||
<Text isTruncated bold fontSize="md" noOfLines={2} height={50}>
|
<Text isTruncated bold fontSize="md" noOfLines={2} height={50}>
|
||||||
|
|||||||
@@ -5,22 +5,24 @@ import { Heading, VStack } from 'native-base';
|
|||||||
|
|
||||||
type SongCardGrid = {
|
type SongCardGrid = {
|
||||||
songs: Parameters<typeof SongCard>[0][];
|
songs: Parameters<typeof SongCard>[0][];
|
||||||
heading?: JSX.Element,
|
heading?: JSX.Element;
|
||||||
maxItemsPerRow?: number,
|
maxItemsPerRow?: number;
|
||||||
style?: Parameters<typeof FlatGrid>[0]['additionalRowStyle']
|
style?: Parameters<typeof FlatGrid>[0]['additionalRowStyle'];
|
||||||
}
|
};
|
||||||
|
|
||||||
const SongCardGrid = (props: SongCardGrid) => {
|
const SongCardGrid = (props: SongCardGrid) => {
|
||||||
return <VStack space={5}>
|
return (
|
||||||
<Heading>{props.heading}</Heading>
|
<VStack space={5}>
|
||||||
<FlatGrid
|
<Heading>{props.heading}</Heading>
|
||||||
maxItemsPerRow={props.maxItemsPerRow}
|
<FlatGrid
|
||||||
additionalRowStyle={props.style ?? { justifyContent: 'flex-start' }}
|
maxItemsPerRow={props.maxItemsPerRow}
|
||||||
data={props.songs}
|
additionalRowStyle={props.style ?? { justifyContent: 'flex-start' }}
|
||||||
renderItem={({ item }) => <SongCard {...item} /> }
|
data={props.songs}
|
||||||
spacing={10}
|
renderItem={({ item }) => <SongCard {...item} />}
|
||||||
/>
|
spacing={10}
|
||||||
</VStack>
|
/>
|
||||||
}
|
</VStack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default SongCardGrid;
|
export default SongCardGrid;
|
||||||
|
|||||||
@@ -1,24 +1,26 @@
|
|||||||
import { Button, Text } from "native-base"
|
import { Button, Text } from 'native-base';
|
||||||
import Translate from "./Translate";
|
import Translate from './Translate';
|
||||||
import { RequireExactlyOne } from 'type-fest';
|
import { RequireExactlyOne } from 'type-fest';
|
||||||
|
|
||||||
type TextButtonProps = Parameters<typeof Button>[0] & RequireExactlyOne<{
|
type TextButtonProps = Parameters<typeof Button>[0] &
|
||||||
label: string;
|
RequireExactlyOne<{
|
||||||
translate: Parameters<typeof Translate>[0];
|
label: string;
|
||||||
}>
|
translate: Parameters<typeof Translate>[0];
|
||||||
|
}>;
|
||||||
|
|
||||||
const TextButton = (props: TextButtonProps) => {
|
const TextButton = (props: TextButtonProps) => {
|
||||||
// accepts undefined variant, as it is the default variant
|
// accepts undefined variant, as it is the default variant
|
||||||
const textColor = !props.variant || props.variant == 'solid'
|
const textColor = !props.variant || props.variant == 'solid' ? 'light.50' : undefined;
|
||||||
? 'light.50'
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return <Button {...props}>
|
return (
|
||||||
{ props.label !== undefined
|
<Button {...props}>
|
||||||
? <Text color={textColor}>{props.label}</Text>
|
{props.label !== undefined ? (
|
||||||
: <Translate color={textColor} {...props.translate} />
|
<Text color={textColor}>{props.label}</Text>
|
||||||
}
|
) : (
|
||||||
</Button>
|
<Translate color={textColor} {...props.translate} />
|
||||||
}
|
)}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default TextButton
|
export default TextButton;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Text } from "native-base";
|
import { Text } from 'native-base';
|
||||||
import { translate } from "../i18n/i18n";
|
import { translate } from '../i18n/i18n';
|
||||||
import { en } from "../i18n/Translations";
|
import { en } from '../i18n/Translations';
|
||||||
import { RootState, useSelector } from "../state/Store";
|
import { RootState, useSelector } from '../state/Store';
|
||||||
|
|
||||||
type TranslateProps = {
|
type TranslateProps = {
|
||||||
translationKey: keyof typeof en;
|
translationKey: keyof typeof en;
|
||||||
@@ -9,14 +9,14 @@ type TranslateProps = {
|
|||||||
} & Parameters<typeof Text>[0];
|
} & Parameters<typeof Text>[0];
|
||||||
/**
|
/**
|
||||||
* Translation component
|
* Translation component
|
||||||
* @param param0
|
* @param param0
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const Translate = ({ translationKey, format, ...props }: TranslateProps) => {
|
const Translate = ({ translationKey, format, ...props }: TranslateProps) => {
|
||||||
const selectedLanguage = useSelector((state: RootState) => state.language.value);
|
const selectedLanguage = useSelector((state: RootState) => state.language.value);
|
||||||
const translated = translate(translationKey, selectedLanguage);
|
const translated = translate(translationKey, selectedLanguage);
|
||||||
|
|
||||||
return <Text {...props}>{format ? format(translated) : translated}</Text>;
|
return <Text {...props}>{format ? format(translated) : translated}</Text>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Translate;
|
export default Translate;
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import {
|
|||||||
octaveKeys,
|
octaveKeys,
|
||||||
isAccidental,
|
isAccidental,
|
||||||
HighlightedKey,
|
HighlightedKey,
|
||||||
} from "../../models/Piano";
|
} from '../../models/Piano';
|
||||||
import { Box, Row, Text } from "native-base";
|
import { Box, Row, Text } from 'native-base';
|
||||||
import PianoKeyComp from "./PianoKeyComp";
|
import PianoKeyComp from './PianoKeyComp';
|
||||||
|
|
||||||
type OctaveProps = Parameters<typeof Box>[0] & {
|
type OctaveProps = Parameters<typeof Box>[0] & {
|
||||||
number: number;
|
number: number;
|
||||||
@@ -57,22 +57,20 @@ const Octave = (props: OctaveProps) => {
|
|||||||
const whiteKeys = keys.filter((k) => !isAccidental(k));
|
const whiteKeys = keys.filter((k) => !isAccidental(k));
|
||||||
const blackKeys = keys.filter(isAccidental);
|
const blackKeys = keys.filter(isAccidental);
|
||||||
|
|
||||||
const whiteKeyWidthExpr = "calc(100% / 7)";
|
const whiteKeyWidthExpr = 'calc(100% / 7)';
|
||||||
const whiteKeyHeightExpr = "100%";
|
const whiteKeyHeightExpr = '100%';
|
||||||
const blackKeyWidthExpr = "calc(100% / 13)";
|
const blackKeyWidthExpr = 'calc(100% / 13)';
|
||||||
const blackKeyHeightExpr = "calc(100% / 1.5)";
|
const blackKeyHeightExpr = 'calc(100% / 1.5)';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box {...props}>
|
<Box {...props}>
|
||||||
<Row height={"100%"} width={"100%"}>
|
<Row height={'100%'} width={'100%'}>
|
||||||
{whiteKeys.map((key, i) => {
|
{whiteKeys.map((key) => {
|
||||||
const highlightedKey = highlightedNotes.find(
|
const highlightedKey = highlightedNotes.find((h) => h.key.note === key.note);
|
||||||
(h) => h.key.note === key.note
|
|
||||||
);
|
|
||||||
const isHighlighted = highlightedKey !== undefined;
|
const isHighlighted = highlightedKey !== undefined;
|
||||||
const highlightColor =
|
const highlightColor = highlightedKey?.bgColor ?? defaultHighlightColor;
|
||||||
highlightedKey?.bgColor ?? defaultHighlightColor;
|
|
||||||
return (
|
return (
|
||||||
|
// eslint-disable-next-line react/jsx-key
|
||||||
<PianoKeyComp
|
<PianoKeyComp
|
||||||
pianoKey={key}
|
pianoKey={key}
|
||||||
showNoteName={showNoteNames}
|
showNoteName={showNoteNames}
|
||||||
@@ -89,13 +87,11 @@ const Octave = (props: OctaveProps) => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{blackKeys.map((key, i) => {
|
{blackKeys.map((key, i) => {
|
||||||
const highlightedKey = highlightedNotes.find(
|
const highlightedKey = highlightedNotes.find((h) => h.key.note === key.note);
|
||||||
(h) => h.key.note === key.note
|
|
||||||
);
|
|
||||||
const isHighlighted = highlightedKey !== undefined;
|
const isHighlighted = highlightedKey !== undefined;
|
||||||
const highlightColor =
|
const highlightColor = highlightedKey?.bgColor ?? defaultHighlightColor;
|
||||||
highlightedKey?.bgColor ?? defaultHighlightColor;
|
|
||||||
return (
|
return (
|
||||||
|
// eslint-disable-next-line react/jsx-key
|
||||||
<PianoKeyComp
|
<PianoKeyComp
|
||||||
pianoKey={key}
|
pianoKey={key}
|
||||||
showNoteName={showNoteNames}
|
showNoteName={showNoteNames}
|
||||||
@@ -107,15 +103,15 @@ const Octave = (props: OctaveProps) => {
|
|||||||
style={{
|
style={{
|
||||||
width: blackKeyWidthExpr,
|
width: blackKeyWidthExpr,
|
||||||
height: blackKeyHeightExpr,
|
height: blackKeyHeightExpr,
|
||||||
position: "absolute",
|
position: 'absolute',
|
||||||
left: `calc(calc(${whiteKeyWidthExpr} * ${
|
left: `calc(calc(${whiteKeyWidthExpr} * ${
|
||||||
i + ((i > 1) as unknown as number) + 1
|
i + ((i > 1) as unknown as number) + 1
|
||||||
}) - calc(${blackKeyWidthExpr} / 2))`,
|
}) - calc(${blackKeyWidthExpr} / 2))`,
|
||||||
top: "0px",
|
top: '0px',
|
||||||
}}
|
}}
|
||||||
text={{
|
text={{
|
||||||
color: "white",
|
color: 'white',
|
||||||
fontSize: "xs",
|
fontSize: 'xs',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -139,18 +135,18 @@ const Octave = (props: OctaveProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Octave.defaultProps = {
|
Octave.defaultProps = {
|
||||||
startNote: "C",
|
startNote: 'C',
|
||||||
endNote: "B",
|
endNote: 'B',
|
||||||
showNoteNames: "onpress",
|
showNoteNames: 'onpress',
|
||||||
showOctaveNumber: false,
|
showOctaveNumber: false,
|
||||||
whiteKeyBg: "white",
|
whiteKeyBg: 'white',
|
||||||
whiteKeyBgPressed: "gray.200",
|
whiteKeyBgPressed: 'gray.200',
|
||||||
whiteKeyBgHovered: "gray.100",
|
whiteKeyBgHovered: 'gray.100',
|
||||||
blackKeyBg: "black",
|
blackKeyBg: 'black',
|
||||||
blackKeyBgPressed: "gray.600",
|
blackKeyBgPressed: 'gray.600',
|
||||||
blackKeyBgHovered: "gray.700",
|
blackKeyBgHovered: 'gray.700',
|
||||||
highlightedNotes: [],
|
highlightedNotes: [],
|
||||||
defaultHighlightColor: "#FF0000",
|
defaultHighlightColor: '#FF0000',
|
||||||
onNoteDown: () => {},
|
onNoteDown: () => {},
|
||||||
onNoteUp: () => {},
|
onNoteUp: () => {},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
import { Box, Pressable, Text } from "native-base";
|
import { Box, Pressable, Text } from 'native-base';
|
||||||
import { StyleProp, ViewStyle } from "react-native";
|
import { StyleProp, ViewStyle } from 'react-native';
|
||||||
import {
|
import { PianoKey, NoteNameBehavior, octaveKeys, keyToStr } from '../../models/Piano';
|
||||||
PianoKey,
|
|
||||||
NoteNameBehavior,
|
|
||||||
octaveKeys,
|
|
||||||
keyToStr,
|
|
||||||
} from "../../models/Piano";
|
|
||||||
|
|
||||||
type PianoKeyProps = {
|
type PianoKeyProps = {
|
||||||
pianoKey: PianoKey;
|
pianoKey: PianoKey;
|
||||||
@@ -48,22 +43,18 @@ const PianoKeyComp = ({
|
|||||||
}: PianoKeyProps) => {
|
}: PianoKeyProps) => {
|
||||||
const textDefaultProps = {
|
const textDefaultProps = {
|
||||||
style: {
|
style: {
|
||||||
userSelect: "none",
|
userSelect: 'none',
|
||||||
WebkitUserSelect: "none",
|
WebkitUserSelect: 'none',
|
||||||
MozUserSelect: "none",
|
MozUserSelect: 'none',
|
||||||
msUserSelect: "none",
|
msUserSelect: 'none',
|
||||||
},
|
},
|
||||||
fontSize: "xl",
|
fontSize: 'xl',
|
||||||
color: "black",
|
color: 'black',
|
||||||
} as Parameters<typeof Text>[0];
|
} as Parameters<typeof Text>[0];
|
||||||
|
|
||||||
const textProps = { ...textDefaultProps, ...text };
|
const textProps = { ...textDefaultProps, ...text };
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable onPressIn={onKeyDown} onPressOut={onKeyUp} style={style}>
|
||||||
onPressIn={onKeyDown}
|
|
||||||
onPressOut={onKeyUp}
|
|
||||||
style={style}
|
|
||||||
>
|
|
||||||
{({ isHovered, isPressed }) => (
|
{({ isHovered, isPressed }) => (
|
||||||
<Box
|
<Box
|
||||||
bg={(() => {
|
bg={(() => {
|
||||||
@@ -90,9 +81,9 @@ const PianoKeyComp = ({
|
|||||||
PianoKeyComp.defaultProps = {
|
PianoKeyComp.defaultProps = {
|
||||||
key: octaveKeys[0],
|
key: octaveKeys[0],
|
||||||
showNoteNames: NoteNameBehavior.onhover,
|
showNoteNames: NoteNameBehavior.onhover,
|
||||||
keyBg: "white",
|
keyBg: 'white',
|
||||||
keyBgPressed: "gray.200",
|
keyBgPressed: 'gray.200',
|
||||||
keyBgHovered: "gray.100",
|
keyBgHovered: 'gray.100',
|
||||||
onKeyDown: () => {},
|
onKeyDown: () => {},
|
||||||
onKeyUp: () => {},
|
onKeyUp: () => {},
|
||||||
text: {},
|
text: {},
|
||||||
|
|||||||
@@ -1,16 +1,8 @@
|
|||||||
import { Row, Box } from "native-base";
|
import { Row } from 'native-base';
|
||||||
import React, { useState, useEffect } from "react";
|
import React from 'react';
|
||||||
import Octave from "./Octave";
|
import Octave from './Octave';
|
||||||
import { StyleProp, ViewStyle } from "react-native";
|
import { StyleProp, ViewStyle } from 'react-native';
|
||||||
import {
|
import { Note, PianoKey, NoteNameBehavior, HighlightedKey } from '../../models/Piano';
|
||||||
Note,
|
|
||||||
PianoKey,
|
|
||||||
NoteNameBehavior,
|
|
||||||
KeyPressStyle,
|
|
||||||
keyToStr,
|
|
||||||
strToKey,
|
|
||||||
HighlightedKey,
|
|
||||||
} from "../../models/Piano";
|
|
||||||
|
|
||||||
type VirtualPianoProps = Parameters<typeof Row>[0] & {
|
type VirtualPianoProps = Parameters<typeof Row>[0] & {
|
||||||
onNoteDown: (note: PianoKey) => void;
|
onNoteDown: (note: PianoKey) => void;
|
||||||
@@ -37,29 +29,21 @@ const VirtualPiano = ({
|
|||||||
showOctaveNumbers,
|
showOctaveNumbers,
|
||||||
style,
|
style,
|
||||||
}: VirtualPianoProps) => {
|
}: VirtualPianoProps) => {
|
||||||
const notesList: Array<Note> = [
|
const notesList: Array<Note> = [Note.C, Note.D, Note.E, Note.F, Note.G, Note.A, Note.B];
|
||||||
Note.C,
|
|
||||||
Note.D,
|
|
||||||
Note.E,
|
|
||||||
Note.F,
|
|
||||||
Note.G,
|
|
||||||
Note.A,
|
|
||||||
Note.B,
|
|
||||||
];
|
|
||||||
const octaveList = [];
|
const octaveList = [];
|
||||||
|
|
||||||
for (let octaveNum = startOctave; octaveNum <= endOctave; octaveNum++) {
|
for (let octaveNum = startOctave; octaveNum <= endOctave; octaveNum++) {
|
||||||
octaveList.push(octaveNum);
|
octaveList.push(octaveNum);
|
||||||
}
|
}
|
||||||
|
|
||||||
const octaveWidthExpr = `calc(100% / ${octaveList.length})`;
|
const octaveWidthExpr = `calc(100% / ${octaveList.length})`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row style={style}>
|
<Row style={style}>
|
||||||
{octaveList.map((octaveNum) => {
|
{octaveList.map((octaveNum) => {
|
||||||
return (
|
return (
|
||||||
<Octave
|
<Octave
|
||||||
style={{ width: octaveWidthExpr, height: "100%" }}
|
style={{ width: octaveWidthExpr, height: '100%' }}
|
||||||
key={octaveNum}
|
key={octaveNum}
|
||||||
number={octaveNum}
|
number={octaveNum}
|
||||||
showNoteNames={showNoteNames}
|
showNoteNames={showNoteNames}
|
||||||
@@ -68,9 +52,7 @@ const VirtualPiano = ({
|
|||||||
n.key.octave ? n.key.octave == octaveNum : true
|
n.key.octave ? n.key.octave == octaveNum : true
|
||||||
)}
|
)}
|
||||||
startNote={octaveNum == startOctave ? startNote : notesList[0]}
|
startNote={octaveNum == startOctave ? startNote : notesList[0]}
|
||||||
endNote={
|
endNote={octaveNum == endOctave ? endNote : notesList[notesList.length - 1]}
|
||||||
octaveNum == endOctave ? endNote : notesList[notesList.length - 1]
|
|
||||||
}
|
|
||||||
onNoteDown={onNoteDown}
|
onNoteDown={onNoteDown}
|
||||||
onNoteUp={onNoteUp}
|
onNoteUp={onNoteUp}
|
||||||
/>
|
/>
|
||||||
@@ -81,8 +63,8 @@ const VirtualPiano = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
VirtualPiano.defaultProps = {
|
VirtualPiano.defaultProps = {
|
||||||
onNoteDown: (_n: PianoKey) => {},
|
onNoteDown: () => {},
|
||||||
onNoteUp: (_n: PianoKey) => {},
|
onNoteUp: () => {},
|
||||||
startOctave: 2,
|
startOctave: 2,
|
||||||
startNote: Note.C,
|
startNote: Note.C,
|
||||||
endOctave: 6,
|
endOctave: 6,
|
||||||
@@ -90,7 +72,7 @@ VirtualPiano.defaultProps = {
|
|||||||
showNoteNames: NoteNameBehavior.onpress,
|
showNoteNames: NoteNameBehavior.onpress,
|
||||||
highlightedNotes: [],
|
highlightedNotes: [],
|
||||||
showOctaveNumbers: true,
|
showOctaveNumbers: true,
|
||||||
style: {},
|
style: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default VirtualPiano;
|
export default VirtualPiano;
|
||||||
|
|||||||
@@ -1,37 +1,26 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import { translate } from "../../i18n/i18n";
|
import { translate } from '../../i18n/i18n';
|
||||||
import { string } from "yup";
|
import { string } from 'yup';
|
||||||
import {
|
import { FormControl, Input, Stack, WarningOutlineIcon, Box, Button, useToast } from 'native-base';
|
||||||
FormControl,
|
|
||||||
Input,
|
|
||||||
Stack,
|
|
||||||
WarningOutlineIcon,
|
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
useToast,
|
|
||||||
} from "native-base";
|
|
||||||
|
|
||||||
interface ChangeEmailFormProps {
|
interface ChangeEmailFormProps {
|
||||||
onSubmit: (
|
onSubmit: (oldEmail: string, newEmail: string) => Promise<string>;
|
||||||
oldEmail: string,
|
|
||||||
newEmail: string
|
|
||||||
) => Promise<string>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const validationSchemas = {
|
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 ChangeEmailForm = ({ onSubmit }: ChangeEmailFormProps) => {
|
||||||
const [formData, setFormData] = React.useState({
|
const [formData, setFormData] = React.useState({
|
||||||
oldEmail: {
|
oldEmail: {
|
||||||
value: "",
|
value: '',
|
||||||
error: null as string | null,
|
error: null as string | null,
|
||||||
},
|
},
|
||||||
newEmail: {
|
newEmail: {
|
||||||
value: "",
|
value: '',
|
||||||
error: null as string | null,
|
error: null as string | null,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const [submittingForm, setSubmittingForm] = React.useState(false);
|
const [submittingForm, setSubmittingForm] = React.useState(false);
|
||||||
@@ -42,77 +31,73 @@ const ChangeEmailForm = ({ onSubmit }: ChangeEmailFormProps) => {
|
|||||||
<Stack mx="4" style={{ width: '80%', maxWidth: 400 }}>
|
<Stack mx="4" style={{ width: '80%', maxWidth: 400 }}>
|
||||||
<FormControl
|
<FormControl
|
||||||
isRequired
|
isRequired
|
||||||
isInvalid={
|
isInvalid={formData.oldEmail.error !== null || formData.newEmail.error !== null}
|
||||||
formData.oldEmail.error !== null ||
|
|
||||||
formData.newEmail.error !== null
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<FormControl.Label>{translate("oldEmail")}</FormControl.Label>
|
<FormControl.Label>{translate('oldEmail')}</FormControl.Label>
|
||||||
<Input
|
<Input
|
||||||
isRequired
|
isRequired
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={translate("oldEmail")}
|
placeholder={translate('oldEmail')}
|
||||||
value={formData.oldEmail.value}
|
value={formData.oldEmail.value}
|
||||||
onChangeText={(t) => {
|
onChangeText={(t) => {
|
||||||
let error: null | string = null;
|
let error: null | string = null;
|
||||||
validationSchemas.email
|
validationSchemas.email
|
||||||
.validate(t)
|
.validate(t)
|
||||||
.catch((e) => (error = e.message))
|
.catch((e) => (error = e.message))
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setFormData({ ...formData, oldEmail: { value: t, error } });
|
setFormData({ ...formData, oldEmail: { value: t, error } });
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />} >
|
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />}>
|
||||||
{formData.oldEmail.error}
|
{formData.oldEmail.error}
|
||||||
</FormControl.ErrorMessage>
|
</FormControl.ErrorMessage>
|
||||||
|
|
||||||
<FormControl.Label>{translate("newEmail")}</FormControl.Label>
|
<FormControl.Label>{translate('newEmail')}</FormControl.Label>
|
||||||
<Input
|
<Input
|
||||||
isRequired
|
isRequired
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={translate("newEmail")}
|
placeholder={translate('newEmail')}
|
||||||
value={formData.newEmail.value}
|
value={formData.newEmail.value}
|
||||||
onChangeText={(t) => {
|
onChangeText={(t) => {
|
||||||
let error: null | string = null;
|
let error: null | string = null;
|
||||||
validationSchemas.email
|
validationSchemas.email
|
||||||
.validate(t)
|
.validate(t)
|
||||||
.catch((e) => (error = e.message))
|
.catch((e) => (error = e.message))
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setFormData({ ...formData, newEmail: { value: t, error } });
|
setFormData({ ...formData, newEmail: { value: t, error } });
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />} >
|
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />}>
|
||||||
{formData.oldEmail.error}
|
{formData.oldEmail.error}
|
||||||
</FormControl.ErrorMessage>
|
</FormControl.ErrorMessage>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
style={{ marginTop: 10 }}
|
style={{ marginTop: 10 }}
|
||||||
isLoading={submittingForm}
|
isLoading={submittingForm}
|
||||||
isDisabled={
|
isDisabled={formData.newEmail.error !== null}
|
||||||
formData.newEmail.error !== null
|
onPress={async () => {
|
||||||
}
|
setSubmittingForm(true);
|
||||||
onPress={async () => {
|
try {
|
||||||
setSubmittingForm(true);
|
const resp = await onSubmit(
|
||||||
try {
|
formData.oldEmail.value,
|
||||||
const resp = await onSubmit(formData.oldEmail.value,
|
formData.newEmail.value
|
||||||
formData.newEmail.value
|
);
|
||||||
);
|
toast.show({ description: resp });
|
||||||
toast.show({ description: resp });
|
} catch (e) {
|
||||||
} catch (e) {
|
toast.show({ description: e as string });
|
||||||
toast.show({ description: e as string });
|
} finally {
|
||||||
} finally {
|
setSubmittingForm(false);
|
||||||
setSubmittingForm(false);
|
}
|
||||||
}
|
}}
|
||||||
}}
|
>
|
||||||
>
|
{translate('submitBtn')}
|
||||||
{translate("submitBtn")}
|
</Button>
|
||||||
</Button>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default ChangeEmailForm;
|
export default ChangeEmailForm;
|
||||||
|
|||||||
@@ -1,36 +1,24 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import { translate } from "../../i18n/i18n";
|
import { translate } from '../../i18n/i18n';
|
||||||
import { string } from "yup";
|
import { string } from 'yup';
|
||||||
import {
|
import { FormControl, Input, Stack, WarningOutlineIcon, Box, Button, useToast } from 'native-base';
|
||||||
FormControl,
|
|
||||||
Input,
|
|
||||||
Stack,
|
|
||||||
WarningOutlineIcon,
|
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
useToast,
|
|
||||||
} from "native-base";
|
|
||||||
|
|
||||||
|
|
||||||
interface ChangePasswordFormProps {
|
interface ChangePasswordFormProps {
|
||||||
onSubmit: (
|
onSubmit: (oldPassword: string, newPassword: string) => Promise<string>;
|
||||||
oldPassword: string,
|
|
||||||
newPassword: string
|
|
||||||
) => Promise<string>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChangePasswordForm = ({ onSubmit }: ChangePasswordFormProps) => {
|
const ChangePasswordForm = ({ onSubmit }: ChangePasswordFormProps) => {
|
||||||
const [formData, setFormData] = React.useState({
|
const [formData, setFormData] = React.useState({
|
||||||
oldPassword: {
|
oldPassword: {
|
||||||
value: "",
|
value: '',
|
||||||
error: null as string | null,
|
error: null as string | null,
|
||||||
},
|
},
|
||||||
newPassword: {
|
newPassword: {
|
||||||
value: "",
|
value: '',
|
||||||
error: null as string | null,
|
error: null as string | null,
|
||||||
},
|
},
|
||||||
confirmNewPassword: {
|
confirmNewPassword: {
|
||||||
value: "",
|
value: '',
|
||||||
error: null as string | null,
|
error: null as string | null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -38,120 +26,119 @@ const ChangePasswordForm = ({ onSubmit }: ChangePasswordFormProps) => {
|
|||||||
|
|
||||||
const validationSchemas = {
|
const validationSchemas = {
|
||||||
password: string()
|
password: string()
|
||||||
.min(4, translate("passwordTooShort"))
|
.min(4, translate('passwordTooShort'))
|
||||||
.max(100, translate("passwordTooLong"))
|
.max(100, translate('passwordTooLong'))
|
||||||
.required("Password is required"),
|
.required('Password is required'),
|
||||||
};
|
};
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Stack mx="4" style={{ width: '80%', maxWidth: 400 }}>
|
<Stack mx="4" style={{ width: '80%', maxWidth: 400 }}>
|
||||||
<FormControl
|
<FormControl
|
||||||
isRequired
|
isRequired
|
||||||
isInvalid={
|
isInvalid={
|
||||||
formData.oldPassword.error !== null ||
|
formData.oldPassword.error !== null ||
|
||||||
formData.newPassword.error !== null ||
|
formData.newPassword.error !== null ||
|
||||||
formData.confirmNewPassword.error !== null}
|
formData.confirmNewPassword.error !== null
|
||||||
|
}
|
||||||
>
|
>
|
||||||
|
<FormControl.Label>{translate('oldPassword')}</FormControl.Label>
|
||||||
|
<Input
|
||||||
|
isRequired
|
||||||
|
type="password"
|
||||||
|
placeholder={translate('oldPassword')}
|
||||||
|
value={formData.oldPassword.value}
|
||||||
|
onChangeText={(t) => {
|
||||||
|
let error: null | string = null;
|
||||||
|
validationSchemas.password
|
||||||
|
.validate(t)
|
||||||
|
.catch((e) => (error = e.message))
|
||||||
|
.finally(() => {
|
||||||
|
setFormData({ ...formData, oldPassword: { value: t, error } });
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />}>
|
||||||
|
{formData.oldPassword.error}
|
||||||
|
</FormControl.ErrorMessage>
|
||||||
|
|
||||||
<FormControl.Label>{translate("oldPassword")}</FormControl.Label>
|
<FormControl.Label>{translate('newPassword')}</FormControl.Label>
|
||||||
<Input
|
<Input
|
||||||
isRequired
|
isRequired
|
||||||
type="password"
|
type="password"
|
||||||
placeholder={translate("oldPassword")}
|
placeholder={translate('newPassword')}
|
||||||
value={formData.oldPassword.value}
|
value={formData.newPassword.value}
|
||||||
onChangeText={(t) => {
|
onChangeText={(t) => {
|
||||||
let error: null | string = null;
|
let error: null | string = null;
|
||||||
validationSchemas.password
|
validationSchemas.password
|
||||||
.validate(t)
|
.validate(t)
|
||||||
.catch((e) => (error = e.message))
|
.catch((e) => (error = e.message))
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setFormData({ ...formData, oldPassword: { value: t, error } });
|
setFormData({ ...formData, newPassword: { value: t, error } });
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />} >
|
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />}>
|
||||||
{formData.oldPassword.error}
|
{formData.newPassword.error}
|
||||||
</FormControl.ErrorMessage>
|
</FormControl.ErrorMessage>
|
||||||
|
|
||||||
<FormControl.Label>{translate("newPassword")}</FormControl.Label>
|
<FormControl.Label>{translate('confirmNewPassword')}</FormControl.Label>
|
||||||
<Input
|
<Input
|
||||||
isRequired
|
isRequired
|
||||||
type="password"
|
type="password"
|
||||||
placeholder={translate("newPassword")}
|
placeholder={translate('confirmNewPassword')}
|
||||||
value={formData.newPassword.value}
|
value={formData.confirmNewPassword.value}
|
||||||
onChangeText={(t) => {
|
onChangeText={(t) => {
|
||||||
let error: null | string = null;
|
let error: null | string = null;
|
||||||
validationSchemas.password
|
validationSchemas.password
|
||||||
.validate(t)
|
.validate(t)
|
||||||
.catch((e) => (error = e.message))
|
.catch((e) => (error = e.message));
|
||||||
.finally(() => {
|
|
||||||
setFormData({ ...formData, newPassword: { value: t, error } });
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />} >
|
|
||||||
{formData.newPassword.error}
|
|
||||||
</FormControl.ErrorMessage>
|
|
||||||
|
|
||||||
<FormControl.Label>{translate("confirmNewPassword")}</FormControl.Label>
|
|
||||||
<Input
|
|
||||||
isRequired
|
|
||||||
type="password"
|
|
||||||
placeholder={translate("confirmNewPassword")}
|
|
||||||
value={formData.confirmNewPassword.value}
|
|
||||||
onChangeText={(t) => {
|
|
||||||
let error: null | string = null;
|
|
||||||
validationSchemas.password
|
|
||||||
.validate(t)
|
|
||||||
.catch((e) => (error = e.message))
|
|
||||||
if (!error && t !== formData.newPassword.value) {
|
if (!error && t !== formData.newPassword.value) {
|
||||||
error = translate("passwordsDontMatch");
|
error = translate('passwordsDontMatch');
|
||||||
}
|
}
|
||||||
setFormData({
|
setFormData({
|
||||||
...formData,
|
...formData,
|
||||||
confirmNewPassword: { value: t, error },
|
confirmNewPassword: { value: t, error },
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />} >
|
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />}>
|
||||||
{formData.confirmNewPassword.error}
|
{formData.confirmNewPassword.error}
|
||||||
</FormControl.ErrorMessage>
|
</FormControl.ErrorMessage>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
style={{ marginTop: 10 }}
|
style={{ marginTop: 10 }}
|
||||||
isLoading={submittingForm}
|
isLoading={submittingForm}
|
||||||
isDisabled={
|
isDisabled={
|
||||||
formData.oldPassword.error !== null ||
|
formData.oldPassword.error !== null ||
|
||||||
formData.newPassword.error !== null ||
|
formData.newPassword.error !== null ||
|
||||||
formData.confirmNewPassword.error !== null ||
|
formData.confirmNewPassword.error !== null ||
|
||||||
formData.oldPassword.value === "" ||
|
formData.oldPassword.value === '' ||
|
||||||
formData.newPassword.value === "" ||
|
formData.newPassword.value === '' ||
|
||||||
formData.confirmNewPassword.value === ""
|
formData.confirmNewPassword.value === ''
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}}
|
onPress={async () => {
|
||||||
>
|
setSubmittingForm(true);
|
||||||
{translate("submitBtn")}
|
try {
|
||||||
</Button>
|
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')}
|
||||||
|
</Button>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default ChangePasswordForm;
|
export default ChangePasswordForm;
|
||||||
|
|||||||
@@ -1,17 +1,10 @@
|
|||||||
// a form for sign in
|
// a form for sign in
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { Translate, translate } from "../../i18n/i18n";
|
import { Translate, translate } from '../../i18n/i18n';
|
||||||
import { string } from "yup";
|
import { string } from 'yup';
|
||||||
import {
|
import { FormControl, Input, Stack, WarningOutlineIcon, Box, useToast } from 'native-base';
|
||||||
FormControl,
|
import TextButton from '../TextButton';
|
||||||
Input,
|
|
||||||
Stack,
|
|
||||||
WarningOutlineIcon,
|
|
||||||
Box,
|
|
||||||
useToast,
|
|
||||||
} from "native-base";
|
|
||||||
import TextButton from "../TextButton";
|
|
||||||
|
|
||||||
interface SigninFormProps {
|
interface SigninFormProps {
|
||||||
onSubmit: (username: string, password: string) => Promise<string>;
|
onSubmit: (username: string, password: string) => Promise<string>;
|
||||||
@@ -20,11 +13,11 @@ interface SigninFormProps {
|
|||||||
const LoginForm = ({ onSubmit }: SigninFormProps) => {
|
const LoginForm = ({ onSubmit }: SigninFormProps) => {
|
||||||
const [formData, setFormData] = React.useState({
|
const [formData, setFormData] = React.useState({
|
||||||
username: {
|
username: {
|
||||||
value: "",
|
value: '',
|
||||||
error: null as string | null,
|
error: null as string | null,
|
||||||
},
|
},
|
||||||
password: {
|
password: {
|
||||||
value: "",
|
value: '',
|
||||||
error: null as string | null,
|
error: null as string | null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -32,98 +25,96 @@ const LoginForm = ({ onSubmit }: SigninFormProps) => {
|
|||||||
|
|
||||||
const validationSchemas = {
|
const validationSchemas = {
|
||||||
username: string()
|
username: string()
|
||||||
.min(3, translate("usernameTooShort"))
|
.min(3, translate('usernameTooShort'))
|
||||||
.max(20, translate("usernameTooLong"))
|
.max(20, translate('usernameTooLong'))
|
||||||
.required("Username is required"),
|
.required('Username is required'),
|
||||||
password: string()
|
password: string()
|
||||||
.min(4, translate("passwordTooShort"))
|
.min(4, translate('passwordTooShort'))
|
||||||
.max(100, translate("passwordTooLong"))
|
.max(100, translate('passwordTooLong'))
|
||||||
.required("Password is required"),
|
.required('Password is required'),
|
||||||
};
|
};
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
return (
|
return (
|
||||||
<Box alignItems="center" style={{ width: '100%' }}>
|
<Box alignItems="center" style={{ width: '100%' }}>
|
||||||
<Stack mx="4" style={{ width: '80%', maxWidth: 400 }}>
|
<Stack mx="4" style={{ width: '80%', maxWidth: 400 }}>
|
||||||
<FormControl
|
<FormControl
|
||||||
|
isRequired
|
||||||
|
isInvalid={formData.username.error !== null || formData.password.error !== null}
|
||||||
|
>
|
||||||
|
<FormControl.Label>
|
||||||
|
<Translate translationKey="username" />
|
||||||
|
</FormControl.Label>
|
||||||
|
<Input
|
||||||
isRequired
|
isRequired
|
||||||
isInvalid={
|
type="text"
|
||||||
|
placeholder="Username"
|
||||||
|
autoComplete="username"
|
||||||
|
value={formData.username.value}
|
||||||
|
onChangeText={(t) => {
|
||||||
|
let error: null | string = null;
|
||||||
|
validationSchemas.username
|
||||||
|
.validate(t)
|
||||||
|
.catch((e) => (error = e.message))
|
||||||
|
.finally(() => {
|
||||||
|
setFormData({ ...formData, username: { value: t, error } });
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />}>
|
||||||
|
{formData.username.error}
|
||||||
|
</FormControl.ErrorMessage>
|
||||||
|
<FormControl.Label>
|
||||||
|
<Translate translationKey="password" />
|
||||||
|
</FormControl.Label>
|
||||||
|
<Input
|
||||||
|
isRequired
|
||||||
|
type="password"
|
||||||
|
autoComplete="password"
|
||||||
|
value={formData.password.value}
|
||||||
|
onChangeText={(t) => {
|
||||||
|
let error: null | string = null;
|
||||||
|
validationSchemas.password
|
||||||
|
.validate(t)
|
||||||
|
.catch((e) => (error = e.message))
|
||||||
|
.finally(() => {
|
||||||
|
setFormData({ ...formData, password: { value: t, error } });
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />}>
|
||||||
|
{formData.password.error}
|
||||||
|
</FormControl.ErrorMessage>
|
||||||
|
<TextButton
|
||||||
|
translate={{ translationKey: 'signInBtn' }}
|
||||||
|
style={{ marginTop: 10 }}
|
||||||
|
isLoading={submittingForm}
|
||||||
|
isDisabled={
|
||||||
|
formData.password.error !== null ||
|
||||||
formData.username.error !== null ||
|
formData.username.error !== null ||
|
||||||
formData.password.error !== null
|
formData.username.value === '' ||
|
||||||
|
formData.password.value === ''
|
||||||
}
|
}
|
||||||
>
|
onPress={async () => {
|
||||||
<FormControl.Label>
|
setSubmittingForm(true);
|
||||||
<Translate translationKey='username'/>
|
try {
|
||||||
</FormControl.Label>
|
const resp = await onSubmit(
|
||||||
<Input
|
formData.username.value,
|
||||||
isRequired
|
formData.password.value
|
||||||
type="text"
|
);
|
||||||
placeholder="Username"
|
toast.show({ description: resp, colorScheme: 'secondary' });
|
||||||
autoComplete="username"
|
setSubmittingForm(false);
|
||||||
value={formData.username.value}
|
} catch (e) {
|
||||||
onChangeText={(t) => {
|
toast.show({
|
||||||
let error: null | string = null;
|
description: e as string,
|
||||||
validationSchemas.username
|
colorScheme: 'red',
|
||||||
.validate(t)
|
avoidKeyboard: true,
|
||||||
.catch((e) => (error = e.message))
|
});
|
||||||
.finally(() => {
|
setSubmittingForm(false);
|
||||||
setFormData({ ...formData, username: { value: t, error } });
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<FormControl.ErrorMessage
|
|
||||||
leftIcon={<WarningOutlineIcon size="xs" />}
|
|
||||||
>
|
|
||||||
{formData.username.error}
|
|
||||||
</FormControl.ErrorMessage>
|
|
||||||
<FormControl.Label>
|
|
||||||
<Translate translationKey='password'/>
|
|
||||||
</FormControl.Label>
|
|
||||||
<Input
|
|
||||||
isRequired
|
|
||||||
type="password"
|
|
||||||
autoComplete="password"
|
|
||||||
value={formData.password.value}
|
|
||||||
onChangeText={(t) => {
|
|
||||||
let error: null | string = null;
|
|
||||||
validationSchemas.password
|
|
||||||
.validate(t)
|
|
||||||
.catch((e) => (error = e.message))
|
|
||||||
.finally(() => {
|
|
||||||
setFormData({ ...formData, password: { value: t, error } });
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<FormControl.ErrorMessage
|
|
||||||
leftIcon={<WarningOutlineIcon size="xs" />}
|
|
||||||
>
|
|
||||||
{formData.password.error}
|
|
||||||
</FormControl.ErrorMessage>
|
|
||||||
<TextButton translate={{ translationKey: 'signInBtn' }}
|
|
||||||
style={{ marginTop: 10 }}
|
|
||||||
isLoading={submittingForm}
|
|
||||||
isDisabled={
|
|
||||||
formData.password.error !== null ||
|
|
||||||
formData.username.error !== null ||
|
|
||||||
formData.username.value === "" ||
|
|
||||||
formData.password.value === ""
|
|
||||||
}
|
}
|
||||||
onPress={async () => {
|
}}
|
||||||
setSubmittingForm(true);
|
/>
|
||||||
try {
|
</FormControl>
|
||||||
const resp = await onSubmit(
|
</Stack>
|
||||||
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);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,42 +1,31 @@
|
|||||||
// a form for sign up
|
// a form for sign up
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import { Translate, translate } from "../../i18n/i18n";
|
import { Translate, translate } from '../../i18n/i18n';
|
||||||
import { string } from "yup";
|
import { string } from 'yup';
|
||||||
import {
|
import { FormControl, Input, Stack, WarningOutlineIcon, Box, useToast } from 'native-base';
|
||||||
FormControl,
|
import TextButton from '../TextButton';
|
||||||
Input,
|
|
||||||
Stack,
|
|
||||||
WarningOutlineIcon,
|
|
||||||
Box,
|
|
||||||
useToast,
|
|
||||||
} from "native-base";
|
|
||||||
import TextButton from "../TextButton";
|
|
||||||
|
|
||||||
interface SignupFormProps {
|
interface SignupFormProps {
|
||||||
onSubmit: (
|
onSubmit: (username: string, password: string, email: string) => Promise<string>;
|
||||||
username: string,
|
|
||||||
password: string,
|
|
||||||
email: string
|
|
||||||
) => Promise<string>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SignUpForm = ({ onSubmit }: SignupFormProps) => {
|
const SignUpForm = ({ onSubmit }: SignupFormProps) => {
|
||||||
const [formData, setFormData] = React.useState({
|
const [formData, setFormData] = React.useState({
|
||||||
username: {
|
username: {
|
||||||
value: "",
|
value: '',
|
||||||
error: null as string | null,
|
error: null as string | null,
|
||||||
},
|
},
|
||||||
password: {
|
password: {
|
||||||
value: "",
|
value: '',
|
||||||
error: null as string | null,
|
error: null as string | null,
|
||||||
},
|
},
|
||||||
repeatPassword: {
|
repeatPassword: {
|
||||||
value: "",
|
value: '',
|
||||||
error: null as string | null,
|
error: null as string | null,
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
value: "",
|
value: '',
|
||||||
error: null as string | null,
|
error: null as string | null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -44,20 +33,20 @@ const SignUpForm = ({ onSubmit }: SignupFormProps) => {
|
|||||||
|
|
||||||
const validationSchemas = {
|
const validationSchemas = {
|
||||||
username: string()
|
username: string()
|
||||||
.min(3, translate("usernameTooShort"))
|
.min(3, translate('usernameTooShort'))
|
||||||
.max(20, translate("usernameTooLong"))
|
.max(20, translate('usernameTooLong'))
|
||||||
.required("Username is required"),
|
.required('Username is required'),
|
||||||
email: string().email("Invalid email").required("Email is required"),
|
email: string().email('Invalid email').required('Email is required'),
|
||||||
password: string()
|
password: string()
|
||||||
.min(4, translate("passwordTooShort"))
|
.min(4, translate('passwordTooShort'))
|
||||||
.max(100, translate("passwordTooLong"))
|
.max(100, translate('passwordTooLong'))
|
||||||
// .matches(
|
// .matches(
|
||||||
// /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\$-_%\^&\*])(?=.{8,})/,
|
// /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\$-_%\^&\*])(?=.{8,})/,
|
||||||
// translate(
|
// translate(
|
||||||
// "Must Contain 8 Characters, One Uppercase, One Lowercase, One Number and One Special Case Character"
|
// "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();
|
const toast = useToast();
|
||||||
|
|
||||||
@@ -75,7 +64,7 @@ const SignUpForm = ({ onSubmit }: SignupFormProps) => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<FormControl.Label>
|
<FormControl.Label>
|
||||||
<Translate translationKey='username'/>
|
<Translate translationKey="username" />
|
||||||
</FormControl.Label>
|
</FormControl.Label>
|
||||||
<Input
|
<Input
|
||||||
isRequired
|
isRequired
|
||||||
@@ -93,13 +82,11 @@ const SignUpForm = ({ onSubmit }: SignupFormProps) => {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FormControl.ErrorMessage
|
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />}>
|
||||||
leftIcon={<WarningOutlineIcon size="xs" />}
|
|
||||||
>
|
|
||||||
{formData.username.error}
|
{formData.username.error}
|
||||||
</FormControl.ErrorMessage>
|
</FormControl.ErrorMessage>
|
||||||
<FormControl.Label>
|
<FormControl.Label>
|
||||||
<Translate translationKey='email'/>
|
<Translate translationKey="email" />
|
||||||
</FormControl.Label>
|
</FormControl.Label>
|
||||||
<Input
|
<Input
|
||||||
isRequired
|
isRequired
|
||||||
@@ -117,13 +104,11 @@ const SignUpForm = ({ onSubmit }: SignupFormProps) => {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FormControl.ErrorMessage
|
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />}>
|
||||||
leftIcon={<WarningOutlineIcon size="xs" />}
|
|
||||||
>
|
|
||||||
{formData.email.error}
|
{formData.email.error}
|
||||||
</FormControl.ErrorMessage>
|
</FormControl.ErrorMessage>
|
||||||
<FormControl.Label>
|
<FormControl.Label>
|
||||||
<Translate translationKey='password'/>
|
<Translate translationKey="password" />
|
||||||
</FormControl.Label>
|
</FormControl.Label>
|
||||||
<Input
|
<Input
|
||||||
isRequired
|
isRequired
|
||||||
@@ -140,13 +125,11 @@ const SignUpForm = ({ onSubmit }: SignupFormProps) => {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FormControl.ErrorMessage
|
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />}>
|
||||||
leftIcon={<WarningOutlineIcon size="xs" />}
|
|
||||||
>
|
|
||||||
{formData.password.error}
|
{formData.password.error}
|
||||||
</FormControl.ErrorMessage>
|
</FormControl.ErrorMessage>
|
||||||
<FormControl.Label>
|
<FormControl.Label>
|
||||||
<Translate translationKey='repeatPassword'/>
|
<Translate translationKey="repeatPassword" />
|
||||||
</FormControl.Label>
|
</FormControl.Label>
|
||||||
<Input
|
<Input
|
||||||
isRequired
|
isRequired
|
||||||
@@ -160,7 +143,7 @@ const SignUpForm = ({ onSubmit }: SignupFormProps) => {
|
|||||||
.catch((e) => (error = e.message))
|
.catch((e) => (error = e.message))
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
if (!error && t !== formData.password.value) {
|
if (!error && t !== formData.password.value) {
|
||||||
error = translate("passwordsDontMatch");
|
error = translate('passwordsDontMatch');
|
||||||
}
|
}
|
||||||
setFormData({
|
setFormData({
|
||||||
...formData,
|
...formData,
|
||||||
@@ -169,12 +152,11 @@ const SignUpForm = ({ onSubmit }: SignupFormProps) => {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FormControl.ErrorMessage
|
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />}>
|
||||||
leftIcon={<WarningOutlineIcon size="xs" />}
|
|
||||||
>
|
|
||||||
{formData.repeatPassword.error}
|
{formData.repeatPassword.error}
|
||||||
</FormControl.ErrorMessage>
|
</FormControl.ErrorMessage>
|
||||||
<TextButton translate={{ translationKey: 'signUpBtn' }}
|
<TextButton
|
||||||
|
translate={{ translationKey: 'signUpBtn' }}
|
||||||
style={{ marginTop: 10 }}
|
style={{ marginTop: 10 }}
|
||||||
isLoading={submittingForm}
|
isLoading={submittingForm}
|
||||||
isDisabled={
|
isDisabled={
|
||||||
@@ -182,10 +164,10 @@ const SignUpForm = ({ onSubmit }: SignupFormProps) => {
|
|||||||
formData.username.error !== null ||
|
formData.username.error !== null ||
|
||||||
formData.repeatPassword.error !== null ||
|
formData.repeatPassword.error !== null ||
|
||||||
formData.email.error !== null ||
|
formData.email.error !== null ||
|
||||||
formData.username.value === "" ||
|
formData.username.value === '' ||
|
||||||
formData.password.value === "" ||
|
formData.password.value === '' ||
|
||||||
formData.repeatPassword.value === "" ||
|
formData.repeatPassword.value === '' ||
|
||||||
formData.repeatPassword.value === ""
|
formData.repeatPassword.value === ''
|
||||||
}
|
}
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
setSubmittingForm(true);
|
setSubmittingForm(true);
|
||||||
|
|||||||
@@ -1,16 +1,7 @@
|
|||||||
import * as React from "react";
|
import * as React from 'react';
|
||||||
import { StyleProp, ViewStyle, StyleSheet } from "react-native";
|
import { StyleProp, ViewStyle } from 'react-native';
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
import {
|
import { View, Text, Row, Icon, Button, useBreakpointValue } from 'native-base';
|
||||||
View,
|
|
||||||
Text,
|
|
||||||
Pressable,
|
|
||||||
Box,
|
|
||||||
Row,
|
|
||||||
Icon,
|
|
||||||
Button,
|
|
||||||
useBreakpointValue,
|
|
||||||
} from "native-base";
|
|
||||||
import {
|
import {
|
||||||
createNavigatorFactory,
|
createNavigatorFactory,
|
||||||
DefaultNavigatorOptions,
|
DefaultNavigatorOptions,
|
||||||
@@ -21,12 +12,12 @@ import {
|
|||||||
TabRouter,
|
TabRouter,
|
||||||
TabRouterOptions,
|
TabRouterOptions,
|
||||||
useNavigationBuilder,
|
useNavigationBuilder,
|
||||||
} from "@react-navigation/native";
|
} from '@react-navigation/native';
|
||||||
import { useNavigation } from "../../Navigation";
|
import { useNavigation } from '../../Navigation';
|
||||||
|
|
||||||
const TabRowNavigatorInitialComponentName = "TabIndex";
|
const TabRowNavigatorInitialComponentName = 'TabIndex';
|
||||||
|
|
||||||
export {TabRowNavigatorInitialComponentName};
|
export { TabRowNavigatorInitialComponentName };
|
||||||
|
|
||||||
// Props accepted by the view
|
// Props accepted by the view
|
||||||
type TabNavigationConfig = {
|
type TabNavigationConfig = {
|
||||||
@@ -37,6 +28,7 @@ type TabNavigationConfig = {
|
|||||||
// Supported screen options
|
// Supported screen options
|
||||||
type TabNavigationOptions = {
|
type TabNavigationOptions = {
|
||||||
title?: string;
|
title?: string;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
iconProvider?: any;
|
iconProvider?: any;
|
||||||
iconName?: string;
|
iconName?: string;
|
||||||
};
|
};
|
||||||
@@ -70,34 +62,31 @@ function TabNavigator({
|
|||||||
contentStyle,
|
contentStyle,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const navigator = useNavigation();
|
const navigator = useNavigation();
|
||||||
const { state, navigation, descriptors, NavigationContent } =
|
const { state, navigation, descriptors, NavigationContent } = useNavigationBuilder<
|
||||||
useNavigationBuilder<
|
TabNavigationState<ParamListBase>,
|
||||||
TabNavigationState<ParamListBase>,
|
TabRouterOptions,
|
||||||
TabRouterOptions,
|
TabActionHelpers<ParamListBase>,
|
||||||
TabActionHelpers<ParamListBase>,
|
TabNavigationOptions,
|
||||||
TabNavigationOptions,
|
TabNavigationEventMap
|
||||||
TabNavigationEventMap
|
>(TabRouter, {
|
||||||
>(TabRouter, {
|
children,
|
||||||
children,
|
screenOptions,
|
||||||
screenOptions,
|
initialRouteName,
|
||||||
initialRouteName,
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const screenSize = useBreakpointValue({ base: "small", md: "big" });
|
const screenSize = useBreakpointValue({ base: 'small', md: 'big' });
|
||||||
const [isPanelView, setIsPanelView] = React.useState(false);
|
const [isPanelView, setIsPanelView] = React.useState(false);
|
||||||
const isMobileView = screenSize == "small";
|
const isMobileView = screenSize == 'small';
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (state.index === 0) {
|
if (state.index === 0) {
|
||||||
if (isMobileView) {
|
if (isMobileView) {
|
||||||
setIsPanelView(true);
|
setIsPanelView(true);
|
||||||
} else {
|
} else {
|
||||||
navigation.reset(
|
navigation.reset({
|
||||||
{
|
...state,
|
||||||
...state,
|
index: 1,
|
||||||
index: 1,
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [state.index]);
|
}, [state.index]);
|
||||||
@@ -110,18 +99,18 @@ function TabNavigator({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationContent>
|
<NavigationContent>
|
||||||
<Row height={"100%"}>
|
<Row height={'100%'}>
|
||||||
{(!isMobileView || isPanelView) && (
|
{(!isMobileView || isPanelView) && (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
{
|
{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
justifyContent: "flex-start",
|
justifyContent: 'flex-start',
|
||||||
borderRightWidth: 1,
|
borderRightWidth: 1,
|
||||||
borderRightColor: "lightgray",
|
borderRightColor: 'lightgray',
|
||||||
overflow: "scroll",
|
overflow: 'scroll',
|
||||||
width: isMobileView ? "100%" : "clamp(200px, 20%, 300px)",
|
width: isMobileView ? '100%' : 'clamp(200px, 20%, 300px)',
|
||||||
},
|
},
|
||||||
tabBarStyle,
|
tabBarStyle,
|
||||||
]}
|
]}
|
||||||
@@ -135,11 +124,11 @@ function TabNavigator({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant={"ghost"}
|
variant={'ghost'}
|
||||||
key={route.key}
|
key={route.key}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
const event = navigation.emit({
|
const event = navigation.emit({
|
||||||
type: "tabPress",
|
type: 'tabPress',
|
||||||
target: route.key,
|
target: route.key,
|
||||||
canPreventDefault: true,
|
canPreventDefault: true,
|
||||||
data: {
|
data: {
|
||||||
@@ -157,12 +146,16 @@ function TabNavigator({
|
|||||||
setIsPanelView(false);
|
setIsPanelView(false);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
bgColor={isSelected && (!isMobileView || !isPanelView) ? "primary.300" : undefined}
|
bgColor={
|
||||||
|
isSelected && (!isMobileView || !isPanelView)
|
||||||
|
? 'primary.300'
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
style={{
|
style={{
|
||||||
justifyContent: "flex-start",
|
justifyContent: 'flex-start',
|
||||||
padding: "10px",
|
padding: '10px',
|
||||||
height: "50px",
|
height: '50px',
|
||||||
width: "100%",
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
leftIcon={
|
leftIcon={
|
||||||
options?.iconProvider && options?.iconName ? (
|
options?.iconProvider && options?.iconName ? (
|
||||||
@@ -185,17 +178,14 @@ function TabNavigator({
|
|||||||
)}
|
)}
|
||||||
{(!isMobileView || !isPanelView) && (
|
{(!isMobileView || !isPanelView) && (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[{ flex: 1, width: isMobileView ? '100%' : '700px' }, contentStyle]}
|
||||||
{ flex: 1, width: isMobileView ? "100%" : "700px" },
|
|
||||||
contentStyle,
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
{isMobileView && (
|
{isMobileView && (
|
||||||
<Button
|
<Button
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: 'absolute',
|
||||||
top: "10px",
|
top: '10px',
|
||||||
left: "10px",
|
left: '10px',
|
||||||
zIndex: 100,
|
zIndex: 100,
|
||||||
}}
|
}}
|
||||||
onPress={() => setIsPanelView(true)}
|
onPress={() => setIsPanelView(true)}
|
||||||
@@ -223,4 +213,4 @@ export default createNavigatorFactory<
|
|||||||
TabNavigationOptions,
|
TabNavigationOptions,
|
||||||
TabNavigationEventMap,
|
TabNavigationEventMap,
|
||||||
typeof TabNavigator
|
typeof TabNavigator
|
||||||
>(TabNavigator);
|
>(TabNavigator);
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import API from "../../API";
|
import API from '../../API';
|
||||||
import Song, { SongWithArtist } from "../../models/Song";
|
import { SongWithArtist } from '../../models/Song';
|
||||||
|
|
||||||
export const getSongWArtistSuggestions = async () => {
|
export const getSongWArtistSuggestions = async () => {
|
||||||
const nextStepQuery = await API.getSongSuggestions();
|
const nextStepQuery = await API.getSongSuggestions();
|
||||||
|
|
||||||
const songWartist = await Promise.all(
|
const songWartist = await Promise.all(
|
||||||
nextStepQuery.map(async (song) => {
|
nextStepQuery.map(async (song) => {
|
||||||
if (!song.artistId) throw new Error("Song has no artistId");
|
if (!song.artistId) throw new Error('Song has no artistId');
|
||||||
const artist = await API.getArtist(song.artistId);
|
const artist = await API.getArtist(song.artistId);
|
||||||
return { ...song, artist } as SongWithArtist;
|
return { ...song, artist } as SongWithArtist;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
return songWartist;
|
return songWartist;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,4 +17,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Appearance } from "react-native";
|
import { Appearance } from 'react-native';
|
||||||
import { useSelector } from "../state/Store";
|
import { useSelector } from '../state/Store';
|
||||||
|
|
||||||
const useColorScheme = (): 'light' | 'dark' => {
|
const useColorScheme = (): 'light' | 'dark' => {
|
||||||
const colorScheme = useSelector((state) => state.settings.local.colorScheme);
|
const colorScheme = useSelector((state) => state.settings.local.colorScheme);
|
||||||
@@ -9,6 +9,6 @@ const useColorScheme = (): 'light' | 'dark' => {
|
|||||||
return systemColorScheme ?? 'light';
|
return systemColorScheme ?? 'light';
|
||||||
}
|
}
|
||||||
return colorScheme;
|
return colorScheme;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default useColorScheme;
|
export default useColorScheme;
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { useQuery } from "react-query"
|
import { useQuery } from 'react-query';
|
||||||
import API from "../API"
|
import API from '../API';
|
||||||
|
|
||||||
const useUserSettings = () => {
|
const useUserSettings = () => {
|
||||||
const queryKey = ['settings'];
|
const queryKey = ['settings'];
|
||||||
const settings = useQuery(queryKey, () => API.getUserSettings())
|
const settings = useQuery(queryKey, () => API.getUserSettings());
|
||||||
const updateSettings = (...params: Parameters<typeof API.updateUserSettings>) => API
|
const updateSettings = (...params: Parameters<typeof API.updateUserSettings>) =>
|
||||||
.updateUserSettings(...params)
|
API.updateUserSettings(...params).then(() => settings.refetch());
|
||||||
.then(() => settings.refetch());
|
return { settings, updateSettings };
|
||||||
return { settings, updateSettings }
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export default useUserSettings;
|
export default useUserSettings;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { RootState, useSelector } from "../state/Store";
|
import { RootState, useSelector } from '../state/Store';
|
||||||
import i18n from "./i18n";
|
import i18n from './i18n';
|
||||||
|
|
||||||
type LanguageGateProps = {
|
type LanguageGateProps = {
|
||||||
children: any;
|
children: JSX.Element;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gate to handle language update at startup and on every dispatch
|
* Gate to handle language update at startup and on every dispatch
|
||||||
@@ -13,6 +13,6 @@ const LanguageGate = (props: LanguageGateProps) => {
|
|||||||
const language = useSelector((state: RootState) => state.language.value);
|
const language = useSelector((state: RootState) => state.language.value);
|
||||||
i18n.changeLanguage(language);
|
i18n.changeLanguage(language);
|
||||||
return props.children;
|
return props.children;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default LanguageGate;
|
export default LanguageGate;
|
||||||
|
|||||||
@@ -1,540 +1,539 @@
|
|||||||
export const en = {
|
export const en = {
|
||||||
welcome: "Welcome",
|
welcome: 'Welcome',
|
||||||
welcomeMessage: "Welcome back ",
|
welcomeMessage: 'Welcome back ',
|
||||||
signOutBtn: "Sign out",
|
signOutBtn: 'Sign out',
|
||||||
signInBtn: "Sign in",
|
signInBtn: 'Sign in',
|
||||||
signUpBtn: "Sign up",
|
signUpBtn: 'Sign up',
|
||||||
changeLanguageBtn: "Change language",
|
changeLanguageBtn: 'Change language',
|
||||||
search: "Search",
|
search: 'Search',
|
||||||
login: "Login",
|
login: 'Login',
|
||||||
signUp: "Sign up",
|
signUp: 'Sign up',
|
||||||
signIn: "Sign in",
|
signIn: 'Sign in',
|
||||||
searchBtn: "Search",
|
searchBtn: 'Search',
|
||||||
play: "Play",
|
play: 'Play',
|
||||||
playBtn: "Play",
|
playBtn: 'Play',
|
||||||
practiceBtn: "Practice",
|
practiceBtn: 'Practice',
|
||||||
playAgain: "Play Again",
|
playAgain: 'Play Again',
|
||||||
songPageBtn: "Go to song page",
|
songPageBtn: 'Go to song page',
|
||||||
level: "Level",
|
level: 'Level',
|
||||||
chapters: "Chapters",
|
chapters: 'Chapters',
|
||||||
bestScore: "Best Score",
|
bestScore: 'Best Score',
|
||||||
lastScore: "Last Score",
|
lastScore: 'Last Score',
|
||||||
langBtn: "Language",
|
langBtn: 'Language',
|
||||||
backBtn: "Back",
|
backBtn: 'Back',
|
||||||
settingsBtn: "Settings",
|
settingsBtn: 'Settings',
|
||||||
prefBtn: "Preferences",
|
prefBtn: 'Preferences',
|
||||||
notifBtn: "Notifications",
|
notifBtn: 'Notifications',
|
||||||
privBtn: "Privacy",
|
privBtn: 'Privacy',
|
||||||
goNextStep: "Step Up!",
|
goNextStep: 'Step Up!',
|
||||||
mySkillsToImprove: "My Competencies to work on",
|
mySkillsToImprove: 'My Competencies to work on',
|
||||||
recentlyPlayed: "Recently played",
|
recentlyPlayed: 'Recently played',
|
||||||
|
|
||||||
songsToGetBetter: "Recommendations",
|
songsToGetBetter: 'Recommendations',
|
||||||
lastSearched: "Last searched",
|
lastSearched: 'Last searched',
|
||||||
levelProgress: "good notes",
|
levelProgress: 'good notes',
|
||||||
score: "Score",
|
score: 'Score',
|
||||||
|
|
||||||
//search
|
//search
|
||||||
allFilter: "All",
|
allFilter: 'All',
|
||||||
artistFilter: "Artists",
|
artistFilter: 'Artists',
|
||||||
songsFilter: "Songs",
|
songsFilter: 'Songs',
|
||||||
genreFilter: "Genres",
|
genreFilter: 'Genres',
|
||||||
|
|
||||||
// profile page
|
// profile page
|
||||||
user: "Profile",
|
user: 'Profile',
|
||||||
medals: "Medals",
|
medals: 'Medals',
|
||||||
playerStats: "My stats",
|
playerStats: 'My stats',
|
||||||
mostPlayedSong: "Most played song : ",
|
mostPlayedSong: 'Most played song : ',
|
||||||
goodNotesPlayed: "Good notes played : ",
|
goodNotesPlayed: 'Good notes played : ',
|
||||||
longestCombo: "Longest combo : ",
|
longestCombo: 'Longest combo : ',
|
||||||
favoriteGenre: "Favorite genre : ",
|
favoriteGenre: 'Favorite genre : ',
|
||||||
|
|
||||||
// Difficulty settings
|
// Difficulty settings
|
||||||
diffBtn: "Difficulty",
|
diffBtn: 'Difficulty',
|
||||||
easy: "Beginner",
|
easy: 'Beginner',
|
||||||
medium: "Intermediate",
|
medium: 'Intermediate',
|
||||||
hard: "Pro",
|
hard: 'Pro',
|
||||||
|
|
||||||
// theme settings
|
// theme settings
|
||||||
dark: "Dark",
|
dark: 'Dark',
|
||||||
system: "System",
|
system: 'System',
|
||||||
light: "Light",
|
light: 'Light',
|
||||||
|
|
||||||
// competencies
|
// competencies
|
||||||
pedalsCompetency: "Pedals",
|
pedalsCompetency: 'Pedals',
|
||||||
rightHandCompetency: "Right hand",
|
rightHandCompetency: 'Right hand',
|
||||||
leftHandCompetency: "Left hand",
|
leftHandCompetency: 'Left hand',
|
||||||
accuracyCompetency: "Accuracy",
|
accuracyCompetency: 'Accuracy',
|
||||||
arpegeCompetency: "Arpeges",
|
arpegeCompetency: 'Arpeges',
|
||||||
chordsCompetency: "Chords",
|
chordsCompetency: 'Chords',
|
||||||
|
|
||||||
/* account settings and logs */
|
/* account settings and logs */
|
||||||
// buttons
|
// buttons
|
||||||
requirementsSentence:
|
requirementsSentence:
|
||||||
"Must Contain 8 Characters, One Uppercase, One Lowercase, One Number and One Special Case Character",
|
'Must Contain 8 Characters, One Uppercase, One Lowercase, One Number and One Special Case Character',
|
||||||
|
|
||||||
// feedback for the user
|
// feedback for the user
|
||||||
usernameTooShort: "Username is too short",
|
usernameTooShort: 'Username is too short',
|
||||||
passwordTooShort: "Password is too short",
|
passwordTooShort: 'Password is too short',
|
||||||
usernameTooLong: "Username is too long",
|
usernameTooLong: 'Username is too long',
|
||||||
passwordTooLong: "Password is too long",
|
passwordTooLong: 'Password is too long',
|
||||||
passwordsDontMatch: "Passwords don't match",
|
passwordsDontMatch: "Passwords don't match",
|
||||||
invalidCredentials: "Invalid credentials",
|
invalidCredentials: 'Invalid credentials',
|
||||||
invalidEmail: "Invalid email",
|
invalidEmail: 'Invalid email',
|
||||||
accountCreated: "Account created",
|
accountCreated: 'Account created',
|
||||||
loggedIn: "Logged in",
|
loggedIn: 'Logged in',
|
||||||
precisionScore: "Precision",
|
precisionScore: 'Precision',
|
||||||
goodNotesInARow: "Good notes in a row",
|
goodNotesInARow: 'Good notes in a row',
|
||||||
usernameTaken: "Username already taken",
|
usernameTaken: 'Username already taken',
|
||||||
goodNotes: "good notes",
|
goodNotes: 'good notes',
|
||||||
|
|
||||||
// categories
|
// categories
|
||||||
username: "Username",
|
username: 'Username',
|
||||||
password: "Password",
|
password: 'Password',
|
||||||
email: "Email",
|
email: 'Email',
|
||||||
repeatPassword: "Repeat password",
|
repeatPassword: 'Repeat password',
|
||||||
changepasswdBtn: "Change Password",
|
changepasswdBtn: 'Change Password',
|
||||||
changeemailBtn: "Change Email",
|
changeemailBtn: 'Change Email',
|
||||||
googleacctBtn: "Google Account",
|
googleacctBtn: 'Google Account',
|
||||||
forgottenPassword: "Forgotten password",
|
forgottenPassword: 'Forgotten password',
|
||||||
partition: "Partition",
|
partition: 'Partition',
|
||||||
|
|
||||||
//errors
|
//errors
|
||||||
unknownError: "Unknown error",
|
unknownError: 'Unknown error',
|
||||||
errAlrdExst: "Already exist",
|
errAlrdExst: 'Already exist',
|
||||||
errIncrrct: "Incorrect Credentials",
|
errIncrrct: 'Incorrect Credentials',
|
||||||
errNoResults: "No Results Found",
|
errNoResults: 'No Results Found',
|
||||||
userProfileFetchError: "An error occured while fetching your profile",
|
userProfileFetchError: 'An error occured while fetching your profile',
|
||||||
tryAgain: "Try Again",
|
tryAgain: 'Try Again',
|
||||||
|
|
||||||
// Playback messages
|
// Playback messages
|
||||||
missed: "Missed note",
|
missed: 'Missed note',
|
||||||
perfect: "Perfect",
|
perfect: 'Perfect',
|
||||||
great: "Great",
|
great: 'Great',
|
||||||
good: "Good",
|
good: 'Good',
|
||||||
bestStreak: "Best streak",
|
bestStreak: 'Best streak',
|
||||||
precision: "Precision",
|
precision: 'Precision',
|
||||||
wrong: "Wrong",
|
wrong: 'Wrong',
|
||||||
short: "A little too short",
|
short: 'A little too short',
|
||||||
long: "A little too long",
|
long: 'A little too long',
|
||||||
tooLong: "Too Long",
|
tooLong: 'Too Long',
|
||||||
tooShort: "Too Short",
|
tooShort: 'Too Short',
|
||||||
|
|
||||||
changePassword: "Change password",
|
changePassword: 'Change password',
|
||||||
oldPassword: "Old password",
|
oldPassword: 'Old password',
|
||||||
newPassword: "New password",
|
newPassword: 'New password',
|
||||||
confirmNewPassword: "Confirm new password",
|
confirmNewPassword: 'Confirm new password',
|
||||||
submitBtn: "Submit",
|
submitBtn: 'Submit',
|
||||||
|
|
||||||
changeEmail: "Change email",
|
changeEmail: 'Change email',
|
||||||
oldEmail: "Old email",
|
oldEmail: 'Old email',
|
||||||
newEmail: "New email",
|
newEmail: 'New email',
|
||||||
|
|
||||||
passwordUpdated: "Password updated",
|
passwordUpdated: 'Password updated',
|
||||||
emailUpdated: "Email updated",
|
emailUpdated: 'Email updated',
|
||||||
|
|
||||||
SettingsCategoryProfile: "Profile",
|
SettingsCategoryProfile: 'Profile',
|
||||||
SettingsCategoryPreferences: "Preferences",
|
SettingsCategoryPreferences: 'Preferences',
|
||||||
SettingsCategoryNotifications: "Notifications",
|
SettingsCategoryNotifications: 'Notifications',
|
||||||
SettingsCategoryPrivacy: "Privacy",
|
SettingsCategoryPrivacy: 'Privacy',
|
||||||
SettingsCategorySecurity: "Security",
|
SettingsCategorySecurity: 'Security',
|
||||||
SettingsCategoryEmail: "Email",
|
SettingsCategoryEmail: 'Email',
|
||||||
SettingsCategoryGoogle: "Google",
|
SettingsCategoryGoogle: 'Google',
|
||||||
SettingsCategoryPiano: "Piano",
|
SettingsCategoryPiano: 'Piano',
|
||||||
SettingsCategoryGuest: "Guest",
|
SettingsCategoryGuest: 'Guest',
|
||||||
|
|
||||||
transformGuestToUserExplanations:
|
transformGuestToUserExplanations:
|
||||||
"You can transform your guest account to a user account by providing a username and a password. You will then be able to save your progress and access your profile.",
|
'You can transform your guest account to a user account by providing a username and a password. You will then be able to save your progress and access your profile.',
|
||||||
SettingsNotificationsPushNotifications: "Push",
|
SettingsNotificationsPushNotifications: 'Push',
|
||||||
SettingsNotificationsEmailNotifications: "Email",
|
SettingsNotificationsEmailNotifications: 'Email',
|
||||||
SettingsNotificationsTrainingReminder: "Training reminder",
|
SettingsNotificationsTrainingReminder: 'Training reminder',
|
||||||
SettingsNotificationsReleaseAlert: "Release alert",
|
SettingsNotificationsReleaseAlert: 'Release alert',
|
||||||
|
|
||||||
dataCollection: "Data collection",
|
dataCollection: 'Data collection',
|
||||||
customAds: "Custom ads",
|
customAds: 'Custom ads',
|
||||||
recommendations: "Recommendations",
|
recommendations: 'Recommendations',
|
||||||
|
|
||||||
SettingsPreferencesTheme: "Theme",
|
SettingsPreferencesTheme: 'Theme',
|
||||||
SettingsPreferencesLanguage: "Language",
|
SettingsPreferencesLanguage: 'Language',
|
||||||
SettingsPreferencesDifficulty: "Difficulty",
|
SettingsPreferencesDifficulty: 'Difficulty',
|
||||||
SettingsPreferencesColorblindMode: "Colorblind mode",
|
SettingsPreferencesColorblindMode: 'Colorblind mode',
|
||||||
SettingsPreferencesMicVolume: "Mic volume",
|
SettingsPreferencesMicVolume: 'Mic volume',
|
||||||
SettingsPreferencesDevice: "Device",
|
SettingsPreferencesDevice: 'Device',
|
||||||
|
|
||||||
NoAssociatedEmail: "No associated email",
|
NoAssociatedEmail: 'No associated email',
|
||||||
nbGamesPlayed: "Games played",
|
nbGamesPlayed: 'Games played',
|
||||||
XPDescription:
|
XPDescription:
|
||||||
"XP is a measure of your progress. You earn XP by playing songs and completing challenges.",
|
'XP is a measure of your progress. You earn XP by playing songs and completing challenges.',
|
||||||
userCreatedAt: "Creation date",
|
userCreatedAt: 'Creation date',
|
||||||
premiumAccount: "Premium account",
|
premiumAccount: 'Premium account',
|
||||||
yes: "Yes",
|
yes: 'Yes',
|
||||||
no: "No",
|
no: 'No',
|
||||||
|
|
||||||
Attention: "Attention",
|
Attention: 'Attention',
|
||||||
YouAreCurrentlyConnectedWithAGuestAccountWarning:
|
YouAreCurrentlyConnectedWithAGuestAccountWarning:
|
||||||
"You are currently connected with a guest account. Disconneting will result in your data being lost. If you want to save your progress, you need to create an account.",
|
'You are currently connected with a guest account. Disconneting will result in your data being lost. If you want to save your progress, you need to create an account.',
|
||||||
|
|
||||||
recentSearches: "Recent searches",
|
recentSearches: 'Recent searches',
|
||||||
noRecentSearches: "No recent searches",
|
noRecentSearches: 'No recent searches',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fr: typeof en = {
|
export const fr: typeof en = {
|
||||||
welcome: "Bienvenue",
|
welcome: 'Bienvenue',
|
||||||
welcomeMessage: "Re-Bonjour ",
|
welcomeMessage: 'Re-Bonjour ',
|
||||||
signOutBtn: "Se déconnecter",
|
signOutBtn: 'Se déconnecter',
|
||||||
signInBtn: "Se connecter",
|
signInBtn: 'Se connecter',
|
||||||
changeLanguageBtn: "Changer la langue",
|
changeLanguageBtn: 'Changer la langue',
|
||||||
searchBtn: "Rechercher",
|
searchBtn: 'Rechercher',
|
||||||
playBtn: "Jouer",
|
playBtn: 'Jouer',
|
||||||
practiceBtn: "S'entrainer",
|
practiceBtn: "S'entrainer",
|
||||||
playAgain: "Rejouer",
|
playAgain: 'Rejouer',
|
||||||
songPageBtn: "Aller sur la page de la chanson",
|
songPageBtn: 'Aller sur la page de la chanson',
|
||||||
level: "Niveau",
|
level: 'Niveau',
|
||||||
chapters: "Chapitres",
|
chapters: 'Chapitres',
|
||||||
bestScore: "Meilleur Score",
|
bestScore: 'Meilleur Score',
|
||||||
lastScore: "Dernier Score",
|
lastScore: 'Dernier Score',
|
||||||
bestStreak: "Meilleure série",
|
bestStreak: 'Meilleure série',
|
||||||
precision: "Précision",
|
precision: 'Précision',
|
||||||
|
|
||||||
langBtn: "Langage",
|
langBtn: 'Langage',
|
||||||
backBtn: "Retour",
|
backBtn: 'Retour',
|
||||||
prefBtn: "Préférences",
|
prefBtn: 'Préférences',
|
||||||
notifBtn: "Notifications",
|
notifBtn: 'Notifications',
|
||||||
privBtn: "Confidentialité",
|
privBtn: 'Confidentialité',
|
||||||
|
|
||||||
play: "Jouer",
|
play: 'Jouer',
|
||||||
changeEmail: "Changer d'email",
|
changeEmail: "Changer d'email",
|
||||||
newEmail: "Nouvel email",
|
newEmail: 'Nouvel email',
|
||||||
oldEmail: "Ancien email",
|
oldEmail: 'Ancien email',
|
||||||
|
|
||||||
// profile page
|
// profile page
|
||||||
user: "Profil",
|
user: 'Profil',
|
||||||
medals: "Medailles",
|
medals: 'Medailles',
|
||||||
playerStats: "Mes statistiques",
|
playerStats: 'Mes statistiques',
|
||||||
mostPlayedSong: "Chanson la plus jouée : ",
|
mostPlayedSong: 'Chanson la plus jouée : ',
|
||||||
goodNotesPlayed: "Bonnes notes jouées : ",
|
goodNotesPlayed: 'Bonnes notes jouées : ',
|
||||||
longestCombo: "Combo le plus long : ",
|
longestCombo: 'Combo le plus long : ',
|
||||||
favoriteGenre: "Genre favori : ",
|
favoriteGenre: 'Genre favori : ',
|
||||||
|
|
||||||
//search
|
//search
|
||||||
allFilter: "Tout",
|
allFilter: 'Tout',
|
||||||
artistFilter: "Artistes",
|
artistFilter: 'Artistes',
|
||||||
songsFilter: "Morceaux",
|
songsFilter: 'Morceaux',
|
||||||
genreFilter: "Genres",
|
genreFilter: 'Genres',
|
||||||
|
|
||||||
// Difficulty settings
|
// Difficulty settings
|
||||||
diffBtn: "Difficulté",
|
diffBtn: 'Difficulté',
|
||||||
easy: "Débutant",
|
easy: 'Débutant',
|
||||||
medium: "Intermédiaire",
|
medium: 'Intermédiaire',
|
||||||
hard: "Avancé",
|
hard: 'Avancé',
|
||||||
|
|
||||||
// theme settings
|
// theme settings
|
||||||
dark: "Foncé",
|
dark: 'Foncé',
|
||||||
system: "Système",
|
system: 'Système',
|
||||||
light: "Clair",
|
light: 'Clair',
|
||||||
settingsBtn: "Réglages",
|
settingsBtn: 'Réglages',
|
||||||
goNextStep: "Prochaine Etape",
|
goNextStep: 'Prochaine Etape',
|
||||||
mySkillsToImprove: "Mes Skills",
|
mySkillsToImprove: 'Mes Skills',
|
||||||
recentlyPlayed: "Joués récemment",
|
recentlyPlayed: 'Joués récemment',
|
||||||
search: "Rechercher",
|
search: 'Rechercher',
|
||||||
lastSearched: "Dernières recherches",
|
lastSearched: 'Dernières recherches',
|
||||||
levelProgress: "Niveau",
|
levelProgress: 'Niveau',
|
||||||
login: "Se connecter",
|
login: 'Se connecter',
|
||||||
signUp: "S'inscrire",
|
signUp: "S'inscrire",
|
||||||
signIn: "Se connecter",
|
signIn: 'Se connecter',
|
||||||
|
|
||||||
oldPassword: "Ancien mot de passe",
|
oldPassword: 'Ancien mot de passe',
|
||||||
newPassword: "Nouveau mot de passe",
|
newPassword: 'Nouveau mot de passe',
|
||||||
confirmNewPassword: "Confirmer le nouveau mot de passe",
|
confirmNewPassword: 'Confirmer le nouveau mot de passe',
|
||||||
submitBtn: "Soumettre",
|
submitBtn: 'Soumettre',
|
||||||
// competencies
|
// competencies
|
||||||
pedalsCompetency: "Pédales",
|
pedalsCompetency: 'Pédales',
|
||||||
rightHandCompetency: "Main droite",
|
rightHandCompetency: 'Main droite',
|
||||||
leftHandCompetency: "Main gauche",
|
leftHandCompetency: 'Main gauche',
|
||||||
accuracyCompetency: "Justesse",
|
accuracyCompetency: 'Justesse',
|
||||||
arpegeCompetency: "Arpege",
|
arpegeCompetency: 'Arpege',
|
||||||
chordsCompetency: "Accords",
|
chordsCompetency: 'Accords',
|
||||||
|
|
||||||
/* account settings and logs */
|
/* account settings and logs */
|
||||||
// buttons
|
// buttons
|
||||||
requirementsSentence:
|
requirementsSentence:
|
||||||
"Doit contenir 8 caractères, une majuscule, une minuscule, un chiffre et un caractère spécial",
|
'Doit contenir 8 caractères, une majuscule, une minuscule, un chiffre et un caractère spécial',
|
||||||
|
|
||||||
// feedback for the user
|
// feedback for the user
|
||||||
usernameTooShort: "Le nom d'utilisateur est trop court",
|
usernameTooShort: "Le nom d'utilisateur est trop court",
|
||||||
passwordTooShort: "Le mot de passe est trop court",
|
passwordTooShort: 'Le mot de passe est trop court',
|
||||||
usernameTooLong: "Le nom d'utilisateur est trop long",
|
usernameTooLong: "Le nom d'utilisateur est trop long",
|
||||||
passwordTooLong: "Le mot de passe est trop long",
|
passwordTooLong: 'Le mot de passe est trop long',
|
||||||
passwordsDontMatch: "Mots de passes différents",
|
passwordsDontMatch: 'Mots de passes différents',
|
||||||
invalidCredentials: "Identifiants incorrects",
|
invalidCredentials: 'Identifiants incorrects',
|
||||||
invalidEmail: "Email invalide",
|
invalidEmail: 'Email invalide',
|
||||||
loggedIn: "Connecté",
|
loggedIn: 'Connecté',
|
||||||
usernameTaken: "Nom d'utilisateur déjà pris",
|
usernameTaken: "Nom d'utilisateur déjà pris",
|
||||||
accountCreated: "Compte créé",
|
accountCreated: 'Compte créé',
|
||||||
|
|
||||||
// categories
|
// categories
|
||||||
username: "Nom d'utilisateur",
|
username: "Nom d'utilisateur",
|
||||||
password: "Mot de passe",
|
password: 'Mot de passe',
|
||||||
email: "Email",
|
email: 'Email',
|
||||||
repeatPassword: "Confirmer",
|
repeatPassword: 'Confirmer',
|
||||||
score: "Score",
|
score: 'Score',
|
||||||
changePassword: "Modification du mot de passe",
|
changePassword: 'Modification du mot de passe',
|
||||||
precisionScore: "Précision",
|
precisionScore: 'Précision',
|
||||||
goodNotesInARow: "Bonnes notes à la suite",
|
goodNotesInARow: 'Bonnes notes à la suite',
|
||||||
songsToGetBetter: "Recommendations",
|
songsToGetBetter: 'Recommendations',
|
||||||
goodNotes: "bonnes notes",
|
goodNotes: 'bonnes notes',
|
||||||
changepasswdBtn: "Changer le mot de passe",
|
changepasswdBtn: 'Changer le mot de passe',
|
||||||
changeemailBtn: "Changer l'email",
|
changeemailBtn: "Changer l'email",
|
||||||
googleacctBtn: "Compte Google",
|
googleacctBtn: 'Compte Google',
|
||||||
forgottenPassword: "Mot de passe oublié",
|
forgottenPassword: 'Mot de passe oublié',
|
||||||
partition: "Partition",
|
partition: 'Partition',
|
||||||
signUpBtn: "S'inscrire",
|
signUpBtn: "S'inscrire",
|
||||||
|
|
||||||
//errors
|
//errors
|
||||||
errAlrdExst: "Utilisateur existe déjà",
|
errAlrdExst: 'Utilisateur existe déjà',
|
||||||
unknownError: "Erreur inconnue",
|
unknownError: 'Erreur inconnue',
|
||||||
errIncrrct: "Identifiant incorrect",
|
errIncrrct: 'Identifiant incorrect',
|
||||||
errNoResults: "Aucun resultat",
|
errNoResults: 'Aucun resultat',
|
||||||
userProfileFetchError:
|
userProfileFetchError: 'Une erreur est survenue lors de la récupération du profil',
|
||||||
"Une erreur est survenue lors de la récupération du profil",
|
tryAgain: 'Réessayer',
|
||||||
tryAgain: "Réessayer",
|
|
||||||
|
|
||||||
// Playback messages
|
// Playback messages
|
||||||
missed: "Raté",
|
missed: 'Raté',
|
||||||
perfect: "Parfait",
|
perfect: 'Parfait',
|
||||||
great: "Super",
|
great: 'Super',
|
||||||
good: "Bien",
|
good: 'Bien',
|
||||||
wrong: "Oups",
|
wrong: 'Oups',
|
||||||
short: "Un peu court",
|
short: 'Un peu court',
|
||||||
long: "Un peu long",
|
long: 'Un peu long',
|
||||||
tooLong: "Trop long",
|
tooLong: 'Trop long',
|
||||||
tooShort: "Trop court",
|
tooShort: 'Trop court',
|
||||||
passwordUpdated: "Mot de passe mis à jour",
|
passwordUpdated: 'Mot de passe mis à jour',
|
||||||
emailUpdated: "Email mis à jour",
|
emailUpdated: 'Email mis à jour',
|
||||||
|
|
||||||
SettingsCategoryProfile: "Profil",
|
SettingsCategoryProfile: 'Profil',
|
||||||
SettingsCategoryPreferences: "Préférences",
|
SettingsCategoryPreferences: 'Préférences',
|
||||||
SettingsCategoryNotifications: "Notifications",
|
SettingsCategoryNotifications: 'Notifications',
|
||||||
SettingsCategoryPrivacy: "Confidentialité",
|
SettingsCategoryPrivacy: 'Confidentialité',
|
||||||
SettingsCategorySecurity: "Sécurité",
|
SettingsCategorySecurity: 'Sécurité',
|
||||||
SettingsCategoryEmail: "Email",
|
SettingsCategoryEmail: 'Email',
|
||||||
SettingsCategoryGoogle: "Google",
|
SettingsCategoryGoogle: 'Google',
|
||||||
SettingsCategoryPiano: "Piano",
|
SettingsCategoryPiano: 'Piano',
|
||||||
|
|
||||||
transformGuestToUserExplanations:
|
transformGuestToUserExplanations:
|
||||||
"Vous êtes actuellement connecté en tant qu'invité. Vous pouvez créer un compte pour sauvegarder vos données et profiter de toutes les fonctionnalités de Chromacase.",
|
"Vous êtes actuellement connecté en tant qu'invité. Vous pouvez créer un compte pour sauvegarder vos données et profiter de toutes les fonctionnalités de Chromacase.",
|
||||||
SettingsCategoryGuest: "Invité",
|
SettingsCategoryGuest: 'Invité',
|
||||||
SettingsNotificationsEmailNotifications: "Email",
|
SettingsNotificationsEmailNotifications: 'Email',
|
||||||
SettingsNotificationsPushNotifications: "Notifications push",
|
SettingsNotificationsPushNotifications: 'Notifications push',
|
||||||
SettingsNotificationsReleaseAlert: "Alertes de nouvelles Sorties",
|
SettingsNotificationsReleaseAlert: 'Alertes de nouvelles Sorties',
|
||||||
SettingsNotificationsTrainingReminder: "Rappel d'entrainement",
|
SettingsNotificationsTrainingReminder: "Rappel d'entrainement",
|
||||||
|
|
||||||
SettingsPreferencesColorblindMode: "Mode daltonien",
|
SettingsPreferencesColorblindMode: 'Mode daltonien',
|
||||||
SettingsPreferencesDevice: "Appareil",
|
SettingsPreferencesDevice: 'Appareil',
|
||||||
SettingsPreferencesDifficulty: "Difficulté",
|
SettingsPreferencesDifficulty: 'Difficulté',
|
||||||
SettingsPreferencesLanguage: "Langue",
|
SettingsPreferencesLanguage: 'Langue',
|
||||||
SettingsPreferencesTheme: "Thème",
|
SettingsPreferencesTheme: 'Thème',
|
||||||
SettingsPreferencesMicVolume: "Volume du micro",
|
SettingsPreferencesMicVolume: 'Volume du micro',
|
||||||
|
|
||||||
dataCollection: "Collecte de données",
|
dataCollection: 'Collecte de données',
|
||||||
recommendations: "Recommandations",
|
recommendations: 'Recommandations',
|
||||||
customAds: "Publicités personnalisées",
|
customAds: 'Publicités personnalisées',
|
||||||
|
|
||||||
NoAssociatedEmail: "Aucun email associé",
|
NoAssociatedEmail: 'Aucun email associé',
|
||||||
nbGamesPlayed: "Parties jouées",
|
nbGamesPlayed: 'Parties jouées',
|
||||||
XPDescription:
|
XPDescription:
|
||||||
"L'XP est gagnée en jouant des chansons. Plus vous jouez, plus vous gagnez d'XP. Plus vous avez d'XP, plus vous montez de niveau.",
|
"L'XP est gagnée en jouant des chansons. Plus vous jouez, plus vous gagnez d'XP. Plus vous avez d'XP, plus vous montez de niveau.",
|
||||||
userCreatedAt: "Compte créé le",
|
userCreatedAt: 'Compte créé le',
|
||||||
premiumAccount: "Compte premium",
|
premiumAccount: 'Compte premium',
|
||||||
yes: "Oui",
|
yes: 'Oui',
|
||||||
no: "Non",
|
no: 'Non',
|
||||||
|
|
||||||
Attention: "Attention",
|
Attention: 'Attention',
|
||||||
YouAreCurrentlyConnectedWithAGuestAccountWarning:
|
YouAreCurrentlyConnectedWithAGuestAccountWarning:
|
||||||
"Vous êtes actuellement connecté en tant qu'invité. La déconnexion résultera en une perte de données. Vous pouvez créer un compte pour sauvegarder vos données.",
|
"Vous êtes actuellement connecté en tant qu'invité. La déconnexion résultera en une perte de données. Vous pouvez créer un compte pour sauvegarder vos données.",
|
||||||
|
|
||||||
recentSearches: "Recherches récentes",
|
recentSearches: 'Recherches récentes',
|
||||||
noRecentSearches: "Aucune recherche récente",
|
noRecentSearches: 'Aucune recherche récente',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sp: typeof en = {
|
export const sp: typeof en = {
|
||||||
welcomeMessage: "Benvenido",
|
welcomeMessage: 'Benvenido',
|
||||||
signOutBtn: "Desconectarse",
|
signOutBtn: 'Desconectarse',
|
||||||
signInBtn: "Connectarse",
|
signInBtn: 'Connectarse',
|
||||||
changepasswdBtn: "Cambiar la contraseña",
|
changepasswdBtn: 'Cambiar la contraseña',
|
||||||
changeemailBtn: "Cambiar e-mail",
|
changeemailBtn: 'Cambiar e-mail',
|
||||||
googleacctBtn: "Cuenta Google",
|
googleacctBtn: 'Cuenta Google',
|
||||||
goodNotes: "buenas notas",
|
goodNotes: 'buenas notas',
|
||||||
|
|
||||||
search: "Buscar",
|
search: 'Buscar',
|
||||||
login: "Iniciar sesión",
|
login: 'Iniciar sesión',
|
||||||
signUp: "Registrarse",
|
signUp: 'Registrarse',
|
||||||
signIn: "Iniciar sesión",
|
signIn: 'Iniciar sesión',
|
||||||
changeEmail: "Cambiar el correo electrónico",
|
changeEmail: 'Cambiar el correo electrónico',
|
||||||
newEmail: "Nuevo correo electrónico",
|
newEmail: 'Nuevo correo electrónico',
|
||||||
oldEmail: "Correo electrónico anterior",
|
oldEmail: 'Correo electrónico anterior',
|
||||||
|
|
||||||
// competencies
|
// competencies
|
||||||
changeLanguageBtn: "Cambiar el idioma",
|
changeLanguageBtn: 'Cambiar el idioma',
|
||||||
searchBtn: "Buscar",
|
searchBtn: 'Buscar',
|
||||||
playBtn: "reproducir",
|
playBtn: 'reproducir',
|
||||||
practiceBtn: "Práctica",
|
practiceBtn: 'Práctica',
|
||||||
playAgain: "Repetición",
|
playAgain: 'Repetición',
|
||||||
precisionScore: "Précision",
|
precisionScore: 'Précision',
|
||||||
songPageBtn: "canción",
|
songPageBtn: 'canción',
|
||||||
level: "nivele",
|
level: 'nivele',
|
||||||
chapters: "Capítulos",
|
chapters: 'Capítulos',
|
||||||
bestScore: "Mejor puntuación",
|
bestScore: 'Mejor puntuación',
|
||||||
lastScore: "Ùltima puntuación",
|
lastScore: 'Ùltima puntuación',
|
||||||
prefBtn: "Preferencias",
|
prefBtn: 'Preferencias',
|
||||||
notifBtn: "Notificaciones",
|
notifBtn: 'Notificaciones',
|
||||||
privBtn: "Privacidad",
|
privBtn: 'Privacidad',
|
||||||
goNextStep: "Da el siguiente paso",
|
goNextStep: 'Da el siguiente paso',
|
||||||
mySkillsToImprove: "Mis habilidades para mejorar",
|
mySkillsToImprove: 'Mis habilidades para mejorar',
|
||||||
recentlyPlayed: "Recientemente jugado",
|
recentlyPlayed: 'Recientemente jugado',
|
||||||
lastSearched: "Ultimas búsquedas",
|
lastSearched: 'Ultimas búsquedas',
|
||||||
|
|
||||||
welcome: "Benvenido a Chromacase",
|
welcome: 'Benvenido a Chromacase',
|
||||||
langBtn: "Langua",
|
langBtn: 'Langua',
|
||||||
backBtn: "Volver",
|
backBtn: 'Volver',
|
||||||
settingsBtn: "Ajustes",
|
settingsBtn: 'Ajustes',
|
||||||
play: "Jugar",
|
play: 'Jugar',
|
||||||
|
|
||||||
// profile page
|
// profile page
|
||||||
user: "Perfil",
|
user: 'Perfil',
|
||||||
medals: "Medallas",
|
medals: 'Medallas',
|
||||||
playerStats: "mis estadísticas",
|
playerStats: 'mis estadísticas',
|
||||||
mostPlayedSong: "Canción más reproducida : ",
|
mostPlayedSong: 'Canción más reproducida : ',
|
||||||
goodNotesPlayed: "Buenas notas tocadas : ",
|
goodNotesPlayed: 'Buenas notas tocadas : ',
|
||||||
longestCombo: "combo más largo : ",
|
longestCombo: 'combo más largo : ',
|
||||||
favoriteGenre: "genero favorito : ",
|
favoriteGenre: 'genero favorito : ',
|
||||||
|
|
||||||
//search
|
//search
|
||||||
allFilter: "Todos",
|
allFilter: 'Todos',
|
||||||
artistFilter: "Artistas",
|
artistFilter: 'Artistas',
|
||||||
songsFilter: "canciones",
|
songsFilter: 'canciones',
|
||||||
genreFilter: "géneros",
|
genreFilter: 'géneros',
|
||||||
|
|
||||||
// Difficulty settings
|
// Difficulty settings
|
||||||
diffBtn: "Dificultad",
|
diffBtn: 'Dificultad',
|
||||||
easy: "Principiante",
|
easy: 'Principiante',
|
||||||
medium: "Intermedio",
|
medium: 'Intermedio',
|
||||||
hard: "Avanzado",
|
hard: 'Avanzado',
|
||||||
|
|
||||||
// theme settings
|
// theme settings
|
||||||
dark: "Oscuro",
|
dark: 'Oscuro',
|
||||||
system: "Sistema",
|
system: 'Sistema',
|
||||||
light: "Claro",
|
light: 'Claro',
|
||||||
|
|
||||||
// competencies
|
// competencies
|
||||||
pedalsCompetency: "Pedal",
|
pedalsCompetency: 'Pedal',
|
||||||
rightHandCompetency: "Mano derecha",
|
rightHandCompetency: 'Mano derecha',
|
||||||
leftHandCompetency: "Mano izquierda",
|
leftHandCompetency: 'Mano izquierda',
|
||||||
accuracyCompetency: "Exactitud",
|
accuracyCompetency: 'Exactitud',
|
||||||
arpegeCompetency: "Arpegios",
|
arpegeCompetency: 'Arpegios',
|
||||||
chordsCompetency: "Accords",
|
chordsCompetency: 'Accords',
|
||||||
|
|
||||||
/* account settings and logs */
|
/* account settings and logs */
|
||||||
// buttons
|
// buttons
|
||||||
requirementsSentence:
|
requirementsSentence:
|
||||||
"Debe contener 8 caracteres, una mayúscula, una minúscula, un número y un carácter especial",
|
'Debe contener 8 caracteres, una mayúscula, una minúscula, un número y un carácter especial',
|
||||||
|
|
||||||
// feedback for the user
|
// feedback for the user
|
||||||
usernameTooShort: "Nombre de usuario demasiado corto",
|
usernameTooShort: 'Nombre de usuario demasiado corto',
|
||||||
passwordTooShort: "Contraseña demasiado corta",
|
passwordTooShort: 'Contraseña demasiado corta',
|
||||||
usernameTooLong: "Nombre de usuario demasiado largo",
|
usernameTooLong: 'Nombre de usuario demasiado largo',
|
||||||
passwordTooLong: "Contraseña demasiado larga",
|
passwordTooLong: 'Contraseña demasiado larga',
|
||||||
passwordsDontMatch: "Las contraseñas no coinciden",
|
passwordsDontMatch: 'Las contraseñas no coinciden',
|
||||||
invalidCredentials: "Credenciales incorrectas",
|
invalidCredentials: 'Credenciales incorrectas',
|
||||||
forgottenPassword: "Contraseña olvidada",
|
forgottenPassword: 'Contraseña olvidada',
|
||||||
invalidEmail: "Email inválido",
|
invalidEmail: 'Email inválido',
|
||||||
accountCreated: "Cuenta creada",
|
accountCreated: 'Cuenta creada',
|
||||||
loggedIn: "Connectado",
|
loggedIn: 'Connectado',
|
||||||
usernameTaken: "Nombre de usuario ya tomado",
|
usernameTaken: 'Nombre de usuario ya tomado',
|
||||||
score: "Puntaje",
|
score: 'Puntaje',
|
||||||
goodNotesInARow: "Buenas notas despues",
|
goodNotesInARow: 'Buenas notas despues',
|
||||||
songsToGetBetter: "Recomendaciones",
|
songsToGetBetter: 'Recomendaciones',
|
||||||
|
|
||||||
// categories
|
// categories
|
||||||
username: "Nombre del usuario",
|
username: 'Nombre del usuario',
|
||||||
password: "Contraseña",
|
password: 'Contraseña',
|
||||||
email: "Correo electrónico",
|
email: 'Correo electrónico',
|
||||||
repeatPassword: "Repita la contraseña",
|
repeatPassword: 'Repita la contraseña',
|
||||||
partition: "Partitura",
|
partition: 'Partitura',
|
||||||
levelProgress: "Nivel progreso",
|
levelProgress: 'Nivel progreso',
|
||||||
signUpBtn: "Inscribirse",
|
signUpBtn: 'Inscribirse',
|
||||||
|
|
||||||
//errors
|
//errors
|
||||||
unknownError: "Error desconocido",
|
unknownError: 'Error desconocido',
|
||||||
errAlrdExst: "Ya existe",
|
errAlrdExst: 'Ya existe',
|
||||||
errIncrrct: "credenciales incorrectas",
|
errIncrrct: 'credenciales incorrectas',
|
||||||
errNoResults: "No se han encontrado resultados",
|
errNoResults: 'No se han encontrado resultados',
|
||||||
|
|
||||||
userProfileFetchError: "Ocurrió un error al obtener su perfil",
|
userProfileFetchError: 'Ocurrió un error al obtener su perfil',
|
||||||
tryAgain: "intentar otra vez",
|
tryAgain: 'intentar otra vez',
|
||||||
|
|
||||||
// Playback messages
|
// Playback messages
|
||||||
missed: "Te perdiste una nota",
|
missed: 'Te perdiste una nota',
|
||||||
perfect: "Perfecto",
|
perfect: 'Perfecto',
|
||||||
great: "Excelente",
|
great: 'Excelente',
|
||||||
good: "Bueno",
|
good: 'Bueno',
|
||||||
bestStreak: "Mejor racha",
|
bestStreak: 'Mejor racha',
|
||||||
precision: "Precisión",
|
precision: 'Precisión',
|
||||||
wrong: "Equivocado",
|
wrong: 'Equivocado',
|
||||||
short: "Un poco demasiado corto",
|
short: 'Un poco demasiado corto',
|
||||||
long: "Un poco demasiado largo",
|
long: 'Un poco demasiado largo',
|
||||||
tooLong: "Demasiado largo",
|
tooLong: 'Demasiado largo',
|
||||||
tooShort: "Demasiado corto",
|
tooShort: 'Demasiado corto',
|
||||||
changePassword: "Cambio de contraseña",
|
changePassword: 'Cambio de contraseña',
|
||||||
oldPassword: "Contraseña anterior",
|
oldPassword: 'Contraseña anterior',
|
||||||
newPassword: "Nueva contraseña",
|
newPassword: 'Nueva contraseña',
|
||||||
confirmNewPassword: "Confirmar nueva contraseña",
|
confirmNewPassword: 'Confirmar nueva contraseña',
|
||||||
submitBtn: "Enviar",
|
submitBtn: 'Enviar',
|
||||||
|
|
||||||
passwordUpdated: "Contraseña actualizada",
|
passwordUpdated: 'Contraseña actualizada',
|
||||||
emailUpdated: "Email actualizado",
|
emailUpdated: 'Email actualizado',
|
||||||
|
|
||||||
SettingsCategoryProfile: "Perfil",
|
SettingsCategoryProfile: 'Perfil',
|
||||||
SettingsCategoryPreferences: "Preferencias",
|
SettingsCategoryPreferences: 'Preferencias',
|
||||||
SettingsCategoryNotifications: "Notificaciones",
|
SettingsCategoryNotifications: 'Notificaciones',
|
||||||
SettingsCategoryPrivacy: "Privacidad",
|
SettingsCategoryPrivacy: 'Privacidad',
|
||||||
SettingsCategorySecurity: "Seguridad",
|
SettingsCategorySecurity: 'Seguridad',
|
||||||
SettingsCategoryEmail: "Email",
|
SettingsCategoryEmail: 'Email',
|
||||||
SettingsCategoryGoogle: "Google",
|
SettingsCategoryGoogle: 'Google',
|
||||||
SettingsCategoryPiano: "Piano",
|
SettingsCategoryPiano: 'Piano',
|
||||||
|
|
||||||
transformGuestToUserExplanations:
|
transformGuestToUserExplanations:
|
||||||
"Actualmente estás conectado como invitado. Puedes crear una cuenta para guardar tus datos y disfrutar de todas las funciones de Chromacase.",
|
'Actualmente estás conectado como invitado. Puedes crear una cuenta para guardar tus datos y disfrutar de todas las funciones de Chromacase.',
|
||||||
SettingsCategoryGuest: "Invitado",
|
SettingsCategoryGuest: 'Invitado',
|
||||||
SettingsNotificationsEmailNotifications: "Email",
|
SettingsNotificationsEmailNotifications: 'Email',
|
||||||
SettingsNotificationsPushNotifications: "Notificaciones push",
|
SettingsNotificationsPushNotifications: 'Notificaciones push',
|
||||||
SettingsNotificationsReleaseAlert: "Alertas de nuevas Sorties",
|
SettingsNotificationsReleaseAlert: 'Alertas de nuevas Sorties',
|
||||||
SettingsNotificationsTrainingReminder: "Recordatorio de entrenamiento",
|
SettingsNotificationsTrainingReminder: 'Recordatorio de entrenamiento',
|
||||||
|
|
||||||
SettingsPreferencesColorblindMode: "Modo daltoniano",
|
SettingsPreferencesColorblindMode: 'Modo daltoniano',
|
||||||
SettingsPreferencesDevice: "Dispositivo",
|
SettingsPreferencesDevice: 'Dispositivo',
|
||||||
SettingsPreferencesDifficulty: "Dificultad",
|
SettingsPreferencesDifficulty: 'Dificultad',
|
||||||
SettingsPreferencesLanguage: "Idioma",
|
SettingsPreferencesLanguage: 'Idioma',
|
||||||
SettingsPreferencesTheme: "Tema",
|
SettingsPreferencesTheme: 'Tema',
|
||||||
SettingsPreferencesMicVolume: "Volumen del micrófono",
|
SettingsPreferencesMicVolume: 'Volumen del micrófono',
|
||||||
|
|
||||||
dataCollection: "Recopilación de datos",
|
dataCollection: 'Recopilación de datos',
|
||||||
recommendations: "Recomendaciones",
|
recommendations: 'Recomendaciones',
|
||||||
customAds: "Anuncios personalizados",
|
customAds: 'Anuncios personalizados',
|
||||||
|
|
||||||
NoAssociatedEmail: "No hay correo electrónico asociado",
|
NoAssociatedEmail: 'No hay correo electrónico asociado',
|
||||||
nbGamesPlayed: "Partidos jugados",
|
nbGamesPlayed: 'Partidos jugados',
|
||||||
XPDescription:
|
XPDescription:
|
||||||
"XP se gana jugando canciones. Cuanto más juegas, más XP ganas. Cuanto más XP tienes, más subes de nivel.",
|
'XP se gana jugando canciones. Cuanto más juegas, más XP ganas. Cuanto más XP tienes, más subes de nivel.',
|
||||||
userCreatedAt: "Cuenta creada el",
|
userCreatedAt: 'Cuenta creada el',
|
||||||
premiumAccount: "Cuenta premium",
|
premiumAccount: 'Cuenta premium',
|
||||||
yes: "Sí",
|
yes: 'Sí',
|
||||||
no: "No",
|
no: 'No',
|
||||||
|
|
||||||
Attention: "Atención",
|
Attention: 'Atención',
|
||||||
YouAreCurrentlyConnectedWithAGuestAccountWarning:
|
YouAreCurrentlyConnectedWithAGuestAccountWarning:
|
||||||
"Actualmente estás conectado como invitado. La desconexión resultará en la pérdida de datos. Puedes crear una cuenta para guardar tus datos.",
|
'Actualmente estás conectado como invitado. La desconexión resultará en la pérdida de datos. Puedes crear una cuenta para guardar tus datos.',
|
||||||
|
|
||||||
recentSearches: "Búsquedas recientes",
|
recentSearches: 'Búsquedas recientes',
|
||||||
noRecentSearches: "No hay búsquedas recientes",
|
noRecentSearches: 'No hay búsquedas recientes',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,32 +1,30 @@
|
|||||||
import { en, fr, sp } from './Translations';
|
import { en, fr, sp } from './Translations';
|
||||||
import i18n from "i18next";
|
import i18n from 'i18next';
|
||||||
import { initReactI18next } from "react-i18next";
|
import { initReactI18next } from 'react-i18next';
|
||||||
import Translate from '../components/Translate';
|
import Translate from '../components/Translate';
|
||||||
|
|
||||||
export type AvailableLanguages = 'en' | 'fr' | 'sp';
|
export type AvailableLanguages = 'en' | 'fr' | 'sp';
|
||||||
export const DefaultLanguage: AvailableLanguages = 'en';
|
export const DefaultLanguage: AvailableLanguages = 'en';
|
||||||
|
|
||||||
i18n
|
i18n.use(initReactI18next).init({
|
||||||
.use(initReactI18next)
|
compatibilityJSON: 'v3',
|
||||||
.init({
|
resources: {
|
||||||
compatibilityJSON: 'v3',
|
en: {
|
||||||
resources: {
|
translation: en,
|
||||||
en: {
|
|
||||||
translation: en
|
|
||||||
},
|
|
||||||
fr: {
|
|
||||||
translation: fr
|
|
||||||
},
|
|
||||||
sp: {
|
|
||||||
translation: sp
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
lng: DefaultLanguage,
|
fr: {
|
||||||
fallbackLng: 'en',
|
translation: fr,
|
||||||
interpolation: {
|
},
|
||||||
escapeValue: false
|
sp: {
|
||||||
}
|
translation: sp,
|
||||||
});
|
},
|
||||||
|
},
|
||||||
|
lng: DefaultLanguage,
|
||||||
|
fallbackLng: 'en',
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export default i18n;
|
export default i18n;
|
||||||
|
|
||||||
@@ -37,8 +35,8 @@ export default i18n;
|
|||||||
*/
|
*/
|
||||||
export const translate = (key: keyof typeof en, language?: AvailableLanguages) => {
|
export const translate = (key: keyof typeof en, language?: AvailableLanguages) => {
|
||||||
return i18n.t(key, {
|
return i18n.t(key, {
|
||||||
lng: language
|
lng: language,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export { Translate };
|
export { Translate };
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import Model from "./Model";
|
import Model from './Model';
|
||||||
|
|
||||||
interface Album extends Model {
|
interface Album extends Model {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Album;
|
export default Album;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import Model from "./Model";
|
import Model from './Model';
|
||||||
|
|
||||||
interface Artist extends Model {
|
interface Artist extends Model {
|
||||||
name: string;
|
name: string;
|
||||||
picture?: string;
|
picture?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Artist;
|
export default Artist;
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
type AuthToken = string;
|
type AuthToken = string;
|
||||||
|
|
||||||
export default AuthToken;
|
export default AuthToken;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Skill from "./Skill";
|
import Skill from './Skill';
|
||||||
import Model from "./Model";
|
import Model from './Model';
|
||||||
|
|
||||||
interface Chapter extends Model {
|
interface Chapter extends Model {
|
||||||
start: number;
|
start: number;
|
||||||
@@ -8,7 +8,7 @@ interface Chapter extends Model {
|
|||||||
name: string;
|
name: string;
|
||||||
type: 'chorus' | 'verse';
|
type: 'chorus' | 'verse';
|
||||||
key_aspect: Skill;
|
key_aspect: Skill;
|
||||||
difficulty: number
|
difficulty: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Chapter;
|
export default Chapter;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import Model from "./Model";
|
import Model from './Model';
|
||||||
|
|
||||||
interface Genre extends Model {
|
interface Genre extends Model {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Genre;
|
export default Genre;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Skill from "./Skill";
|
import Skill from './Skill';
|
||||||
import Model from "./Model";
|
import Model from './Model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Lesson is an exercice that the user can try to practice a skill
|
* A Lesson is an exercice that the user can try to practice a skill
|
||||||
@@ -8,7 +8,7 @@ interface Lesson extends Model {
|
|||||||
/**
|
/**
|
||||||
* The title of the lesson
|
* The title of the lesson
|
||||||
*/
|
*/
|
||||||
title: string,
|
title: string;
|
||||||
/**
|
/**
|
||||||
* Short description of the lesson
|
* Short description of the lesson
|
||||||
*/
|
*/
|
||||||
@@ -23,4 +23,4 @@ interface Lesson extends Model {
|
|||||||
mainSkill: Skill;
|
mainSkill: Skill;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Lesson;
|
export default Lesson;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
interface LessonHistory {
|
interface LessonHistory {
|
||||||
lessonId: number;
|
lessonId: number;
|
||||||
userId: number
|
userId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LessonHistory;
|
export default LessonHistory;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
export default interface LocalSettings {
|
export default interface LocalSettings {
|
||||||
deviceId: number,
|
deviceId: number;
|
||||||
micVolume: number,
|
micVolume: number;
|
||||||
colorScheme: 'light' | 'dark' | 'system',
|
colorScheme: 'light' | 'dark' | 'system';
|
||||||
lang: 'fr' | 'en' | 'sp',
|
lang: 'fr' | 'en' | 'sp';
|
||||||
difficulty: 'beg' | 'inter' | 'pro',
|
difficulty: 'beg' | 'inter' | 'pro';
|
||||||
colorBlind: boolean,
|
colorBlind: boolean;
|
||||||
customAds: boolean,
|
customAds: boolean;
|
||||||
dataCollection: boolean
|
dataCollection: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ interface Model {
|
|||||||
id: number;
|
id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Model;
|
export default Model;
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
export enum Note {
|
export enum Note {
|
||||||
"C",
|
'C',
|
||||||
"C#",
|
'C#',
|
||||||
"D",
|
'D',
|
||||||
"D#",
|
'D#',
|
||||||
"E",
|
'E',
|
||||||
"F",
|
'F',
|
||||||
"F#",
|
'F#',
|
||||||
"G",
|
'G',
|
||||||
"G#",
|
'G#',
|
||||||
"A",
|
'A',
|
||||||
"A#",
|
'A#',
|
||||||
"B",
|
'B',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum NoteNameBehavior {
|
export enum NoteNameBehavior {
|
||||||
"always",
|
'always',
|
||||||
"onpress",
|
'onpress',
|
||||||
"onhighlight",
|
'onhighlight',
|
||||||
"onhover",
|
'onhover',
|
||||||
"never",
|
'never',
|
||||||
}
|
}
|
||||||
export enum KeyPressStyle {
|
export enum KeyPressStyle {
|
||||||
"subtle",
|
'subtle',
|
||||||
"vivid",
|
'vivid',
|
||||||
}
|
}
|
||||||
export type HighlightedKey = {
|
export type HighlightedKey = {
|
||||||
key: PianoKey;
|
key: PianoKey;
|
||||||
@@ -40,54 +40,54 @@ export class PianoKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public toString = () => {
|
public toString = () => {
|
||||||
return (this.note as unknown as string) + (this.octave || "");
|
return (this.note as unknown as string) + (this.octave || '');
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const strToKey = (str: string): PianoKey => {
|
export const strToKey = (str: string): PianoKey => {
|
||||||
let note: Note;
|
let note: Note;
|
||||||
const isSimpleNote = str[1]! >= "0" && str[1]! <= "9";
|
const isSimpleNote = str[1]! >= '0' && str[1]! <= '9';
|
||||||
// later we need to support different annotations
|
// later we need to support different annotations
|
||||||
|
|
||||||
switch (isSimpleNote ? str[0] : str.substring(0, 2)) {
|
switch (isSimpleNote ? str[0] : str.substring(0, 2)) {
|
||||||
case "E":
|
case 'E':
|
||||||
note = Note.E;
|
note = Note.E;
|
||||||
break;
|
break;
|
||||||
case "B":
|
case 'B':
|
||||||
note = Note.B;
|
note = Note.B;
|
||||||
break;
|
break;
|
||||||
case "C":
|
case 'C':
|
||||||
note = Note.C;
|
note = Note.C;
|
||||||
break;
|
break;
|
||||||
case "D":
|
case 'D':
|
||||||
note = Note.D;
|
note = Note.D;
|
||||||
break;
|
break;
|
||||||
case "F":
|
case 'F':
|
||||||
note = Note.F;
|
note = Note.F;
|
||||||
break;
|
break;
|
||||||
case "G":
|
case 'G':
|
||||||
note = Note.G;
|
note = Note.G;
|
||||||
break;
|
break;
|
||||||
case "A":
|
case 'A':
|
||||||
note = Note.A;
|
note = Note.A;
|
||||||
break;
|
break;
|
||||||
case "C#":
|
case 'C#':
|
||||||
note = Note["C#"];
|
note = Note['C#'];
|
||||||
break;
|
break;
|
||||||
case "D#":
|
case 'D#':
|
||||||
note = Note["D#"];
|
note = Note['D#'];
|
||||||
break;
|
break;
|
||||||
case "F#":
|
case 'F#':
|
||||||
note = Note["F#"];
|
note = Note['F#'];
|
||||||
break;
|
break;
|
||||||
case "G#":
|
case 'G#':
|
||||||
note = Note["G#"];
|
note = Note['G#'];
|
||||||
break;
|
break;
|
||||||
case "A#":
|
case 'A#':
|
||||||
note = Note["A#"];
|
note = Note['A#'];
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error("Invalid note name");
|
throw new Error('Invalid note name');
|
||||||
}
|
}
|
||||||
if ((isSimpleNote && !str[1]) || (!isSimpleNote && str.length < 3)) {
|
if ((isSimpleNote && !str[1]) || (!isSimpleNote && str.length < 3)) {
|
||||||
return new PianoKey(note);
|
return new PianoKey(note);
|
||||||
@@ -96,47 +96,47 @@ export const strToKey = (str: string): PianoKey => {
|
|||||||
return new PianoKey(note, octave);
|
return new PianoKey(note, octave);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const keyToStr = (key: PianoKey, showOctave: boolean = true): string => {
|
export const keyToStr = (key: PianoKey, showOctave = true): string => {
|
||||||
let s = "";
|
let s = '';
|
||||||
switch (key.note) {
|
switch (key.note) {
|
||||||
case Note.C:
|
case Note.C:
|
||||||
s += "C";
|
s += 'C';
|
||||||
break;
|
break;
|
||||||
case Note.D:
|
case Note.D:
|
||||||
s += "D";
|
s += 'D';
|
||||||
break;
|
break;
|
||||||
case Note.E:
|
case Note.E:
|
||||||
s += "E";
|
s += 'E';
|
||||||
break;
|
break;
|
||||||
case Note.F:
|
case Note.F:
|
||||||
s += "F";
|
s += 'F';
|
||||||
break;
|
break;
|
||||||
case Note.G:
|
case Note.G:
|
||||||
s += "G";
|
s += 'G';
|
||||||
break;
|
break;
|
||||||
case Note.A:
|
case Note.A:
|
||||||
s += "A";
|
s += 'A';
|
||||||
break;
|
break;
|
||||||
case Note.B:
|
case Note.B:
|
||||||
s += "B";
|
s += 'B';
|
||||||
break;
|
break;
|
||||||
case Note["C#"]:
|
case Note['C#']:
|
||||||
s += "C#";
|
s += 'C#';
|
||||||
break;
|
break;
|
||||||
case Note["D#"]:
|
case Note['D#']:
|
||||||
s += "D#";
|
s += 'D#';
|
||||||
break;
|
break;
|
||||||
case Note["F#"]:
|
case Note['F#']:
|
||||||
s += "F#";
|
s += 'F#';
|
||||||
break;
|
break;
|
||||||
case Note["G#"]:
|
case Note['G#']:
|
||||||
s += "G#";
|
s += 'G#';
|
||||||
break;
|
break;
|
||||||
case Note["A#"]:
|
case Note['A#']:
|
||||||
s += "A#";
|
s += 'A#';
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error("Invalid note name");
|
throw new Error('Invalid note name');
|
||||||
}
|
}
|
||||||
if (showOctave && key.octave) {
|
if (showOctave && key.octave) {
|
||||||
s += key.octave;
|
s += key.octave;
|
||||||
@@ -146,25 +146,25 @@ export const keyToStr = (key: PianoKey, showOctave: boolean = true): string => {
|
|||||||
|
|
||||||
export const isAccidental = (key: PianoKey): boolean => {
|
export const isAccidental = (key: PianoKey): boolean => {
|
||||||
return (
|
return (
|
||||||
key.note === Note["C#"] ||
|
key.note === Note['C#'] ||
|
||||||
key.note === Note["D#"] ||
|
key.note === Note['D#'] ||
|
||||||
key.note === Note["F#"] ||
|
key.note === Note['F#'] ||
|
||||||
key.note === Note["G#"] ||
|
key.note === Note['G#'] ||
|
||||||
key.note === Note["A#"]
|
key.note === Note['A#']
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const octaveKeys: Array<PianoKey> = [
|
export const octaveKeys: Array<PianoKey> = [
|
||||||
new PianoKey(Note.C),
|
new PianoKey(Note.C),
|
||||||
new PianoKey(Note["C#"]),
|
new PianoKey(Note['C#']),
|
||||||
new PianoKey(Note.D),
|
new PianoKey(Note.D),
|
||||||
new PianoKey(Note["D#"]),
|
new PianoKey(Note['D#']),
|
||||||
new PianoKey(Note.E),
|
new PianoKey(Note.E),
|
||||||
new PianoKey(Note.F),
|
new PianoKey(Note.F),
|
||||||
new PianoKey(Note["F#"]),
|
new PianoKey(Note['F#']),
|
||||||
new PianoKey(Note.G),
|
new PianoKey(Note.G),
|
||||||
new PianoKey(Note["G#"]),
|
new PianoKey(Note['G#']),
|
||||||
new PianoKey(Note.A),
|
new PianoKey(Note.A),
|
||||||
new PianoKey(Note["A#"]),
|
new PianoKey(Note['A#']),
|
||||||
new PianoKey(Note.B),
|
new PianoKey(Note.B),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import Model from "./Model";
|
import Model from './Model';
|
||||||
|
|
||||||
interface SearchHistory extends Model {
|
interface SearchHistory extends Model {
|
||||||
query: string;
|
query: string;
|
||||||
type: "song" | "artist" | "album" | "genre";
|
type: 'song' | 'artist' | 'album' | 'genre';
|
||||||
userId: number;
|
userId: number;
|
||||||
timestamp: Date;
|
timestamp: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SearchHistory;
|
export default SearchHistory;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
type Skill = 'rhythm'
|
type Skill =
|
||||||
|
| 'rhythm'
|
||||||
| 'two-hands'
|
| 'two-hands'
|
||||||
| 'combos'
|
| 'combos'
|
||||||
| 'arpeggio'
|
| 'arpeggio'
|
||||||
@@ -9,7 +10,6 @@ type Skill = 'rhythm'
|
|||||||
| 'chord-complexity'
|
| 'chord-complexity'
|
||||||
| 'chord-timing'
|
| 'chord-timing'
|
||||||
| 'pedal'
|
| 'pedal'
|
||||||
| 'precision'
|
| 'precision';
|
||||||
|
|
||||||
|
export default Skill;
|
||||||
export default Skill;
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Model from "./Model";
|
import Model from './Model';
|
||||||
import SongDetails from "./SongDetails";
|
import SongDetails from './SongDetails';
|
||||||
import Artist from "./Artist";
|
import Artist from './Artist';
|
||||||
|
|
||||||
interface Song extends Model {
|
interface Song extends Model {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -16,4 +16,4 @@ export interface SongWithArtist extends Song {
|
|||||||
artist: Artist;
|
artist: Artist;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Song;
|
export default Song;
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
interface SongDetails {
|
interface SongDetails {
|
||||||
length: number,
|
length: number;
|
||||||
rhythm: number,
|
rhythm: number;
|
||||||
arppegio: number,
|
arppegio: number;
|
||||||
distance: number,
|
distance: number;
|
||||||
lefthand: number,
|
lefthand: number;
|
||||||
righthand: number,
|
righthand: number;
|
||||||
twohands: number,
|
twohands: number;
|
||||||
notecombo: number,
|
notecombo: number;
|
||||||
precision: number,
|
precision: number;
|
||||||
pedalpoint: number,
|
pedalpoint: number;
|
||||||
chordtiming: number,
|
chordtiming: number;
|
||||||
leadheadchange: number,
|
leadheadchange: number;
|
||||||
chordcomplexity: number,
|
chordcomplexity: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SongDetails;
|
export default SongDetails;
|
||||||
|
|||||||
@@ -5,4 +5,4 @@ interface SongHistory {
|
|||||||
difficulties: JSON;
|
difficulties: JSON;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SongHistory;
|
export default SongHistory;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import UserData from "./UserData";
|
import UserData from './UserData';
|
||||||
import Model from "./Model";
|
import Model from './Model';
|
||||||
import UserSettings from "./UserSettings";
|
import UserSettings from './UserSettings';
|
||||||
|
|
||||||
interface User extends Model {
|
interface User extends Model {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -11,4 +11,4 @@ interface User extends Model {
|
|||||||
settings: UserSettings;
|
settings: UserSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default User;
|
export default User;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
interface UserData {
|
interface UserData {
|
||||||
gamesPlayed: number;
|
gamesPlayed: number;
|
||||||
xp: number;
|
xp: number;
|
||||||
avatar: string | undefined;
|
avatar: string | undefined;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default UserData;
|
export default UserData;
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
interface UserSettings {
|
interface UserSettings {
|
||||||
notifications: {
|
notifications: {
|
||||||
pushNotif: boolean,
|
pushNotif: boolean;
|
||||||
emailNotif: boolean,
|
emailNotif: boolean;
|
||||||
trainNotif: boolean,
|
trainNotif: boolean;
|
||||||
newSongNotif: boolean
|
newSongNotif: boolean;
|
||||||
},
|
};
|
||||||
weeklyReport: boolean,
|
weeklyReport: boolean;
|
||||||
leaderBoard: boolean,
|
leaderBoard: boolean;
|
||||||
showActivity: boolean,
|
showActivity: boolean;
|
||||||
recommendations: boolean
|
recommendations: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default UserSettings
|
export default UserSettings;
|
||||||
|
|||||||
@@ -1,102 +1,112 @@
|
|||||||
{
|
{
|
||||||
"name": "chromacase",
|
"name": "chromacase",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "node_modules/expo/AppEntry.js",
|
"main": "node_modules/expo/AppEntry.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "expo start",
|
"start": "expo start",
|
||||||
"android": "expo start --android",
|
"android": "expo start --android",
|
||||||
"ios": "expo start --ios",
|
"ios": "expo start --ios",
|
||||||
"web": "expo start --web",
|
"web": "expo start --web",
|
||||||
"eject": "expo eject",
|
"eject": "expo eject",
|
||||||
"test": "jest -i",
|
"pretty:check": "prettier --check",
|
||||||
"test:cov": "jest -i --coverage",
|
"pretty:write": "prettier --write",
|
||||||
"test:watch": "jest -i --watch",
|
"lint": "eslint .",
|
||||||
"storybook": "start-storybook -p 6006",
|
"test": "jest -i",
|
||||||
"build-storybook": "build-storybook",
|
"test:cov": "jest -i --coverage",
|
||||||
"chromatic": "chromatic --exit-zero-on-changes"
|
"test:watch": "jest -i --watch",
|
||||||
},
|
"storybook": "start-storybook -p 6006",
|
||||||
"dependencies": {
|
"build-storybook": "build-storybook",
|
||||||
"@expo/vector-icons": "^13.0.0",
|
"chromatic": "chromatic --exit-zero-on-changes"
|
||||||
"@motiz88/react-native-midi": "^0.0.5",
|
},
|
||||||
"@react-native-async-storage/async-storage": "~1.17.3",
|
"dependencies": {
|
||||||
"@react-navigation/native": "^6.0.11",
|
"@expo/vector-icons": "^13.0.0",
|
||||||
"@react-navigation/native-stack": "^6.7.0",
|
"@motiz88/react-native-midi": "^0.0.5",
|
||||||
"@reduxjs/toolkit": "^1.8.3",
|
"@react-native-async-storage/async-storage": "~1.17.3",
|
||||||
"@tanstack/react-query": "^4.2.3",
|
"@react-navigation/native": "^6.0.11",
|
||||||
"@types/jest": "^28.1.4",
|
"@react-navigation/native-stack": "^6.7.0",
|
||||||
"@types/react-dom": "~18.0.8",
|
"@reduxjs/toolkit": "^1.8.3",
|
||||||
"@types/react-query": "^1.2.9",
|
"@tanstack/react-query": "^4.2.3",
|
||||||
"@types/react-test-renderer": "^18.0.0",
|
"@types/jest": "^28.1.4",
|
||||||
"add": "^2.0.6",
|
"@types/react-dom": "~18.0.8",
|
||||||
"expo": "^47.0.8",
|
"@types/react-query": "^1.2.9",
|
||||||
"expo-asset": "~8.7.0",
|
"@types/react-test-renderer": "^18.0.0",
|
||||||
"expo-dev-client": "~2.0.1",
|
"add": "^2.0.6",
|
||||||
"expo-linking": "~3.3.1",
|
"expo": "^47.0.8",
|
||||||
"expo-screen-orientation": "~5.0.1",
|
"expo-asset": "~8.7.0",
|
||||||
"expo-secure-store": "~12.0.0",
|
"expo-dev-client": "~2.0.1",
|
||||||
"expo-splash-screen": "~0.17.5",
|
"expo-linking": "~3.3.1",
|
||||||
"expo-status-bar": "~1.4.2",
|
"expo-screen-orientation": "~5.0.1",
|
||||||
"format-duration": "^2.0.0",
|
"expo-secure-store": "~12.0.0",
|
||||||
"i18next": "^21.8.16",
|
"expo-splash-screen": "~0.17.5",
|
||||||
"install": "^0.13.0",
|
"expo-status-bar": "~1.4.2",
|
||||||
"jest": "^26.6.3",
|
"format-duration": "^2.0.0",
|
||||||
"jest-expo": "^45.0.1",
|
"i18next": "^21.8.16",
|
||||||
"midi-player-js": "^2.0.16",
|
"install": "^0.13.0",
|
||||||
"moti": "^0.22.0",
|
"jest": "^26.6.3",
|
||||||
"native-base": "^3.4.17",
|
"jest-expo": "^45.0.1",
|
||||||
"opensheetmusicdisplay": "^1.7.5",
|
"midi-player-js": "^2.0.16",
|
||||||
"react": "18.1.0",
|
"moti": "^0.22.0",
|
||||||
"react-dom": "18.1.0",
|
"native-base": "^3.4.17",
|
||||||
"react-i18next": "^11.18.3",
|
"opensheetmusicdisplay": "^1.7.5",
|
||||||
"react-native": "0.70.5",
|
"react": "18.1.0",
|
||||||
"react-native-paper": "^4.12.5",
|
"react-dom": "18.1.0",
|
||||||
"react-native-reanimated": "~2.12.0",
|
"react-i18next": "^11.18.3",
|
||||||
"react-native-safe-area-context": "4.4.1",
|
"react-native": "0.70.5",
|
||||||
"react-native-screens": "~3.18.0",
|
"react-native-paper": "^4.12.5",
|
||||||
"react-native-super-grid": "^4.6.1",
|
"react-native-reanimated": "~2.12.0",
|
||||||
"react-native-svg": "13.4.0",
|
"react-native-safe-area-context": "4.4.1",
|
||||||
"react-native-testing-library": "^6.0.0",
|
"react-native-screens": "~3.18.0",
|
||||||
"react-native-url-polyfill": "^1.3.0",
|
"react-native-super-grid": "^4.6.1",
|
||||||
"react-native-web": "~0.18.7",
|
"react-native-svg": "13.4.0",
|
||||||
"react-redux": "^8.0.2",
|
"react-native-testing-library": "^6.0.0",
|
||||||
"react-timer-hook": "^3.0.5",
|
"react-native-url-polyfill": "^1.3.0",
|
||||||
"react-use-precision-timer": "^3.3.1",
|
"react-native-web": "~0.18.7",
|
||||||
"redux-persist": "^6.0.0",
|
"react-redux": "^8.0.2",
|
||||||
"soundfont-player": "^0.12.0",
|
"react-timer-hook": "^3.0.5",
|
||||||
"standardized-audio-context": "^25.3.51",
|
"react-use-precision-timer": "^3.3.1",
|
||||||
"type-fest": "^3.6.0",
|
"redux-persist": "^6.0.0",
|
||||||
"yup": "^0.32.11"
|
"soundfont-player": "^0.12.0",
|
||||||
},
|
"standardized-audio-context": "^25.3.51",
|
||||||
"devDependencies": {
|
"type-fest": "^3.6.0",
|
||||||
"@babel/core": "^7.19.3",
|
"yup": "^0.32.11"
|
||||||
"@babel/plugin-proposal-export-namespace-from": "^7.18.9",
|
},
|
||||||
"@expo/webpack-config": "^0.17.4",
|
"devDependencies": {
|
||||||
"@storybook/addon-actions": "^6.5.15",
|
"@babel/core": "^7.19.3",
|
||||||
"@storybook/addon-essentials": "^6.5.15",
|
"@babel/plugin-proposal-export-namespace-from": "^7.18.9",
|
||||||
"@storybook/addon-interactions": "^6.5.15",
|
"@expo/webpack-config": "^0.17.4",
|
||||||
"@storybook/addon-links": "^6.5.15",
|
"@storybook/addon-actions": "^6.5.15",
|
||||||
"@storybook/builder-webpack4": "^6.5.15",
|
"@storybook/addon-essentials": "^6.5.15",
|
||||||
"@storybook/manager-webpack4": "^6.5.15",
|
"@storybook/addon-interactions": "^6.5.15",
|
||||||
"@storybook/react": "^6.5.15",
|
"@storybook/addon-links": "^6.5.15",
|
||||||
"@storybook/testing-library": "^0.0.13",
|
"@storybook/builder-webpack4": "^6.5.15",
|
||||||
"@testing-library/react-native": "^11.0.0",
|
"@storybook/manager-webpack4": "^6.5.15",
|
||||||
"@types/node": "^18.11.8",
|
"@storybook/react": "^6.5.15",
|
||||||
"@types/react": "~18.0.24",
|
"@storybook/testing-library": "^0.0.13",
|
||||||
"@types/react-native": "~0.70.6",
|
"@testing-library/react-native": "^11.0.0",
|
||||||
"@types/react-navigation": "^3.4.0",
|
"@types/node": "^18.11.8",
|
||||||
"babel-loader": "^8.3.0",
|
"@types/react": "~18.0.24",
|
||||||
"babel-plugin-transform-inline-environment-variables": "^0.4.4",
|
"@types/react-native": "~0.70.6",
|
||||||
"chromatic": "^6.14.0",
|
"@types/react-navigation": "^3.4.0",
|
||||||
"react-test-renderer": "17.0.2",
|
"babel-loader": "^8.3.0",
|
||||||
"typescript": "^4.6.3"
|
"babel-plugin-transform-inline-environment-variables": "^0.4.4",
|
||||||
},
|
"chromatic": "^6.14.0",
|
||||||
"private": true,
|
"@typescript-eslint/eslint-plugin": "^5.43.0",
|
||||||
"jest": {
|
"@typescript-eslint/parser": "^5.0.0",
|
||||||
"preset": "jest-expo",
|
"eslint": "^8.42.0",
|
||||||
"transformIgnorePatterns": [
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg)"
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
]
|
"eslint-plugin-react": "^7.31.11",
|
||||||
},
|
"prettier": "^2.8.8",
|
||||||
"readme": "ERROR: No README data found!",
|
"react-test-renderer": "17.0.2",
|
||||||
"_id": "chromacase@1.0.0"
|
"typescript": "^4.6.3"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"jest": {
|
||||||
|
"preset": "jest-expo",
|
||||||
|
"transformIgnorePatterns": [
|
||||||
|
"node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"readme": "ERROR: No README data found!",
|
||||||
|
"_id": "chromacase@1.0.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { AvailableLanguages, DefaultLanguage } from "../i18n/i18n";
|
import { AvailableLanguages, DefaultLanguage } from '../i18n/i18n';
|
||||||
|
|
||||||
|
|
||||||
export const languageSlice = createSlice({
|
export const languageSlice = createSlice({
|
||||||
name: 'language',
|
name: 'language',
|
||||||
initialState: {
|
initialState: {
|
||||||
value: DefaultLanguage
|
value: DefaultLanguage,
|
||||||
},
|
},
|
||||||
reducers: {
|
reducers: {
|
||||||
useLanguage: (state, action: PayloadAction<AvailableLanguages>) => {
|
useLanguage: (state, action: PayloadAction<AvailableLanguages>) => {
|
||||||
@@ -17,4 +16,4 @@ export const languageSlice = createSlice({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
export const { useLanguage, resetLanguage } = languageSlice.actions;
|
export const { useLanguage, resetLanguage } = languageSlice.actions;
|
||||||
export default languageSlice.reducer;
|
export default languageSlice.reducer;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
import LocalSettings from "../models/LocalSettings";
|
import LocalSettings from '../models/LocalSettings';
|
||||||
|
|
||||||
export const settingsSlice = createSlice({
|
export const settingsSlice = createSlice({
|
||||||
name: 'settings',
|
name: 'settings',
|
||||||
@@ -12,14 +12,14 @@ export const settingsSlice = createSlice({
|
|||||||
difficulty: 'beg',
|
difficulty: 'beg',
|
||||||
colorBlind: false,
|
colorBlind: false,
|
||||||
customAds: true,
|
customAds: true,
|
||||||
dataCollection: true
|
dataCollection: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
reducers: {
|
reducers: {
|
||||||
updateSettings: (state, action: PayloadAction<Partial<LocalSettings>>) => {
|
updateSettings: (state, action: PayloadAction<Partial<LocalSettings>>) => {
|
||||||
state.local = { ...state.local, ...action.payload };
|
state.local = { ...state.local, ...action.payload };
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
export const { updateSettings } = settingsSlice.actions;
|
export const { updateSettings } = settingsSlice.actions;
|
||||||
export default settingsSlice.reducer;
|
export default settingsSlice.reducer;
|
||||||
|
|||||||
@@ -2,42 +2,55 @@ import userReducer from '../state/UserSlice';
|
|||||||
import settingsReduder from './SettingsSlice';
|
import settingsReduder from './SettingsSlice';
|
||||||
import { StateFromReducersMapObject, configureStore } from '@reduxjs/toolkit';
|
import { StateFromReducersMapObject, configureStore } from '@reduxjs/toolkit';
|
||||||
import languageReducer from './LanguageSlice';
|
import languageReducer from './LanguageSlice';
|
||||||
import { TypedUseSelectorHook, useDispatch as reduxDispatch, useSelector as reduxSelector } from 'react-redux'
|
import {
|
||||||
import { persistStore, persistCombineReducers, FLUSH, PAUSE, PERSIST, PURGE, REGISTER, REHYDRATE } from "redux-persist";
|
TypedUseSelectorHook,
|
||||||
|
useDispatch as reduxDispatch,
|
||||||
|
useSelector as reduxSelector,
|
||||||
|
} from 'react-redux';
|
||||||
|
import {
|
||||||
|
persistStore,
|
||||||
|
persistCombineReducers,
|
||||||
|
FLUSH,
|
||||||
|
PAUSE,
|
||||||
|
PERSIST,
|
||||||
|
PURGE,
|
||||||
|
REGISTER,
|
||||||
|
REHYDRATE,
|
||||||
|
} from 'redux-persist';
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
import { CurriedGetDefaultMiddleware } from '@reduxjs/toolkit/dist/getDefaultMiddleware';
|
import { CurriedGetDefaultMiddleware } from '@reduxjs/toolkit/dist/getDefaultMiddleware';
|
||||||
import { PersistPartial } from 'redux-persist/es/persistReducer';
|
import { PersistPartial } from 'redux-persist/es/persistReducer';
|
||||||
|
|
||||||
const persistConfig = {
|
const persistConfig = {
|
||||||
key: 'root',
|
key: 'root',
|
||||||
storage: AsyncStorage
|
storage: AsyncStorage,
|
||||||
}
|
};
|
||||||
const reducers = {
|
const reducers = {
|
||||||
user: userReducer,
|
user: userReducer,
|
||||||
language: languageReducer,
|
language: languageReducer,
|
||||||
settings: settingsReduder
|
settings: settingsReduder,
|
||||||
}
|
};
|
||||||
|
|
||||||
type State = StateFromReducersMapObject<typeof reducers>;
|
type State = StateFromReducersMapObject<typeof reducers>;
|
||||||
|
|
||||||
let store = configureStore({
|
const store = configureStore({
|
||||||
reducer: persistCombineReducers(persistConfig, reducers),
|
reducer: persistCombineReducers(persistConfig, reducers),
|
||||||
middleware: (getDefaultMiddleware: CurriedGetDefaultMiddleware<State & PersistPartial>) =>
|
middleware: (getDefaultMiddleware: CurriedGetDefaultMiddleware<State & PersistPartial>) =>
|
||||||
getDefaultMiddleware({
|
getDefaultMiddleware({
|
||||||
serializableCheck: {
|
serializableCheck: {
|
||||||
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
|
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
})
|
});
|
||||||
let persistor = persistStore(store);
|
const persistor = persistStore(store);
|
||||||
|
|
||||||
// Infer the `RootState` and `AppDispatch` types from the store itself
|
// Infer the `RootState` and `AppDispatch` types from the store itself
|
||||||
export type RootState = ReturnType<typeof store.getState>
|
export type RootState = ReturnType<typeof store.getState>;
|
||||||
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
|
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
|
||||||
export type AppDispatch = typeof store.dispatch;
|
export type AppDispatch = typeof store.dispatch;
|
||||||
// Use throughout your app instead of plain `useDispatch` and `useSelector`
|
// Use throughout your app instead of plain `useDispatch` and `useSelector`
|
||||||
export const useDispatch: () => AppDispatch = reduxDispatch
|
export const useDispatch: () => AppDispatch = reduxDispatch;
|
||||||
export const useSelector: TypedUseSelectorHook<RootState> = reduxSelector
|
export const useSelector: TypedUseSelectorHook<RootState> = reduxSelector;
|
||||||
|
|
||||||
export default store
|
export default store;
|
||||||
export { persistor }
|
export { persistor };
|
||||||
|
|||||||
@@ -2,18 +2,18 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
|||||||
import { AccessToken } from '../API';
|
import { AccessToken } from '../API';
|
||||||
|
|
||||||
export const userSlice = createSlice({
|
export const userSlice = createSlice({
|
||||||
name: 'user',
|
name: 'user',
|
||||||
initialState: {
|
initialState: {
|
||||||
accessToken: undefined as AccessToken | undefined
|
accessToken: undefined as AccessToken | undefined,
|
||||||
},
|
},
|
||||||
reducers: {
|
reducers: {
|
||||||
setAccessToken: (state, action: PayloadAction<AccessToken>) => {
|
setAccessToken: (state, action: PayloadAction<AccessToken>) => {
|
||||||
state.accessToken = action.payload;
|
state.accessToken = action.payload;
|
||||||
},
|
},
|
||||||
unsetAccessToken: (state) => {
|
unsetAccessToken: (state) => {
|
||||||
state.accessToken = undefined;
|
state.accessToken = undefined;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
export const { setAccessToken, unsetAccessToken } = userSlice.actions;
|
export const { setAccessToken, unsetAccessToken } = userSlice.actions;
|
||||||
export default userSlice.reducer;
|
export default userSlice.reducer;
|
||||||
|
|||||||
@@ -1,107 +1,117 @@
|
|||||||
{
|
{
|
||||||
"extends": "expo/tsconfig.base",
|
"extends": "expo/tsconfig.base",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||||
|
|
||||||
/* Projects */
|
/* Projects */
|
||||||
// "incremental": true, /* Enable incremental compilation */
|
// "incremental": true, /* Enable incremental compilation */
|
||||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||||
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
|
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
|
||||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
|
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
|
||||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||||
|
|
||||||
/* Language and Environment */
|
/* Language and Environment */
|
||||||
"target": "esnext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
"target": "esnext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
||||||
"lib": ["es2019", "DOM"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
"lib": [
|
||||||
"jsx": "react-native", /* Specify what JSX code is generated. */
|
"es2019",
|
||||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
"DOM"
|
||||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
|
||||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
|
"jsx": "react-native" /* Specify what JSX code is generated. */,
|
||||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
|
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||||
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
|
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
|
||||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
|
||||||
|
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
|
||||||
|
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||||
|
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||||
|
|
||||||
/* Modules */
|
/* Modules */
|
||||||
"module": "commonjs", /* Specify what module code is generated. */
|
"module": "commonjs" /* Specify what module code is generated. */,
|
||||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||||
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
"moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
|
||||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
|
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
|
||||||
"types": ["react-native", "jest", "node"], /* Specify type package names to be included without being referenced in a source file. */
|
"types": [
|
||||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
"react-native",
|
||||||
"resolveJsonModule": true, /* Enable importing .json files */
|
"jest",
|
||||||
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
|
"node"
|
||||||
|
] /* Specify type package names to be included without being referenced in a source file. */,
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
"resolveJsonModule": true /* Enable importing .json files */,
|
||||||
|
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
|
||||||
|
|
||||||
/* JavaScript Support */
|
/* JavaScript Support */
|
||||||
"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
|
"allowJs": true /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */,
|
||||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
|
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
|
||||||
|
|
||||||
/* Emit */
|
/* Emit */
|
||||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
|
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
|
||||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||||
// "removeComments": true, /* Disable emitting comments. */
|
// "removeComments": true, /* Disable emitting comments. */
|
||||||
"noEmit": true, /* Disable emitting files from a compilation. */
|
"noEmit": true /* Disable emitting files from a compilation. */,
|
||||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
|
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
|
||||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||||
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
|
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
|
||||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
|
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
|
||||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||||
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
|
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
|
||||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||||
|
|
||||||
/* Interop Constraints */
|
/* Interop Constraints */
|
||||||
"isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
"isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */,
|
||||||
"allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
"allowSyntheticDefaultImports": true /* Allow 'import x from y' when a module doesn't have a default export. */,
|
||||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
|
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
|
||||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||||
|
|
||||||
/* Type Checking */
|
/* Type Checking */
|
||||||
"strict": true, /* Enable all strict type-checking options. */
|
"strict": true /* Enable all strict type-checking options. */,
|
||||||
"noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
|
"noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied `any` type.. */,
|
||||||
"strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
|
"strictNullChecks": true /* When type checking, take into account `null` and `undefined`. */,
|
||||||
"strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
"strictFunctionTypes": true /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */,
|
||||||
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
|
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
|
||||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||||
"noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
|
"noImplicitThis": true /* Enable error reporting when `this` is given the type `any`. */,
|
||||||
"useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
|
"useUnknownInCatchVariables": true /* Type catch clause variables as 'unknown' instead of 'any'. */,
|
||||||
"alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
"alwaysStrict": true /* Ensure 'use strict' is always emitted. */,
|
||||||
"noUnusedLocals": false, /* Enable error reporting when a local variables aren't read. */
|
"noUnusedLocals": false /* Enable error reporting when a local variables aren't read. */,
|
||||||
"noUnusedParameters": false, /* Raise an error when a function parameter isn't read */
|
"noUnusedParameters": false /* Raise an error when a function parameter isn't read */,
|
||||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||||
"noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
"noImplicitReturns": true /* Enable error reporting for codepaths that do not explicitly return in a function. */,
|
||||||
"noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
"noFallthroughCasesInSwitch": true /* Enable error reporting for fallthrough cases in switch statements. */,
|
||||||
"noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
"noUncheckedIndexedAccess": true /* Include 'undefined' in index signature results */,
|
||||||
"noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
"noImplicitOverride": true /* Ensure overriding members in derived classes are marked with an override modifier. */,
|
||||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
|
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
|
||||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||||
|
|
||||||
/* Completeness */
|
/* Completeness */
|
||||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules", "babel.config.js", "metro.config.js",
|
"node_modules",
|
||||||
"jest.config.js", "app.config.ts",
|
"babel.config.js",
|
||||||
"*/*.test.tsx"
|
"metro.config.js",
|
||||||
]
|
"jest.config.js",
|
||||||
|
"app.config.ts",
|
||||||
|
"*/*.test.tsx"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +1,46 @@
|
|||||||
import { VStack, Text, Image, Heading, IconButton, Icon, Container } from 'native-base';
|
import { VStack, Image, Heading, IconButton, Icon, Container } from 'native-base';
|
||||||
import { Ionicons } from '@expo/vector-icons';
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
import { SafeAreaView } from 'react-native';
|
import { SafeAreaView } from 'react-native';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import LoadingComponent from '../components/Loading';
|
import LoadingComponent from '../components/Loading';
|
||||||
import API from '../API';
|
import API from '../API';
|
||||||
|
|
||||||
const handleFavorite = () => {
|
const handleFavorite = () => {};
|
||||||
|
|
||||||
|
type ArtistDetailsViewProps = {
|
||||||
|
artistId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ArtistDetailsView = ({ artistId }: any) => {
|
const ArtistDetailsView = ({ artistId }: ArtistDetailsViewProps) => {
|
||||||
const { isLoading, data: artistData, error } = useQuery(['artist', artistId], () => API.getArtist(artistId));
|
const { isLoading, data: artistData } = useQuery(['artist', artistId], () =>
|
||||||
|
API.getArtist(artistId)
|
||||||
|
);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <LoadingComponent />;
|
return <LoadingComponent />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView>
|
<SafeAreaView>
|
||||||
<Container m={3}>
|
<Container m={3}>
|
||||||
<Image
|
<Image
|
||||||
source={{ uri: 'https://picsum.photos/200' }}
|
source={{ uri: 'https://picsum.photos/200' }}
|
||||||
alt={artistData?.name}
|
alt={artistData?.name}
|
||||||
size={20}
|
size={20}
|
||||||
borderRadius="full"
|
borderRadius="full"
|
||||||
/>
|
/>
|
||||||
<VStack space={3}>
|
<VStack space={3}>
|
||||||
<Heading>{artistData?.name}</Heading>
|
<Heading>{artistData?.name}</Heading>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<Icon as={Ionicons} name="heart" size={6} color="red.500" />}
|
icon={<Icon as={Ionicons} name="heart" size={6} color="red.500" />}
|
||||||
onPress={() => handleFavorite()}
|
onPress={() => handleFavorite()}
|
||||||
variant="unstyled"
|
variant="unstyled"
|
||||||
_pressed={{ opacity: 0.6 }}
|
_pressed={{ opacity: 0.6 }}
|
||||||
/>
|
/>
|
||||||
</VStack>
|
</VStack>
|
||||||
</Container>
|
</Container>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ArtistDetailsView;
|
export default ArtistDetailsView;
|
||||||
|
|||||||
@@ -6,8 +6,12 @@ import store from '../state/Store';
|
|||||||
import AuthenticationView from '../views/AuthenticationView';
|
import AuthenticationView from '../views/AuthenticationView';
|
||||||
|
|
||||||
describe('<AuthenticationView />', () => {
|
describe('<AuthenticationView />', () => {
|
||||||
it('has 3 children', () => {
|
it('has 3 children', () => {
|
||||||
const tree = TestRenderer.create(<Provider store={store}><AuthenticationView /></Provider>).toJSON();
|
const tree = TestRenderer.create(
|
||||||
expect(tree.children.length).toBe(3);
|
<Provider store={store}>
|
||||||
});
|
<AuthenticationView />
|
||||||
});
|
</Provider>
|
||||||
|
).toJSON();
|
||||||
|
expect(tree.children.length).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,58 +1,88 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import { useDispatch } from '../state/Store';
|
import { useDispatch } from '../state/Store';
|
||||||
import { Translate, translate } from "../i18n/i18n";
|
import { Translate, translate } from '../i18n/i18n';
|
||||||
import API, { APIError } from "../API";
|
import API, { APIError } from '../API';
|
||||||
import { setAccessToken } from "../state/UserSlice";
|
import { setAccessToken } from '../state/UserSlice';
|
||||||
import { Center, Button, Text } from 'native-base';
|
import { Center, Button, Text } from 'native-base';
|
||||||
import SigninForm from "../components/forms/signinform";
|
import SigninForm from '../components/forms/signinform';
|
||||||
import SignupForm from "../components/forms/signupform";
|
import SignupForm from '../components/forms/signupform';
|
||||||
import TextButton from "../components/TextButton";
|
import TextButton from '../components/TextButton';
|
||||||
import { RouteProps } from "../Navigation";
|
import { RouteProps } from '../Navigation';
|
||||||
|
|
||||||
const hanldeSignin = async (username: string, password: string, apiSetter: (accessToken: string) => void): Promise<string> => {
|
const hanldeSignin = async (
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
apiSetter: (accessToken: string) => void
|
||||||
|
): Promise<string> => {
|
||||||
try {
|
try {
|
||||||
const apiAccess = await API.authenticate({ username, password });
|
const apiAccess = await API.authenticate({ username, password });
|
||||||
apiSetter(apiAccess);
|
apiSetter(apiAccess);
|
||||||
return translate("loggedIn");
|
return translate('loggedIn');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof APIError) return translate(error.userMessage);
|
if (error instanceof APIError) return translate(error.userMessage);
|
||||||
if (error instanceof Error) return error.message;
|
if (error instanceof Error) return error.message;
|
||||||
return translate("unknownError");
|
return translate('unknownError');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSignup = async (username: string, password: string, email: string, apiSetter: (accessToken: string) => void): Promise<string> => {
|
const handleSignup = async (
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
email: string,
|
||||||
|
apiSetter: (accessToken: string) => void
|
||||||
|
): Promise<string> => {
|
||||||
try {
|
try {
|
||||||
const apiAccess = await API.createAccount({ username, password, email });
|
const apiAccess = await API.createAccount({ username, password, email });
|
||||||
apiSetter(apiAccess);
|
apiSetter(apiAccess);
|
||||||
return translate("loggedIn");
|
return translate('loggedIn');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof APIError) return translate(error.userMessage);
|
if (error instanceof APIError) return translate(error.userMessage);
|
||||||
if (error instanceof Error) return error.message;
|
if (error instanceof Error) return error.message;
|
||||||
return translate("unknownError");
|
return translate('unknownError');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
type AuthenticationViewProps = {
|
type AuthenticationViewProps = {
|
||||||
isSignup: boolean;
|
isSignup: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
const AuthenticationView = ({ isSignup }: RouteProps<AuthenticationViewProps>) => {
|
const AuthenticationView = ({ isSignup }: RouteProps<AuthenticationViewProps>) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [mode, setMode] = React.useState<"signin" | "signup">(isSignup ? "signup" : "signin");
|
const [mode, setMode] = React.useState<'signin' | 'signup'>(isSignup ? 'signup' : 'signin');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Center style={{ flex: 1 }}>
|
<Center style={{ flex: 1 }}>
|
||||||
<Text><Translate translationKey='welcome'/></Text>
|
<Text>
|
||||||
{mode === "signin"
|
<Translate translationKey="welcome" />
|
||||||
? <SigninForm onSubmit={(username, password) => hanldeSignin(username, password, (accessToken) => dispatch(setAccessToken(accessToken)))} />
|
</Text>
|
||||||
: <SignupForm onSubmit={(username, password, email) => handleSignup(username, password, email, (accessToken) => dispatch(setAccessToken(accessToken)))} />
|
{mode === 'signin' ? (
|
||||||
}
|
<SigninForm
|
||||||
{ mode ==="signin" && <Button variant="outline" marginTop={5} colorScheme="error" >{translate("forgottenPassword")}</Button> }
|
onSubmit={(username, password) =>
|
||||||
|
hanldeSignin(username, password, (accessToken) =>
|
||||||
|
dispatch(setAccessToken(accessToken))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<SignupForm
|
||||||
|
onSubmit={(username, password, email) =>
|
||||||
|
handleSignup(username, password, email, (accessToken) =>
|
||||||
|
dispatch(setAccessToken(accessToken))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{mode === 'signin' && (
|
||||||
|
<Button variant="outline" marginTop={5} colorScheme="error">
|
||||||
|
{translate('forgottenPassword')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<TextButton
|
<TextButton
|
||||||
translate={{ translationKey: mode === "signin" ? "signUpBtn" : "signInBtn" }}
|
translate={{ translationKey: mode === 'signin' ? 'signUpBtn' : 'signInBtn' }}
|
||||||
variant='outline' marginTop={5} colorScheme='primary'
|
variant="outline"
|
||||||
onPress={() => setMode(mode === "signin" ? "signup" : "signin")}
|
marginTop={5}
|
||||||
|
colorScheme="primary"
|
||||||
|
onPress={() => setMode(mode === 'signin' ? 'signup' : 'signin')}
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,8 +6,12 @@ import store from '../state/Store';
|
|||||||
import HomeView from '../views/HomeView';
|
import HomeView from '../views/HomeView';
|
||||||
|
|
||||||
describe('<HomeView />', () => {
|
describe('<HomeView />', () => {
|
||||||
it('has 2 children', () => {
|
it('has 2 children', () => {
|
||||||
const tree = TestRenderer.create(<Provider store={store}><HomeView /></Provider>).toJSON();
|
const tree = TestRenderer.create(
|
||||||
expect(tree.children.length).toBe(2);
|
<Provider store={store}>
|
||||||
});
|
<HomeView />
|
||||||
|
</Provider>
|
||||||
|
).toJSON();
|
||||||
|
expect(tree.children.length).toBe(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,40 +1,19 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import { useQueries, useQuery } from "react-query";
|
import { useQueries, useQuery } from 'react-query';
|
||||||
import API from "../API";
|
import API from '../API';
|
||||||
import LoadingComponent from "../components/Loading";
|
import { LoadingView } from '../components/Loading';
|
||||||
import CardGridCustom from "../components/CardGridCustom";
|
import { Box, ScrollView, Flex, Stack, Heading, VStack, HStack } from 'native-base';
|
||||||
import { LoadingView } from "../components/Loading";
|
import { useNavigation } from '../Navigation';
|
||||||
import {
|
import SongCardGrid from '../components/SongCardGrid';
|
||||||
Center,
|
import CompetenciesTable from '../components/CompetenciesTable';
|
||||||
Box,
|
import ProgressBar from '../components/ProgressBar';
|
||||||
ScrollView,
|
import Translate from '../components/Translate';
|
||||||
Flex,
|
import TextButton from '../components/TextButton';
|
||||||
useBreakpointValue,
|
import Song from '../models/Song';
|
||||||
Stack,
|
import { FontAwesome5 } from '@expo/vector-icons';
|
||||||
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";
|
|
||||||
|
|
||||||
const HomeView = () => {
|
const HomeView = () => {
|
||||||
const theme = useTheme();
|
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const screenSize = useBreakpointValue({ base: 'small', md: "big"});
|
|
||||||
const userQuery = useQuery(['user'], () => API.getUserInfo());
|
const userQuery = useQuery(['user'], () => API.getUserInfo());
|
||||||
const playHistoryQuery = useQuery(['history', 'play'], () => API.getUserPlayHistory());
|
const playHistoryQuery = useQuery(['history', 'play'], () => API.getUserPlayHistory());
|
||||||
const searchHistoryQuery = useQuery(['history', 'search'], () => API.getSearchHistory(0, 10));
|
const searchHistoryQuery = useQuery(['history', 'search'], () => API.getSearchHistory(0, 10));
|
||||||
@@ -43,125 +22,168 @@ const HomeView = () => {
|
|||||||
const songHistory = useQueries(
|
const songHistory = useQueries(
|
||||||
playHistoryQuery.data?.map(({ songID }) => ({
|
playHistoryQuery.data?.map(({ songID }) => ({
|
||||||
queryKey: ['song', songID],
|
queryKey: ['song', songID],
|
||||||
queryFn: () => API.getSong(songID)
|
queryFn: () => API.getSong(songID),
|
||||||
})) ?? []
|
})) ?? []
|
||||||
);
|
);
|
||||||
const artistsQueries = useQueries((songHistory
|
const artistsQueries = useQueries(
|
||||||
.map((entry) => entry.data)
|
songHistory
|
||||||
.concat(nextStepQuery.data ?? [])
|
.map((entry) => entry.data)
|
||||||
.filter((s): s is Song => s !== undefined))
|
.concat(nextStepQuery.data ?? [])
|
||||||
.map((song) => (
|
.filter((s): s is Song => s !== undefined)
|
||||||
{ queryKey: ['artist', song.id], queryFn: () => API.getArtist(song.artistId) }
|
.map((song) => ({
|
||||||
))
|
queryKey: ['artist', song.id],
|
||||||
|
queryFn: () => API.getArtist(song.artistId),
|
||||||
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!userQuery.data || !skillsQuery.data || !searchHistoryQuery.data || !playHistoryQuery.data) {
|
if (
|
||||||
return <LoadingView/>
|
!userQuery.data ||
|
||||||
|
!skillsQuery.data ||
|
||||||
|
!searchHistoryQuery.data ||
|
||||||
|
!playHistoryQuery.data
|
||||||
|
) {
|
||||||
|
return <LoadingView />;
|
||||||
}
|
}
|
||||||
return <ScrollView p={10}>
|
return (
|
||||||
<Flex>
|
<ScrollView p={10}>
|
||||||
<Stack space={4}
|
<Flex>
|
||||||
display={{ base: 'block', md: 'flex' }}
|
<Stack
|
||||||
direction={{ base: 'column', md: 'row' }}
|
space={4}
|
||||||
textAlign={{ base: 'center', md: 'inherit' }}
|
display={{ base: 'block', md: 'flex' }}
|
||||||
justifyContent="space-evenly"
|
direction={{ base: 'column', md: 'row' }}
|
||||||
>
|
textAlign={{ base: 'center', md: 'inherit' }}
|
||||||
<Translate fontSize="xl" flex={2}
|
justifyContent="space-evenly"
|
||||||
translationKey="welcome" format={(welcome) => `${welcome} ${userQuery.data.name}!`}
|
>
|
||||||
/>
|
<Translate
|
||||||
<Box flex={1}>
|
fontSize="xl"
|
||||||
<ProgressBar xp={userQuery.data.data.xp}/>
|
flex={2}
|
||||||
</Box>
|
translationKey="welcome"
|
||||||
</Stack>
|
format={(welcome) => `${welcome} ${userQuery.data.name}!`}
|
||||||
</Flex>
|
/>
|
||||||
<Stack direction={{ base: 'column', lg: 'row' }} height="100%" space={5} paddingTop={5}>
|
<Box flex={1}>
|
||||||
<VStack flex={{ lg: 2 }} space={5}>
|
<ProgressBar xp={userQuery.data.data.xp} />
|
||||||
<SongCardGrid
|
|
||||||
heading={<Translate translationKey='goNextStep'/>}
|
|
||||||
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
|
|
||||||
})) ?? []
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Stack direction={{ base: 'column', lg: 'row' }}>
|
|
||||||
<Box flex={{ lg: 1 }}>
|
|
||||||
<Heading><Translate translationKey='mySkillsToImprove'/></Heading>
|
|
||||||
<Box padding={5}>
|
|
||||||
<CompetenciesTable {...skillsQuery.data}/>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box flex={{ lg: 1 }}>
|
</Stack>
|
||||||
<SongCardGrid
|
</Flex>
|
||||||
heading={<Translate translationKey='recentlyPlayed'/>}
|
<Stack direction={{ base: 'column', lg: 'row' }} height="100%" space={5} paddingTop={5}>
|
||||||
songs={songHistory
|
<VStack flex={{ lg: 2 }} space={5}>
|
||||||
.map(({ data }) => data)
|
<SongCardGrid
|
||||||
.filter((data): data is Song => data !== undefined)
|
heading={<Translate translationKey="goNextStep" />}
|
||||||
.filter((song, i, array) => array.map((s) => s.id).findIndex((id) => id == song.id) == i)
|
songs={
|
||||||
.filter((song) => artistsQueries.find((artistQuery) => artistQuery.data?.id === song.artistId))
|
nextStepQuery.data
|
||||||
|
?.filter((song) =>
|
||||||
|
artistsQueries.find(
|
||||||
|
(artistQuery) => artistQuery.data?.id === song.artistId
|
||||||
|
)
|
||||||
|
)
|
||||||
.map((song) => ({
|
.map((song) => ({
|
||||||
cover: song.cover,
|
cover: song.cover,
|
||||||
name: song.name,
|
name: song.name,
|
||||||
songId: song.id,
|
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,
|
||||||
})) ?? []
|
})) ?? []
|
||||||
}
|
}
|
||||||
|
/>
|
||||||
|
<Stack direction={{ base: 'column', lg: 'row' }}>
|
||||||
|
<Box flex={{ lg: 1 }}>
|
||||||
|
<Heading>
|
||||||
|
<Translate translationKey="mySkillsToImprove" />
|
||||||
|
</Heading>
|
||||||
|
<Box padding={5}>
|
||||||
|
<CompetenciesTable {...skillsQuery.data} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box flex={{ lg: 1 }}>
|
||||||
|
<SongCardGrid
|
||||||
|
heading={<Translate translationKey="recentlyPlayed" />}
|
||||||
|
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,
|
||||||
|
})) ?? []
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</VStack>
|
||||||
|
<VStack flex={{ lg: 1 }} height={{ lg: '100%' }} alignItems="center">
|
||||||
|
<HStack width="100%" justifyContent="space-evenly" p={5} space={5}>
|
||||||
|
<TextButton
|
||||||
|
translate={{ translationKey: 'searchBtn' }}
|
||||||
|
colorScheme="secondary"
|
||||||
|
size="sm"
|
||||||
|
onPress={() => navigation.navigate('Search', {})}
|
||||||
/>
|
/>
|
||||||
</Box>
|
<TextButton
|
||||||
</Stack>
|
translate={{ translationKey: 'settingsBtn' }}
|
||||||
</VStack>
|
colorScheme="gray"
|
||||||
<VStack flex={{ lg: 1 }} height={{ lg: '100%' }} alignItems="center">
|
size="sm"
|
||||||
<HStack width="100%" justifyContent="space-evenly" p={5} space={5}>
|
onPress={() => navigation.navigate('Settings')}
|
||||||
<TextButton
|
/>
|
||||||
translate={{ translationKey: 'searchBtn' }}
|
</HStack>
|
||||||
colorScheme='secondary' size="sm"
|
<Box style={{ width: '100%' }}>
|
||||||
onPress={() => navigation.navigate('Search', {})}
|
<Heading>
|
||||||
/>
|
<Translate translationKey="recentSearches" />
|
||||||
<TextButton translate={{ translationKey: 'settingsBtn' }}
|
</Heading>
|
||||||
colorScheme='gray' size="sm"
|
<Flex
|
||||||
onPress={() => navigation.navigate('Settings')}
|
padding={3}
|
||||||
/>
|
style={{
|
||||||
</HStack>
|
width: '100%',
|
||||||
<Box style={{ width: '100%' }}>
|
alignItems: 'flex-start',
|
||||||
<Heading><Translate translationKey='recentSearches'/></Heading>
|
alignContent: 'flex-start',
|
||||||
<Flex padding={3} style={{
|
flexDirection: 'row',
|
||||||
width: '100%',
|
flexWrap: 'wrap',
|
||||||
alignItems: 'flex-start',
|
}}
|
||||||
alignContent: 'flex-start',
|
>
|
||||||
flexDirection: 'row',
|
{searchHistoryQuery.data?.length === 0 && (
|
||||||
flexWrap: 'wrap',
|
<Translate translationKey="noRecentSearches" />
|
||||||
}}>
|
)}
|
||||||
{
|
{[...new Set(searchHistoryQuery.data.map((x) => x.query))]
|
||||||
searchHistoryQuery.data?.length === 0 && <Translate translationKey='noRecentSearches'/>
|
.slice(0, 5)
|
||||||
}
|
.map((query) => (
|
||||||
{
|
<TextButton
|
||||||
[...(new Set(searchHistoryQuery.data.map((x) => x.query)))].slice(0, 5).map((query) => (
|
leftIcon={<FontAwesome5 name="search" size={16} />}
|
||||||
<TextButton
|
style={{
|
||||||
leftIcon={
|
margin: 2,
|
||||||
<FontAwesome5 name="search" size={16} />
|
}}
|
||||||
}
|
key={query}
|
||||||
style={{
|
variant="solid"
|
||||||
margin: 2,
|
size="xs"
|
||||||
}}
|
colorScheme="primary"
|
||||||
key={ query }
|
label={query}
|
||||||
variant="solid"
|
onPress={() =>
|
||||||
size="xs"
|
navigation.navigate('Search', { query: query })
|
||||||
colorScheme="primary"
|
}
|
||||||
label={query}
|
/>
|
||||||
onPress={() => navigation.navigate('Search', { query: query })}
|
))}
|
||||||
/>
|
</Flex>
|
||||||
))
|
</Box>
|
||||||
}
|
</VStack>
|
||||||
</Flex>
|
</Stack>
|
||||||
</Box>
|
</ScrollView>
|
||||||
</VStack>
|
);
|
||||||
</Stack>
|
};
|
||||||
|
|
||||||
</ScrollView>
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default HomeView;
|
export default HomeView;
|
||||||
|
|||||||
@@ -1,10 +1,22 @@
|
|||||||
|
/* eslint-disable no-mixed-spaces-and-tabs */
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { SafeAreaView, Platform, Animated } from 'react-native';
|
import { SafeAreaView, Platform, Animated } from 'react-native';
|
||||||
import * as ScreenOrientation from 'expo-screen-orientation';
|
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 IconButton from '../components/IconButton';
|
||||||
import { Ionicons, MaterialCommunityIcons } from "@expo/vector-icons";
|
import { Ionicons, MaterialCommunityIcons } from '@expo/vector-icons';
|
||||||
import { RouteProps, useNavigation } from "../Navigation";
|
import { RouteProps, useNavigation } from '../Navigation';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import API from '../API';
|
import API from '../API';
|
||||||
import LoadingComponent, { LoadingView } from '../components/Loading';
|
import LoadingComponent, { LoadingView } from '../components/Loading';
|
||||||
@@ -13,39 +25,36 @@ import VirtualPiano from '../components/VirtualPiano/VirtualPiano';
|
|||||||
import { strToKey, keyToStr, Note } from '../models/Piano';
|
import { strToKey, keyToStr, Note } from '../models/Piano';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { RootState } from '../state/Store';
|
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 { 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 PartitionView from '../components/PartitionView';
|
||||||
import TextButton from '../components/TextButton';
|
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 * as Linking from 'expo-linking';
|
||||||
import { URL } from 'url';
|
import { URL } from 'url';
|
||||||
import { url } from 'inspector';
|
|
||||||
|
|
||||||
type PlayViewProps = {
|
type PlayViewProps = {
|
||||||
songId: number,
|
songId: number;
|
||||||
type: 'practice' | 'normal'
|
type: 'practice' | 'normal';
|
||||||
}
|
};
|
||||||
|
|
||||||
type ScoreMessage = {
|
type ScoreMessage = {
|
||||||
content: string,
|
content: string;
|
||||||
color?: ColorSchemeType
|
color?: ColorSchemeType;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
// this a hot fix this should be reverted soon
|
// this a hot fix this should be reverted soon
|
||||||
let scoroBaseApiUrl = Constants.manifest?.extra?.scoroUrl;
|
let scoroBaseApiUrl = Constants.manifest?.extra?.scoroUrl;
|
||||||
|
|
||||||
if (process.env.NODE_ENV != 'development' && Platform.OS === 'web') {
|
if (process.env.NODE_ENV != 'development' && Platform.OS === 'web') {
|
||||||
|
|
||||||
Linking.getInitialURL().then((url) => {
|
Linking.getInitialURL().then((url) => {
|
||||||
if (url !== null) {
|
if (url !== null) {
|
||||||
const location = new URL(url);
|
const location = new URL(url);
|
||||||
if (location.protocol === 'https:') {
|
if (location.protocol === 'https:') {
|
||||||
scoroBaseApiUrl = "wss://" + location.host + "/ws";
|
scoroBaseApiUrl = 'wss://' + location.host + '/ws';
|
||||||
} else {
|
} else {
|
||||||
scoroBaseApiUrl = "ws://" + location.host + "/ws";
|
scoroBaseApiUrl = 'ws://' + location.host + '/ws';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -74,8 +83,9 @@ const PlayView = ({ songId, type, route }: RouteProps<PlayViewProps>) => {
|
|||||||
const [partitionRendered, setPartitionRendered] = useState(false); // Used to know when partitionview can render
|
const [partitionRendered, setPartitionRendered] = useState(false); // Used to know when partitionview can render
|
||||||
const [score, setScore] = useState(0); // Between 0 and 100
|
const [score, setScore] = useState(0); // Between 0 and 100
|
||||||
const fadeAnim = useRef(new Animated.Value(0)).current;
|
const fadeAnim = useRef(new Animated.Value(0)).current;
|
||||||
const musixml = useQuery(["musixml", songId], () =>
|
const musixml = useQuery(
|
||||||
API.getSongMusicXML(songId).then((data) => new TextDecoder().decode(data)),
|
['musixml', songId],
|
||||||
|
() => API.getSongMusicXML(songId).then((data) => new TextDecoder().decode(data)),
|
||||||
{ staleTime: Infinity }
|
{ staleTime: Infinity }
|
||||||
);
|
);
|
||||||
const getElapsedTime = () => stopwatch.getElapsedRunningTime() - 3000;
|
const getElapsedTime = () => stopwatch.getElapsedRunningTime() - 3000;
|
||||||
@@ -84,12 +94,14 @@ const PlayView = ({ songId, type, route }: RouteProps<PlayViewProps>) => {
|
|||||||
const onPause = () => {
|
const onPause = () => {
|
||||||
stopwatch.pause();
|
stopwatch.pause();
|
||||||
setPause(true);
|
setPause(true);
|
||||||
webSocket.current?.send(JSON.stringify({
|
webSocket.current?.send(
|
||||||
type: "pause",
|
JSON.stringify({
|
||||||
paused: true,
|
type: 'pause',
|
||||||
time: getElapsedTime()
|
paused: true,
|
||||||
}));
|
time: getElapsedTime(),
|
||||||
}
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
const onResume = () => {
|
const onResume = () => {
|
||||||
if (stopwatch.isStarted()) {
|
if (stopwatch.isStarted()) {
|
||||||
stopwatch.resume();
|
stopwatch.resume();
|
||||||
@@ -97,17 +109,21 @@ const PlayView = ({ songId, type, route }: RouteProps<PlayViewProps>) => {
|
|||||||
stopwatch.start();
|
stopwatch.start();
|
||||||
}
|
}
|
||||||
setPause(false);
|
setPause(false);
|
||||||
webSocket.current?.send(JSON.stringify({
|
webSocket.current?.send(
|
||||||
type: "pause",
|
JSON.stringify({
|
||||||
paused: false,
|
type: 'pause',
|
||||||
time: getElapsedTime()
|
paused: false,
|
||||||
}));
|
time: getElapsedTime(),
|
||||||
}
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
const onEnd = () => {
|
const onEnd = () => {
|
||||||
webSocket.current?.send(JSON.stringify({
|
webSocket.current?.send(
|
||||||
type: "end"
|
JSON.stringify({
|
||||||
}));
|
type: 'end',
|
||||||
}
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const onMIDISuccess = (access: MIDIAccess) => {
|
const onMIDISuccess = (access: MIDIAccess) => {
|
||||||
const inputs = access.inputs;
|
const inputs = access.inputs;
|
||||||
@@ -120,12 +136,14 @@ const PlayView = ({ songId, type, route }: RouteProps<PlayViewProps>) => {
|
|||||||
let inputIndex = 0;
|
let inputIndex = 0;
|
||||||
webSocket.current = new WebSocket(scoroBaseApiUrl);
|
webSocket.current = new WebSocket(scoroBaseApiUrl);
|
||||||
webSocket.current.onopen = () => {
|
webSocket.current.onopen = () => {
|
||||||
webSocket.current!.send(JSON.stringify({
|
webSocket.current!.send(
|
||||||
type: "start",
|
JSON.stringify({
|
||||||
id: song.data!.id,
|
type: 'start',
|
||||||
mode: type,
|
id: song.data!.id,
|
||||||
bearer: accessToken
|
mode: type,
|
||||||
}));
|
bearer: accessToken,
|
||||||
|
})
|
||||||
|
);
|
||||||
};
|
};
|
||||||
webSocket.current.onmessage = (message) => {
|
webSocket.current.onmessage = (message) => {
|
||||||
try {
|
try {
|
||||||
@@ -137,7 +155,7 @@ const PlayView = ({ songId, type, route }: RouteProps<PlayViewProps>) => {
|
|||||||
const points = data.info.score;
|
const points = data.info.score;
|
||||||
const maxPoints = data.info.max_score || 1;
|
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 formattedMessage = '';
|
||||||
let messageColor: ColorSchemeType | undefined;
|
let messageColor: ColorSchemeType | undefined;
|
||||||
@@ -172,18 +190,18 @@ const PlayView = ({ songId, type, route }: RouteProps<PlayViewProps>) => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
inputs.forEach((input) => {
|
inputs.forEach((input) => {
|
||||||
if (inputIndex != 0) {
|
if (inputIndex != 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
input.onmidimessage = (message) => {
|
input.onmidimessage = (message) => {
|
||||||
const { command, channel, note, velocity } = parseMidiMessage(message);
|
const { command } = parseMidiMessage(message);
|
||||||
const keyIsPressed = command == 9;;
|
const keyIsPressed = command == 9;
|
||||||
const keyCode = message.data[1];
|
const keyCode = message.data[1];
|
||||||
webSocket.current?.send(
|
webSocket.current?.send(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
type: keyIsPressed ? "note_on" : "note_off",
|
type: keyIsPressed ? 'note_on' : 'note_off',
|
||||||
note: keyCode,
|
note: keyCode,
|
||||||
id: song.data!.id,
|
id: song.data!.id,
|
||||||
time: getElapsedTime(),
|
time: getElapsedTime(),
|
||||||
@@ -192,22 +210,22 @@ const PlayView = ({ songId, type, route }: RouteProps<PlayViewProps>) => {
|
|||||||
};
|
};
|
||||||
inputIndex++;
|
inputIndex++;
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
const onMIDIFailure = () => {
|
const onMIDIFailure = () => {
|
||||||
toast.show({ description: `Failed to get MIDI access` });
|
toast.show({ description: `Failed to get MIDI access` });
|
||||||
}
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE).catch(() => {});
|
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE).catch(() => {});
|
||||||
let interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
setTime(() => getElapsedTime()) // Countdown
|
setTime(() => getElapsedTime()); // Countdown
|
||||||
}, 1);
|
}, 1);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
ScreenOrientation.unlockAsync().catch(() => {});
|
ScreenOrientation.unlockAsync().catch(() => {});
|
||||||
onEnd();
|
onEnd();
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
}
|
};
|
||||||
}, []);
|
}, []);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lastScoreMessage) {
|
if (lastScoreMessage) {
|
||||||
@@ -231,109 +249,157 @@ const PlayView = ({ songId, type, route }: RouteProps<PlayViewProps>) => {
|
|||||||
}, [song.data, partitionRendered]);
|
}, [song.data, partitionRendered]);
|
||||||
|
|
||||||
if (!song.data || !musixml.data) {
|
if (!song.data || !musixml.data) {
|
||||||
return <LoadingView/>;
|
return <LoadingView />;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={{ flexGrow: 1, flexDirection: 'column' }}>
|
<SafeAreaView style={{ flexGrow: 1, flexDirection: 'column' }}>
|
||||||
<HStack width="100%" justifyContent="center" p={3} style={{ position: 'absolute', top: 1 }}>
|
<HStack
|
||||||
|
width="100%"
|
||||||
|
justifyContent="center"
|
||||||
|
p={3}
|
||||||
|
style={{ position: 'absolute', top: 1 }}
|
||||||
|
>
|
||||||
<Animated.View style={{ opacity: fadeAnim }}>
|
<Animated.View style={{ opacity: fadeAnim }}>
|
||||||
<TextButton
|
<TextButton
|
||||||
disabled
|
disabled
|
||||||
label={lastScoreMessage?.content ?? ''}
|
label={lastScoreMessage?.content ?? ''}
|
||||||
colorScheme={lastScoreMessage?.color} rounded='sm'
|
colorScheme={lastScoreMessage?.color}
|
||||||
|
rounded="sm"
|
||||||
/>
|
/>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</HStack>
|
</HStack>
|
||||||
<View style={{ flexGrow: 1, justifyContent: 'center' }}>
|
<View style={{ flexGrow: 1, justifyContent: 'center' }}>
|
||||||
<PartitionView file={musixml.data}
|
<PartitionView
|
||||||
|
file={musixml.data}
|
||||||
onPartitionReady={() => setPartitionRendered(true)}
|
onPartitionReady={() => setPartitionRendered(true)}
|
||||||
timestamp={Math.max(0, time)}
|
timestamp={Math.max(0, time)}
|
||||||
onEndReached={() => {
|
onEndReached={() => {
|
||||||
onEnd();
|
onEnd();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{ !partitionRendered && <LoadingComponent/> }
|
{!partitionRendered && <LoadingComponent />}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{isVirtualPianoVisible && <Column
|
{isVirtualPianoVisible && (
|
||||||
|
<Column
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '20%',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<VirtualPiano
|
||||||
|
onNoteDown={(note) => {
|
||||||
|
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' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Column>
|
||||||
|
)}
|
||||||
|
<Box
|
||||||
|
shadow={4}
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
height: '12%',
|
||||||
justifyContent: "flex-end",
|
|
||||||
alignItems: "center",
|
|
||||||
height: '20%',
|
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
borderWidth: 0.5,
|
||||||
|
margin: 5,
|
||||||
|
display: !partitionRendered ? 'none' : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<VirtualPiano
|
<Row justifyContent="space-between" style={{ flexGrow: 1, alignItems: 'center' }}>
|
||||||
onNoteDown={(note: any) => {
|
<Column
|
||||||
console.log("On note down", keyToStr(note));
|
space={2}
|
||||||
}}
|
style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}
|
||||||
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" },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
/>
|
|
||||||
</Column>}
|
|
||||||
<Box shadow={4} style={{ height: '12%', width:'100%', borderWidth: 0.5, margin: 5, display: !partitionRendered ? 'none' : undefined }}>
|
|
||||||
<Row justifyContent='space-between' style={{ flexGrow: 1, alignItems: 'center' }} >
|
|
||||||
<Column space={2} style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
|
||||||
<Text style={{ fontWeight: 'bold' }}>Score: {score}%</Text>
|
<Text style={{ fontWeight: 'bold' }}>Score: {score}%</Text>
|
||||||
<Progress value={score} style={{ width: '90%' }}/>
|
<Progress value={score} style={{ width: '90%' }} />
|
||||||
</Column>
|
</Column>
|
||||||
<Center style={{ flex: 1, alignItems: 'center' }}>
|
<Center style={{ flex: 1, alignItems: 'center' }}>
|
||||||
<Text style={{ fontWeight: '700' }}>{song.data.name}</Text>
|
<Text style={{ fontWeight: '700' }}>{song.data.name}</Text>
|
||||||
</Center>
|
</Center>
|
||||||
<Row style={{ flex: 1, height: '100%', justifyContent: 'space-evenly', alignItems: 'center' }}>
|
<Row
|
||||||
{midiKeyboardFound && <>
|
style={{
|
||||||
<IconButton size='sm' variant='solid' icon={
|
flex: 1,
|
||||||
<Icon as={Ionicons} name={paused ? "play" : "pause"}/>
|
height: '100%',
|
||||||
} onPress={() => {
|
justifyContent: 'space-evenly',
|
||||||
if (paused) {
|
alignItems: 'center',
|
||||||
onResume();
|
}}
|
||||||
} else {
|
>
|
||||||
onPause();
|
{midiKeyboardFound && (
|
||||||
}
|
<>
|
||||||
}}/>
|
<IconButton
|
||||||
<IconButton size='sm' colorScheme='coolGray' variant='solid' icon={
|
size="sm"
|
||||||
<Icon as={MaterialCommunityIcons}
|
variant="solid"
|
||||||
name={ isVirtualPianoVisible ? "piano-off" : "piano"} />
|
icon={<Icon as={Ionicons} name={paused ? 'play' : 'pause'} />}
|
||||||
} onPress={() => {
|
onPress={() => {
|
||||||
setVirtualPianoVisible(!isVirtualPianoVisible);
|
if (paused) {
|
||||||
}}/>
|
onResume();
|
||||||
<Text>
|
} else {
|
||||||
{ time < 0
|
onPause();
|
||||||
? 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')}`
|
<IconButton
|
||||||
}
|
size="sm"
|
||||||
</Text>
|
colorScheme="coolGray"
|
||||||
<IconButton size='sm' colorScheme='coolGray' variant='solid' icon={
|
variant="solid"
|
||||||
<Icon as={Ionicons} name="stop"/>
|
icon={
|
||||||
} onPress={() => {
|
<Icon
|
||||||
onEnd();
|
as={MaterialCommunityIcons}
|
||||||
}}/>
|
name={isVirtualPianoVisible ? 'piano-off' : 'piano'}
|
||||||
</>}
|
/>
|
||||||
|
}
|
||||||
|
onPress={() => {
|
||||||
|
setVirtualPianoVisible(!isVirtualPianoVisible);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text>
|
||||||
|
{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')}`}
|
||||||
|
</Text>
|
||||||
|
<IconButton
|
||||||
|
size="sm"
|
||||||
|
colorScheme="coolGray"
|
||||||
|
variant="solid"
|
||||||
|
icon={<Icon as={Ionicons} name="stop" />}
|
||||||
|
onPress={() => {
|
||||||
|
onEnd();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
</Row>
|
</Row>
|
||||||
</Box>
|
</Box>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default PlayView
|
export default PlayView;
|
||||||
|
|||||||
@@ -1,100 +1,131 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Dimensions, View } from 'react-native';
|
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 Translate from '../components/Translate';
|
||||||
import { useNavigation } from "../Navigation";
|
import { useNavigation } from '../Navigation';
|
||||||
import TextButton from '../components/TextButton';
|
import TextButton from '../components/TextButton';
|
||||||
|
|
||||||
const UserMedals = () => {
|
const UserMedals = () => {
|
||||||
return (
|
return (
|
||||||
<Card marginX={20} marginY={10}>
|
<Card marginX={20} marginY={10}>
|
||||||
<Heading>
|
<Heading>
|
||||||
<Translate translationKey='medals'/>
|
<Translate translationKey="medals" />
|
||||||
</Heading>
|
</Heading>
|
||||||
<HStack alignItems={'row'} space='10'>
|
<HStack alignItems={'row'} space="10">
|
||||||
<Image source={{
|
<Image
|
||||||
uri: "https://wallpaperaccess.com/full/317501.jpg"
|
source={{
|
||||||
}} alt="Profile picture" size="lg"
|
uri: 'https://wallpaperaccess.com/full/317501.jpg',
|
||||||
/>
|
}}
|
||||||
<Image source={{
|
alt="Profile picture"
|
||||||
uri: "https://wallpaperaccess.com/full/317501.jpg"
|
size="lg"
|
||||||
}} alt="Profile picture" size="lg"
|
/>
|
||||||
/>
|
<Image
|
||||||
<Image source={{
|
source={{
|
||||||
uri: "https://wallpaperaccess.com/full/317501.jpg"
|
uri: 'https://wallpaperaccess.com/full/317501.jpg',
|
||||||
}} alt="Profile picture" size="lg"
|
}}
|
||||||
/>
|
alt="Profile picture"
|
||||||
<Image source={{
|
size="lg"
|
||||||
uri: "https://wallpaperaccess.com/full/317501.jpg"
|
/>
|
||||||
}} alt="Profile picture" size="lg"
|
<Image
|
||||||
/>
|
source={{
|
||||||
</HStack>
|
uri: 'https://wallpaperaccess.com/full/317501.jpg',
|
||||||
</Card>
|
}}
|
||||||
);
|
alt="Profile picture"
|
||||||
}
|
size="lg"
|
||||||
|
/>
|
||||||
|
<Image
|
||||||
|
source={{
|
||||||
|
uri: 'https://wallpaperaccess.com/full/317501.jpg',
|
||||||
|
}}
|
||||||
|
alt="Profile picture"
|
||||||
|
size="lg"
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const PlayerStats = () => {
|
const PlayerStats = () => {
|
||||||
const answer = "Answer from back";
|
const answer = 'Answer from back';
|
||||||
|
|
||||||
return(
|
return (
|
||||||
<Card marginX={20} marginY={10}>
|
<Card marginX={20} marginY={10}>
|
||||||
<Heading> <Translate translationKey='playerStats'/> </Heading>
|
<Heading>
|
||||||
<Text> <Translate translationKey='mostPlayedSong'/> {answer} </Text>
|
{' '}
|
||||||
<Text> <Translate translationKey='goodNotesPlayed'/> {answer} </Text>
|
<Translate translationKey="playerStats" />{' '}
|
||||||
<Text> <Translate translationKey='longestCombo'/> {answer} </Text>
|
</Heading>
|
||||||
<Text> <Translate translationKey='favoriteGenre'/> {answer} </Text>
|
<Text>
|
||||||
</Card>
|
{' '}
|
||||||
|
<Translate translationKey="mostPlayedSong" /> {answer}{' '}
|
||||||
);
|
</Text>
|
||||||
}
|
<Text>
|
||||||
|
{' '}
|
||||||
|
<Translate translationKey="goodNotesPlayed" /> {answer}{' '}
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
{' '}
|
||||||
|
<Translate translationKey="longestCombo" /> {answer}{' '}
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
{' '}
|
||||||
|
<Translate translationKey="favoriteGenre" /> {answer}{' '}
|
||||||
|
</Text>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const ProfilePictureBannerAndLevel = () => {
|
const ProfilePictureBannerAndLevel = () => {
|
||||||
const profilePic = "https://wallpaperaccess.com/full/317501.jpg"
|
const profilePic = 'https://wallpaperaccess.com/full/317501.jpg';
|
||||||
const username = "Username"
|
const username = 'Username';
|
||||||
const level = "1"
|
const level = '1';
|
||||||
|
|
||||||
// banner size
|
// banner size
|
||||||
const dimensions = Dimensions.get('window');
|
const dimensions = Dimensions.get('window');
|
||||||
const imageHeight = dimensions.height / 5;
|
const imageHeight = dimensions.height / 5;
|
||||||
const imageWidth = dimensions.width;
|
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 (
|
return (
|
||||||
<View style={{flexDirection: 'row'}}>
|
<View style={{ flexDirection: 'row' }}>
|
||||||
<Image source={{ uri : "https://wallpaperaccess.com/full/317501.jpg" }} size="lg"
|
<Image
|
||||||
style={{ height: imageHeight, width: imageWidth, zIndex:0, opacity: 0.5 }}
|
source={{ uri: 'https://wallpaperaccess.com/full/317501.jpg' }}
|
||||||
/>
|
size="lg"
|
||||||
<Box zIndex={1} position={"absolute"} marginY={10} marginX={10}>
|
style={{ height: imageHeight, width: imageWidth, zIndex: 0, opacity: 0.5 }}
|
||||||
<Image borderRadius={100} source={{ uri: profilePic }}
|
/>
|
||||||
alt="Profile picture" size="lg"
|
<Box zIndex={1} position={'absolute'} marginY={10} marginX={10}>
|
||||||
style= {{position: 'absolute'}}
|
<Image
|
||||||
|
borderRadius={100}
|
||||||
|
source={{ uri: profilePic }}
|
||||||
|
alt="Profile picture"
|
||||||
|
size="lg"
|
||||||
|
style={{ position: 'absolute' }}
|
||||||
/>
|
/>
|
||||||
<Box w="100%" paddingY={3} paddingLeft={100}>
|
<Box w="100%" paddingY={3} paddingLeft={100}>
|
||||||
<Heading>{username}</Heading>
|
<Heading>{username}</Heading>
|
||||||
<Heading>Level : {level}</Heading>
|
<Heading>Level : {level}</Heading>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const ProfileView = () => {
|
const ProfileView = () => {
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{flexDirection: 'column'}}>
|
<View style={{ flexDirection: 'column' }}>
|
||||||
<ProfilePictureBannerAndLevel/>
|
<ProfilePictureBannerAndLevel />
|
||||||
<UserMedals/>
|
<UserMedals />
|
||||||
<PlayerStats/>
|
<PlayerStats />
|
||||||
<Box w="10%" paddingY={10} paddingLeft={5} paddingRight={50} zIndex={1}>
|
<Box w="10%" paddingY={10} paddingLeft={5} paddingRight={50} zIndex={1}>
|
||||||
<TextButton
|
<TextButton
|
||||||
onPress={() => navigation.navigate('Settings', { screen: 'Profile' })}
|
onPress={() => navigation.navigate('Settings', { screen: 'Profile' })}
|
||||||
translate={{ translationKey: 'settingsBtn' }}
|
translate={{ translationKey: 'settingsBtn' }}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default ProfileView;
|
export default ProfileView;
|
||||||
|
|||||||
@@ -1,23 +1,13 @@
|
|||||||
import {
|
import { Card, Column, Image, Row, Text, ScrollView, VStack } from 'native-base';
|
||||||
Card,
|
import Translate from '../components/Translate';
|
||||||
Column,
|
import { RouteProps, useNavigation } from '../Navigation';
|
||||||
Image,
|
import { CardBorderRadius } from '../components/Card';
|
||||||
Row,
|
import TextButton from '../components/TextButton';
|
||||||
Text,
|
import API from '../API';
|
||||||
ScrollView,
|
import CardGridCustom from '../components/CardGridCustom';
|
||||||
VStack,
|
import SongCard from '../components/SongCard';
|
||||||
} from "native-base";
|
import { useQueries, useQuery } from 'react-query';
|
||||||
import Translate from "../components/Translate";
|
import { LoadingView } from '../components/Loading';
|
||||||
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";
|
|
||||||
|
|
||||||
type ScoreViewProps = {
|
type ScoreViewProps = {
|
||||||
songId: number;
|
songId: number;
|
||||||
@@ -35,28 +25,23 @@ type ScoreViewProps = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const ScoreView = ({
|
const ScoreView = ({ songId, overallScore, precision, score }: RouteProps<ScoreViewProps>) => {
|
||||||
songId,
|
|
||||||
overallScore,
|
|
||||||
precision,
|
|
||||||
score,
|
|
||||||
}: RouteProps<ScoreViewProps>) => {
|
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const songQuery = useQuery(["song", songId], () => API.getSong(songId));
|
const songQuery = useQuery(['song', songId], () => API.getSong(songId));
|
||||||
const artistQuery = useQuery(
|
const artistQuery = useQuery(
|
||||||
["song", songId, "artist"],
|
['song', songId, 'artist'],
|
||||||
() => API.getArtist(songQuery.data!.artistId!),
|
() => 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 perfoamnceRecommandationsQuery = useQuery(['song', props.songId, 'score', 'latest', 'recommendations'], () => API.getLastSongPerformanceScore(props.songId));
|
||||||
const recommendations = useQuery(["song", "recommendations"], () =>
|
const recommendations = useQuery(['song', 'recommendations'], () => API.getSongSuggestions());
|
||||||
API.getSongSuggestions()
|
|
||||||
);
|
|
||||||
const artistRecommendations = useQueries(
|
const artistRecommendations = useQueries(
|
||||||
recommendations.data
|
recommendations.data
|
||||||
?.filter(({ artistId }) => artistId !== null)
|
?.filter(({ artistId }) => artistId !== null)
|
||||||
.map((song) => ({
|
.map((song) => ({
|
||||||
queryKey: ["artist", song.artistId],
|
queryKey: ['artist', song.artistId],
|
||||||
queryFn: () => API.getArtist(song.artistId!),
|
queryFn: () => API.getArtist(song.artistId!),
|
||||||
})) ?? []
|
})) ?? []
|
||||||
);
|
);
|
||||||
@@ -71,13 +56,13 @@ const ScoreView = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView p={8} contentContainerStyle={{ alignItems: "center" }}>
|
<ScrollView p={8} contentContainerStyle={{ alignItems: 'center' }}>
|
||||||
<VStack width={{ base: "100%", lg: "50%" }} textAlign="center">
|
<VStack width={{ base: '100%', lg: '50%' }} textAlign="center">
|
||||||
<Text bold fontSize="lg">
|
<Text bold fontSize="lg">
|
||||||
{songQuery.data.name}
|
{songQuery.data.name}
|
||||||
</Text>
|
</Text>
|
||||||
<Text bold>{artistQuery.data?.name}</Text>
|
<Text bold>{artistQuery.data?.name}</Text>
|
||||||
<Row style={{ justifyContent: "center", display: "flex" }}>
|
<Row style={{ justifyContent: 'center', display: 'flex' }}>
|
||||||
<Card shadow={3} style={{ flex: 1 }}>
|
<Card shadow={3} style={{ flex: 1 }}>
|
||||||
<Image
|
<Image
|
||||||
style={{
|
style={{
|
||||||
@@ -90,7 +75,7 @@ const ScoreView = ({
|
|||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
<Card shadow={3} style={{ flex: 1 }}>
|
<Card shadow={3} style={{ flex: 1 }}>
|
||||||
<Column style={{ justifyContent: "space-evenly", flexGrow: 1 }}>
|
<Column style={{ justifyContent: 'space-evenly', flexGrow: 1 }}>
|
||||||
{/*<Row style={{ alignItems: 'center' }}>
|
{/*<Row style={{ alignItems: 'center' }}>
|
||||||
<Text bold fontSize='xl'>
|
<Text bold fontSize='xl'>
|
||||||
|
|
||||||
@@ -103,67 +88,66 @@ const ScoreView = ({
|
|||||||
</Text>
|
</Text>
|
||||||
<Translate translationKey='goodNotesInARow' format={(t) => ' ' + t}/>
|
<Translate translationKey='goodNotesInARow' format={(t) => ' ' + t}/>
|
||||||
</Row>*/}
|
</Row>*/}
|
||||||
<Row style={{ alignItems: "center" }}>
|
<Row style={{ alignItems: 'center' }}>
|
||||||
<Translate translationKey="score" format={(t) => t + " : "} />
|
<Translate translationKey="score" format={(t) => t + ' : '} />
|
||||||
<Text bold fontSize="xl">
|
<Text bold fontSize="xl">
|
||||||
{overallScore + "pts"}
|
{overallScore + 'pts'}
|
||||||
</Text>
|
</Text>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{ alignItems: "center" }}>
|
<Row style={{ alignItems: 'center' }}>
|
||||||
<Translate translationKey="perfect" format={(t) => t + " : "} />
|
<Translate translationKey="perfect" format={(t) => t + ' : '} />
|
||||||
<Text bold fontSize="xl">
|
<Text bold fontSize="xl">
|
||||||
{score.perfect}
|
{score.perfect}
|
||||||
</Text>
|
</Text>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{ alignItems: "center" }}>
|
<Row style={{ alignItems: 'center' }}>
|
||||||
<Translate translationKey="great" format={(t) => t + " : "} />
|
<Translate translationKey="great" format={(t) => t + ' : '} />
|
||||||
<Text bold fontSize="xl">
|
<Text bold fontSize="xl">
|
||||||
{score.great}
|
{score.great}
|
||||||
</Text>
|
</Text>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{ alignItems: "center" }}>
|
<Row style={{ alignItems: 'center' }}>
|
||||||
<Translate translationKey="good" format={(t) => t + " : "} />
|
<Translate translationKey="good" format={(t) => t + ' : '} />
|
||||||
<Text bold fontSize="xl">
|
<Text bold fontSize="xl">
|
||||||
{score.good}
|
{score.good}
|
||||||
</Text>
|
</Text>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{ alignItems: "center" }}>
|
<Row style={{ alignItems: 'center' }}>
|
||||||
<Translate translationKey="wrong" format={(t) => t + " : "} />
|
<Translate translationKey="wrong" format={(t) => t + ' : '} />
|
||||||
<Text bold fontSize="xl">
|
<Text bold fontSize="xl">
|
||||||
{score.wrong}
|
{score.wrong}
|
||||||
</Text>
|
</Text>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Row style={{ alignItems: "center" }}>
|
<Row style={{ alignItems: 'center' }}>
|
||||||
<Translate translationKey="missed" format={(t) => t + " : "} />
|
<Translate translationKey="missed" format={(t) => t + ' : '} />
|
||||||
<Text bold fontSize="xl">
|
<Text bold fontSize="xl">
|
||||||
{score.missed}
|
{score.missed}
|
||||||
</Text>
|
</Text>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{ alignItems: "center" }}>
|
<Row style={{ alignItems: 'center' }}>
|
||||||
<Translate translationKey="bestStreak" format={(t) => t + " : "} />
|
<Translate translationKey="bestStreak" format={(t) => t + ' : '} />
|
||||||
<Text bold fontSize="xl">
|
<Text bold fontSize="xl">
|
||||||
{score.max_streak}
|
{score.max_streak}
|
||||||
</Text>
|
</Text>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{ alignItems: "center" }}>
|
<Row style={{ alignItems: 'center' }}>
|
||||||
<Translate translationKey="precision" format={(t) => t + " : "} />
|
<Translate translationKey="precision" format={(t) => t + ' : '} />
|
||||||
<Text bold fontSize="xl">
|
<Text bold fontSize="xl">
|
||||||
{precision + "%"}
|
{precision + '%'}
|
||||||
</Text>
|
</Text>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
</Column>
|
</Column>
|
||||||
</Card>
|
</Card>
|
||||||
</Row>
|
</Row>
|
||||||
<CardGridCustom
|
<CardGridCustom
|
||||||
style={{ justifyContent: "space-evenly" }}
|
style={{ justifyContent: 'space-evenly' }}
|
||||||
content={recommendations.data.map((i) => ({
|
content={recommendations.data.map((i) => ({
|
||||||
cover: i.cover,
|
cover: i.cover,
|
||||||
name: i.name,
|
name: i.name,
|
||||||
artistName:
|
artistName:
|
||||||
artistRecommendations.find(({ data }) => data?.id == i.artistId)
|
artistRecommendations.find(({ data }) => data?.id == i.artistId)?.data
|
||||||
?.data?.name ?? "",
|
?.name ?? '',
|
||||||
songId: i.id,
|
songId: i.id,
|
||||||
}))}
|
}))}
|
||||||
cardComponent={SongCard}
|
cardComponent={SongCard}
|
||||||
@@ -173,15 +157,15 @@ const ScoreView = ({
|
|||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Row space={3} style={{ width: "100%", justifyContent: "center" }}>
|
<Row space={3} style={{ width: '100%', justifyContent: 'center' }}>
|
||||||
<TextButton
|
<TextButton
|
||||||
colorScheme="gray"
|
colorScheme="gray"
|
||||||
translate={{ translationKey: "backBtn" }}
|
translate={{ translationKey: 'backBtn' }}
|
||||||
onPress={() => navigation.navigate("Home")}
|
onPress={() => navigation.navigate('Home')}
|
||||||
/>
|
/>
|
||||||
<TextButton
|
<TextButton
|
||||||
onPress={() => navigation.navigate("Song", { songId })}
|
onPress={() => navigation.navigate('Song', { songId })}
|
||||||
translate={{ translationKey: "playAgain" }}
|
translate={{ translationKey: 'playAgain' }}
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
</VStack>
|
</VStack>
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from 'react';
|
||||||
import SearchBar from "../components/SearchBar";
|
import SearchBar from '../components/SearchBar';
|
||||||
import Artist from "../models/Artist";
|
import Artist from '../models/Artist';
|
||||||
import Song from "../models/Song";
|
import Song from '../models/Song';
|
||||||
import Genre from "../models/Genre";
|
import Genre from '../models/Genre';
|
||||||
import API from "../API";
|
import API from '../API';
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from 'react-query';
|
||||||
import { SearchResultComponent } from "../components/SearchResult";
|
import { SearchResultComponent } from '../components/SearchResult';
|
||||||
import { SafeAreaView } from "react-native";
|
import { SafeAreaView } from 'react-native';
|
||||||
import { Filter } from "../components/SearchBar";
|
import { Filter } from '../components/SearchBar';
|
||||||
import { ScrollView } from "native-base";
|
import { ScrollView } from 'native-base';
|
||||||
import { RouteProps } from "../Navigation";
|
import { RouteProps } from '../Navigation';
|
||||||
|
|
||||||
interface SearchContextType {
|
interface SearchContextType {
|
||||||
filter: "artist" | "song" | "genre" | "all";
|
filter: 'artist' | 'song' | 'genre' | 'all';
|
||||||
updateFilter: (newData: "artist" | "song" | "genre" | "all") => void;
|
updateFilter: (newData: 'artist' | 'song' | 'genre' | 'all') => void;
|
||||||
stringQuery: string;
|
stringQuery: string;
|
||||||
updateStringQuery: (newData: string) => void;
|
updateStringQuery: (newData: string) => void;
|
||||||
songData: Song[];
|
songData: Song[];
|
||||||
@@ -25,9 +25,9 @@ interface SearchContextType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const SearchContext = React.createContext<SearchContextType>({
|
export const SearchContext = React.createContext<SearchContextType>({
|
||||||
filter: "all",
|
filter: 'all',
|
||||||
updateFilter: () => {},
|
updateFilter: () => {},
|
||||||
stringQuery: "",
|
stringQuery: '',
|
||||||
updateStringQuery: () => {},
|
updateStringQuery: () => {},
|
||||||
songData: [],
|
songData: [],
|
||||||
artistData: [],
|
artistData: [],
|
||||||
@@ -42,24 +42,23 @@ type SearchViewProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SearchView = (props: RouteProps<SearchViewProps>) => {
|
const SearchView = (props: RouteProps<SearchViewProps>) => {
|
||||||
let isRequestSucceeded = false;
|
const [filter, setFilter] = useState<Filter>('all');
|
||||||
const [filter, setFilter] = useState<Filter>("all");
|
const [stringQuery, setStringQuery] = useState<string>(props?.query ?? '');
|
||||||
const [stringQuery, setStringQuery] = useState<string>(props?.query ?? "");
|
|
||||||
|
|
||||||
const { isLoading: isLoadingSong, data: songData = [] } = useQuery(
|
const { isLoading: isLoadingSong, data: songData = [] } = useQuery(
|
||||||
["song", stringQuery],
|
['song', stringQuery],
|
||||||
() => API.searchSongs(stringQuery),
|
() => API.searchSongs(stringQuery),
|
||||||
{ enabled: !!stringQuery }
|
{ enabled: !!stringQuery }
|
||||||
);
|
);
|
||||||
|
|
||||||
const { isLoading: isLoadingArtist, data: artistData = [] } = useQuery(
|
const { isLoading: isLoadingArtist, data: artistData = [] } = useQuery(
|
||||||
["artist", stringQuery],
|
['artist', stringQuery],
|
||||||
() => API.searchArtists(stringQuery),
|
() => API.searchArtists(stringQuery),
|
||||||
{ enabled: !!stringQuery }
|
{ enabled: !!stringQuery }
|
||||||
);
|
);
|
||||||
|
|
||||||
const { isLoading: isLoadingGenre, data: genreData = [] } = useQuery(
|
const { isLoading: isLoadingGenre, data: genreData = [] } = useQuery(
|
||||||
["genre", stringQuery],
|
['genre', stringQuery],
|
||||||
() => API.searchGenres(stringQuery),
|
() => API.searchGenres(stringQuery),
|
||||||
{ enabled: !!stringQuery }
|
{ enabled: !!stringQuery }
|
||||||
);
|
);
|
||||||
@@ -72,7 +71,6 @@ const SearchView = (props: RouteProps<SearchViewProps>) => {
|
|||||||
const updateStringQuery = (newData: string) => {
|
const updateStringQuery = (newData: string) => {
|
||||||
// called when the stringQuery is updated
|
// called when the stringQuery is updated
|
||||||
setStringQuery(newData);
|
setStringQuery(newData);
|
||||||
isRequestSucceeded = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -6,8 +6,12 @@ import store from '../state/Store';
|
|||||||
import AuthenticationView from '../views/AuthenticationView';
|
import AuthenticationView from '../views/AuthenticationView';
|
||||||
|
|
||||||
describe('<AuthenticationView />', () => {
|
describe('<AuthenticationView />', () => {
|
||||||
it('has 3 children', () => {
|
it('has 3 children', () => {
|
||||||
const tree = TestRenderer.create(<Provider store={store}><AuthenticationView /></Provider>).toJSON();
|
const tree = TestRenderer.create(
|
||||||
expect(tree.children.length).toBe(3);
|
<Provider store={store}>
|
||||||
});
|
<AuthenticationView />
|
||||||
});
|
</Provider>
|
||||||
|
).toJSON();
|
||||||
|
expect(tree.children.length).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,23 +1,13 @@
|
|||||||
import {
|
import { Divider, Box, Image, Text, VStack, PresenceTransition, Icon, Stack } from 'native-base';
|
||||||
Divider,
|
import { useQuery } from 'react-query';
|
||||||
Box,
|
import LoadingComponent, { LoadingView } from '../components/Loading';
|
||||||
Center,
|
import React, { useEffect, useState } from 'react';
|
||||||
Image,
|
import { Translate, translate } from '../i18n/i18n';
|
||||||
Text,
|
import formatDuration from 'format-duration';
|
||||||
VStack,
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
PresenceTransition,
|
import API from '../API';
|
||||||
Icon,
|
import TextButton from '../components/TextButton';
|
||||||
Stack,
|
import { RouteProps, useNavigation } from '../Navigation';
|
||||||
} 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 {
|
interface SongLobbyProps {
|
||||||
// The unique identifier to find a song
|
// The unique identifier to find a song
|
||||||
@@ -26,32 +16,30 @@ interface SongLobbyProps {
|
|||||||
|
|
||||||
const SongLobbyView = (props: RouteProps<SongLobbyProps>) => {
|
const SongLobbyView = (props: RouteProps<SongLobbyProps>) => {
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const songQuery = useQuery(["song", props.songId], () =>
|
const songQuery = useQuery(['song', props.songId], () => API.getSong(props.songId));
|
||||||
API.getSong(props.songId)
|
const chaptersQuery = useQuery(['song', props.songId, 'chapters'], () =>
|
||||||
);
|
|
||||||
const chaptersQuery = useQuery(["song", props.songId, "chapters"], () =>
|
|
||||||
API.getSongChapters(props.songId)
|
API.getSongChapters(props.songId)
|
||||||
);
|
);
|
||||||
const scoresQuery = useQuery(["song", props.songId, "scores"], () =>
|
const scoresQuery = useQuery(['song', props.songId, 'scores'], () =>
|
||||||
API.getSongHistory(props.songId)
|
API.getSongHistory(props.songId)
|
||||||
);
|
);
|
||||||
const [chaptersOpen, setChaptersOpen] = useState(false);
|
const [chaptersOpen, setChaptersOpen] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (chaptersOpen && !chaptersQuery.data) chaptersQuery.refetch();
|
if (chaptersOpen && !chaptersQuery.data) chaptersQuery.refetch();
|
||||||
}, [chaptersOpen]);
|
}, [chaptersOpen]);
|
||||||
useEffect(() => { }, [songQuery.isLoading]);
|
useEffect(() => {}, [songQuery.isLoading]);
|
||||||
if (songQuery.isLoading || scoresQuery.isLoading) return <LoadingView />;
|
if (songQuery.isLoading || scoresQuery.isLoading) return <LoadingView />;
|
||||||
return (
|
return (
|
||||||
<Box style={{ padding: 30, flexDirection: "column" }}>
|
<Box style={{ padding: 30, flexDirection: 'column' }}>
|
||||||
<Box style={{ flexDirection: "row", height: "30%" }}>
|
<Box style={{ flexDirection: 'row', height: '30%' }}>
|
||||||
<Box style={{ flex: 3 }}>
|
<Box style={{ flex: 3 }}>
|
||||||
<Image
|
<Image
|
||||||
source={{ uri: songQuery.data!.cover }}
|
source={{ uri: songQuery.data!.cover }}
|
||||||
alt={songQuery.data?.name}
|
alt={songQuery.data?.name}
|
||||||
style={{
|
style={{
|
||||||
height: "100%",
|
height: '100%',
|
||||||
width: undefined,
|
width: undefined,
|
||||||
resizeMode: "contain",
|
resizeMode: 'contain',
|
||||||
aspectRatio: 1,
|
aspectRatio: 1,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -61,8 +49,8 @@ const SongLobbyView = (props: RouteProps<SongLobbyProps>) => {
|
|||||||
style={{
|
style={{
|
||||||
flex: 3,
|
flex: 3,
|
||||||
padding: 10,
|
padding: 10,
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
justifyContent: "space-between",
|
justifyContent: 'space-between',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack flex={1} space={3}>
|
<Stack flex={1} space={3}>
|
||||||
@@ -73,30 +61,31 @@ const SongLobbyView = (props: RouteProps<SongLobbyProps>) => {
|
|||||||
<Translate
|
<Translate
|
||||||
translationKey="level"
|
translationKey="level"
|
||||||
format={(level) =>
|
format={(level) =>
|
||||||
`${level}: ${chaptersQuery.data!.reduce((a, b) => a + b.difficulty, 0) /
|
`${level}: ${
|
||||||
chaptersQuery.data!.length
|
chaptersQuery.data!.reduce((a, b) => a + b.difficulty, 0) /
|
||||||
|
chaptersQuery.data!.length
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Text>
|
</Text>
|
||||||
<TextButton
|
<TextButton
|
||||||
translate={{ translationKey: "playBtn" }}
|
translate={{ translationKey: 'playBtn' }}
|
||||||
width="auto"
|
width="auto"
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
navigation.navigate("Play", {
|
navigation.navigate('Play', {
|
||||||
songId: songQuery.data!.id,
|
songId: songQuery.data!.id,
|
||||||
type: "normal",
|
type: 'normal',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
rightIcon={<Icon as={Ionicons} name="play-outline" />}
|
rightIcon={<Icon as={Ionicons} name="play-outline" />}
|
||||||
/>
|
/>
|
||||||
<TextButton
|
<TextButton
|
||||||
translate={{ translationKey: "practiceBtn" }}
|
translate={{ translationKey: 'practiceBtn' }}
|
||||||
width="auto"
|
width="auto"
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
navigation.navigate("Play", {
|
navigation.navigate('Play', {
|
||||||
songId: songQuery.data!.id,
|
songId: songQuery.data!.id,
|
||||||
type: "practice",
|
type: 'practice',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
rightIcon={<Icon as={Ionicons} name="play-outline" />}
|
rightIcon={<Icon as={Ionicons} name="play-outline" />}
|
||||||
@@ -107,18 +96,18 @@ const SongLobbyView = (props: RouteProps<SongLobbyProps>) => {
|
|||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
flexDirection: "row",
|
flexDirection: 'row',
|
||||||
justifyContent: "space-between",
|
justifyContent: 'space-between',
|
||||||
padding: 30,
|
padding: 30,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box style={{ flexDirection: "column", alignItems: "center" }}>
|
<Box style={{ flexDirection: 'column', alignItems: 'center' }}>
|
||||||
<Text bold fontSize="lg">
|
<Text bold fontSize="lg">
|
||||||
<Translate translationKey="bestScore" />
|
<Translate translationKey="bestScore" />
|
||||||
</Text>
|
</Text>
|
||||||
<Text>{scoresQuery.data?.best ?? 0}</Text>
|
<Text>{scoresQuery.data?.best ?? 0}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box style={{ flexDirection: "column", alignItems: "center" }}>
|
<Box style={{ flexDirection: 'column', alignItems: 'center' }}>
|
||||||
<Text bold fontSize="lg">
|
<Text bold fontSize="lg">
|
||||||
<Translate translationKey="lastScore" />
|
<Translate translationKey="lastScore" />
|
||||||
</Text>
|
</Text>
|
||||||
@@ -128,15 +117,13 @@ const SongLobbyView = (props: RouteProps<SongLobbyProps>) => {
|
|||||||
{/* <Text style={{ paddingBottom: 10 }}>{songQuery.data!.description}</Text> */}
|
{/* <Text style={{ paddingBottom: 10 }}>{songQuery.data!.description}</Text> */}
|
||||||
<Box flexDirection="row">
|
<Box flexDirection="row">
|
||||||
<TextButton
|
<TextButton
|
||||||
translate={{ translationKey: "chapters" }}
|
translate={{ translationKey: 'chapters' }}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onPress={() => setChaptersOpen(!chaptersOpen)}
|
onPress={() => setChaptersOpen(!chaptersOpen)}
|
||||||
endIcon={
|
endIcon={
|
||||||
<Icon
|
<Icon
|
||||||
as={Ionicons}
|
as={Ionicons}
|
||||||
name={
|
name={chaptersOpen ? 'chevron-up-outline' : 'chevron-down-outline'}
|
||||||
chaptersOpen ? "chevron-up-outline" : "chevron-down-outline"
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -154,8 +141,9 @@ const SongLobbyView = (props: RouteProps<SongLobbyProps>) => {
|
|||||||
>
|
>
|
||||||
<Text>{chapter.name}</Text>
|
<Text>{chapter.name}</Text>
|
||||||
<Text>
|
<Text>
|
||||||
{`${translate("level")} ${chapter.difficulty
|
{`${translate('level')} ${
|
||||||
} - ${formatDuration((chapter.end - chapter.start) * 1000)}`}
|
chapter.difficulty
|
||||||
|
} - ${formatDuration((chapter.end - chapter.start) * 1000)}`}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import { useNavigation } from "../Navigation";
|
import { useNavigation } from '../Navigation';
|
||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
Text,
|
Text,
|
||||||
@@ -15,54 +15,52 @@ import {
|
|||||||
Row,
|
Row,
|
||||||
Heading,
|
Heading,
|
||||||
Icon,
|
Icon,
|
||||||
} from "native-base";
|
} from 'native-base';
|
||||||
import { FontAwesome5 } from "@expo/vector-icons";
|
import { FontAwesome5 } from '@expo/vector-icons';
|
||||||
import BigActionButton from "../components/BigActionButton";
|
import BigActionButton from '../components/BigActionButton';
|
||||||
import API, { APIError } from "../API";
|
import API, { APIError } from '../API';
|
||||||
import { setAccessToken } from "../state/UserSlice";
|
import { setAccessToken } from '../state/UserSlice';
|
||||||
import { useDispatch } from "../state/Store";
|
import { useDispatch } from '../state/Store';
|
||||||
import { translate } from "../i18n/i18n";
|
import { translate } from '../i18n/i18n';
|
||||||
|
|
||||||
const handleGuestLogin = async (
|
const handleGuestLogin = async (apiSetter: (accessToken: string) => void): Promise<string> => {
|
||||||
apiSetter: (accessToken: string) => void
|
|
||||||
): Promise<string> => {
|
|
||||||
const apiAccess = await API.createAndGetGuestAccount();
|
const apiAccess = await API.createAndGetGuestAccount();
|
||||||
apiSetter(apiAccess);
|
apiSetter(apiAccess);
|
||||||
return translate("loggedIn");
|
return translate('loggedIn');
|
||||||
};
|
};
|
||||||
|
|
||||||
const imgLogin =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 StartPageView = () => {
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const screenSize = useBreakpointValue({ base: "small", md: "big" });
|
const screenSize = useBreakpointValue({ base: 'small', md: 'big' });
|
||||||
const isSmallScreen = screenSize === "small";
|
const isSmallScreen = screenSize === 'small';
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: '100%',
|
||||||
height: "100%",
|
height: '100%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Center>
|
<Center>
|
||||||
<Row
|
<Row
|
||||||
style={{
|
style={{
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -75,17 +73,17 @@ const StartPageView = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
size={isSmallScreen ? "5xl" : "6xl"}
|
size={isSmallScreen ? '5xl' : '6xl'}
|
||||||
/>
|
/>
|
||||||
<Heading fontSize={isSmallScreen ? "3xl" : "5xl"}>Chromacase</Heading>
|
<Heading fontSize={isSmallScreen ? '3xl' : '5xl'}>Chromacase</Heading>
|
||||||
</Row>
|
</Row>
|
||||||
</Center>
|
</Center>
|
||||||
<Stack
|
<Stack
|
||||||
direction={screenSize === "small" ? "column" : "row"}
|
direction={screenSize === 'small' ? 'column' : 'row'}
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: '100%',
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<BigActionButton
|
<BigActionButton
|
||||||
@@ -94,11 +92,11 @@ const StartPageView = () => {
|
|||||||
image={imgLogin}
|
image={imgLogin}
|
||||||
iconName="user"
|
iconName="user"
|
||||||
iconProvider={FontAwesome5}
|
iconProvider={FontAwesome5}
|
||||||
onPress={() => navigation.navigate("Login", { isSignup: false })}
|
onPress={() => navigation.navigate('Login', { isSignup: false })}
|
||||||
style={{
|
style={{
|
||||||
width: isSmallScreen ? "90%" : "clamp(100px, 33.3%, 600px)",
|
width: isSmallScreen ? '90%' : 'clamp(100px, 33.3%, 600px)',
|
||||||
height: "300px",
|
height: '300px',
|
||||||
margin: "clamp(10px, 2%, 50px)",
|
margin: 'clamp(10px, 2%, 50px)',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<BigActionButton
|
<BigActionButton
|
||||||
@@ -121,9 +119,9 @@ const StartPageView = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
width: isSmallScreen ? "90%" : "clamp(100px, 33.3%, 600px)",
|
width: isSmallScreen ? '90%' : 'clamp(100px, 33.3%, 600px)',
|
||||||
height: "300px",
|
height: '300px',
|
||||||
margin: "clamp(10px, 2%, 50px)",
|
margin: 'clamp(10px, 2%, 50px)',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -134,60 +132,60 @@ const StartPageView = () => {
|
|||||||
subtitle="Create an account to save your progress"
|
subtitle="Create an account to save your progress"
|
||||||
iconProvider={FontAwesome5}
|
iconProvider={FontAwesome5}
|
||||||
iconName="user-plus"
|
iconName="user-plus"
|
||||||
onPress={() => navigation.navigate("Login", { isSignup: true })}
|
onPress={() => navigation.navigate('Login', { isSignup: true })}
|
||||||
style={{
|
style={{
|
||||||
height: "150px",
|
height: '150px',
|
||||||
width: isSmallScreen ? "90%" : "clamp(150px, 50%, 600px)",
|
width: isSmallScreen ? '90%' : 'clamp(150px, 50%, 600px)',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
<Column
|
<Column
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: '100%',
|
||||||
marginTop: 40,
|
marginTop: 40,
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
maxWidth: "90%",
|
maxWidth: '90%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading fontSize="4xl" style={{ textAlign: "center" }}>
|
<Heading fontSize="4xl" style={{ textAlign: 'center' }}>
|
||||||
What is Chromacase?
|
What is Chromacase?
|
||||||
</Heading>
|
</Heading>
|
||||||
<Text fontSize={"xl"}>
|
<Text fontSize={'xl'}>
|
||||||
Chromacase is a free and open source project that aims to provide a
|
Chromacase is a free and open source project that aims to provide a complete
|
||||||
complete learning experience for anyone willing to learn piano.
|
learning experience for anyone willing to learn piano.
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
width: "90%",
|
width: '90%',
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: '100%',
|
||||||
height: "100%",
|
height: '100%',
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
href="https://chromacase.studio"
|
href="https://chromacase.studio"
|
||||||
isExternal
|
isExternal
|
||||||
style={{
|
style={{
|
||||||
width: "clamp(200px, 100%, 700px)",
|
width: 'clamp(200px, 100%, 700px)',
|
||||||
position: "relative",
|
position: 'relative',
|
||||||
overflow: "hidden",
|
overflow: 'hidden',
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AspectRatio ratio={40 / 9} style={{ width: "100%" }}>
|
<AspectRatio ratio={40 / 9} style={{ width: '100%' }}>
|
||||||
<Image
|
<Image
|
||||||
alt="Chromacase Banner"
|
alt="Chromacase Banner"
|
||||||
source={{ uri: imgBanner }}
|
source={{ uri: imgBanner }}
|
||||||
@@ -196,22 +194,22 @@ const StartPageView = () => {
|
|||||||
</AspectRatio>
|
</AspectRatio>
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: 'absolute',
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
width: "100%",
|
width: '100%',
|
||||||
height: "100%",
|
height: '100%',
|
||||||
backgroundColor: "rgba(0,0,0,0.5)",
|
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||||
}}
|
}}
|
||||||
></Box>
|
></Box>
|
||||||
<Heading
|
<Heading
|
||||||
fontSize="2xl"
|
fontSize="2xl"
|
||||||
style={{
|
style={{
|
||||||
textAlign: "center",
|
textAlign: 'center',
|
||||||
position: "absolute",
|
position: 'absolute',
|
||||||
top: "40%",
|
top: '40%',
|
||||||
left: 20,
|
left: 20,
|
||||||
color: "white",
|
color: 'white',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Click here for more infos
|
Click here for more infos
|
||||||
|
|||||||
@@ -1,31 +1,27 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import SignUpForm from "../../components/forms/signupform";
|
import SignUpForm from '../../components/forms/signupform';
|
||||||
import { Center, Heading, Text } from "native-base";
|
import { Center, Heading, Text } from 'native-base';
|
||||||
import API, { APIError } from "../../API";
|
import API, { APIError } from '../../API';
|
||||||
import { translate } from "../../i18n/i18n";
|
import { translate } from '../../i18n/i18n';
|
||||||
|
|
||||||
const handleSubmit = async (
|
const handleSubmit = async (username: string, password: string, email: string) => {
|
||||||
username: string,
|
|
||||||
password: string,
|
|
||||||
email: string
|
|
||||||
) => {
|
|
||||||
try {
|
try {
|
||||||
await API.transformGuestToUser({ username, password, email });
|
await API.transformGuestToUser({ username, password, email });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof APIError) return translate(error.userMessage);
|
if (error instanceof APIError) return translate(error.userMessage);
|
||||||
if (error instanceof Error) return error.message;
|
if (error instanceof Error) return error.message;
|
||||||
return translate("unknownError");
|
return translate('unknownError');
|
||||||
}
|
}
|
||||||
return translate("loggedIn");
|
return translate('loggedIn');
|
||||||
};
|
};
|
||||||
|
|
||||||
const GuestToUserView = () => {
|
const GuestToUserView = () => {
|
||||||
return (
|
return (
|
||||||
<Center flex={1} justifyContent={"center"}>
|
<Center flex={1} justifyContent={'center'}>
|
||||||
<Center width="90%" justifyContent={"center"}>
|
<Center width="90%" justifyContent={'center'}>
|
||||||
<Heading>{translate("signUp")}</Heading>
|
<Heading>{translate('signUp')}</Heading>
|
||||||
<Text mt={5} mb={10}>
|
<Text mt={5} mb={10}>
|
||||||
{translate("transformGuestToUserExplanations")}
|
{translate('transformGuestToUserExplanations')}
|
||||||
</Text>
|
</Text>
|
||||||
<SignUpForm
|
<SignUpForm
|
||||||
onSubmit={(username, password, email) =>
|
onSubmit={(username, password, email) =>
|
||||||
|
|||||||
@@ -1,72 +1,80 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import { Center, Heading } from "native-base";
|
import { Center, Heading } from 'native-base';
|
||||||
import { translate, Translate } from "../../i18n/i18n";
|
import { translate, Translate } from '../../i18n/i18n';
|
||||||
import ElementList from "../../components/GtkUI/ElementList";
|
import ElementList from '../../components/GtkUI/ElementList';
|
||||||
import useUserSettings from "../../hooks/userSettings";
|
import useUserSettings from '../../hooks/userSettings';
|
||||||
import { LoadingView } from "../../components/Loading";
|
import { LoadingView } from '../../components/Loading';
|
||||||
|
|
||||||
const NotificationsView = () => {
|
const NotificationsView = () => {
|
||||||
const { settings, updateSettings } = useUserSettings();
|
const { settings, updateSettings } = useUserSettings();
|
||||||
|
|
||||||
if (!settings.data) {
|
if (!settings.data) {
|
||||||
return <LoadingView/>
|
return <LoadingView />;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Center style={{ flex: 1, justifyContent: "center" }}>
|
<Center style={{ flex: 1, justifyContent: 'center' }}>
|
||||||
<Heading style={{ textAlign: "center" }}>
|
<Heading style={{ textAlign: 'center' }}>
|
||||||
<Translate translationKey="notifBtn" />
|
<Translate translationKey="notifBtn" />
|
||||||
</Heading>
|
</Heading>
|
||||||
<ElementList
|
<ElementList
|
||||||
style={{
|
style={{
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
width: "90%",
|
width: '90%',
|
||||||
maxWidth: 850,
|
maxWidth: 850,
|
||||||
}}
|
}}
|
||||||
elements={[
|
elements={[
|
||||||
{
|
{
|
||||||
type: "toggle",
|
type: 'toggle',
|
||||||
title: translate("SettingsNotificationsPushNotifications"),
|
title: translate('SettingsNotificationsPushNotifications'),
|
||||||
data: {
|
data: {
|
||||||
value: settings.data.notifications.pushNotif,
|
value: settings.data.notifications.pushNotif,
|
||||||
onToggle: () => {
|
onToggle: () => {
|
||||||
updateSettings({
|
updateSettings({
|
||||||
notifications: { pushNotif: !settings.data.notifications.pushNotif },
|
notifications: {
|
||||||
|
pushNotif: !settings.data.notifications.pushNotif,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "toggle",
|
type: 'toggle',
|
||||||
title: translate("SettingsNotificationsEmailNotifications"),
|
title: translate('SettingsNotificationsEmailNotifications'),
|
||||||
data: {
|
data: {
|
||||||
value: settings.data.notifications.emailNotif,
|
value: settings.data.notifications.emailNotif,
|
||||||
onToggle: () => {
|
onToggle: () => {
|
||||||
updateSettings({
|
updateSettings({
|
||||||
notifications: { emailNotif: !settings.data.notifications.emailNotif },
|
notifications: {
|
||||||
|
emailNotif: !settings.data.notifications.emailNotif,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "toggle",
|
type: 'toggle',
|
||||||
title: translate("SettingsNotificationsTrainingReminder"),
|
title: translate('SettingsNotificationsTrainingReminder'),
|
||||||
data: {
|
data: {
|
||||||
value: settings.data.notifications.trainNotif,
|
value: settings.data.notifications.trainNotif,
|
||||||
onToggle: () => {
|
onToggle: () => {
|
||||||
updateSettings({
|
updateSettings({
|
||||||
notifications: { trainNotif: !settings.data.notifications.trainNotif },
|
notifications: {
|
||||||
|
trainNotif: !settings.data.notifications.trainNotif,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "toggle",
|
type: 'toggle',
|
||||||
title: translate("SettingsNotificationsReleaseAlert"),
|
title: translate('SettingsNotificationsReleaseAlert'),
|
||||||
data: {
|
data: {
|
||||||
value: settings.data.notifications.newSongNotif,
|
value: settings.data.notifications.newSongNotif,
|
||||||
onToggle: () => {
|
onToggle: () => {
|
||||||
updateSettings({
|
updateSettings({
|
||||||
notifications: { newSongNotif: !settings.data.notifications.newSongNotif },
|
notifications: {
|
||||||
|
newSongNotif: !settings.data.notifications.newSongNotif,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from 'react-redux';
|
||||||
import {
|
import { Center, Heading } from 'native-base';
|
||||||
Center,
|
import { useLanguage } from '../../state/LanguageSlice';
|
||||||
Heading,
|
import { AvailableLanguages, DefaultLanguage, translate, Translate } from '../../i18n/i18n';
|
||||||
} from "native-base";
|
import { useSelector } from '../../state/Store';
|
||||||
import { useLanguage } from "../../state/LanguageSlice";
|
import { updateSettings } from '../../state/SettingsSlice';
|
||||||
import {
|
import ElementList from '../../components/GtkUI/ElementList';
|
||||||
AvailableLanguages,
|
import LocalSettings from '../../models/LocalSettings';
|
||||||
DefaultLanguage,
|
|
||||||
translate,
|
|
||||||
Translate,
|
|
||||||
} from "../../i18n/i18n";
|
|
||||||
import { useSelector } from "../../state/Store";
|
|
||||||
import { updateSettings } from "../../state/SettingsSlice";
|
|
||||||
import ElementList from "../../components/GtkUI/ElementList";
|
|
||||||
|
|
||||||
const PreferencesView = () => {
|
const PreferencesView = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@@ -21,37 +14,39 @@ const PreferencesView = () => {
|
|||||||
const settings = useSelector((state) => state.settings.local);
|
const settings = useSelector((state) => state.settings.local);
|
||||||
return (
|
return (
|
||||||
<Center style={{ flex: 1 }}>
|
<Center style={{ flex: 1 }}>
|
||||||
<Heading style={{ textAlign: "center" }}>
|
<Heading style={{ textAlign: 'center' }}>
|
||||||
<Translate translationKey="prefBtn" />
|
<Translate translationKey="prefBtn" />
|
||||||
</Heading>
|
</Heading>
|
||||||
<ElementList
|
<ElementList
|
||||||
style={{
|
style={{
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
width: "90%",
|
width: '90%',
|
||||||
maxWidth: 850,
|
maxWidth: 850,
|
||||||
}}
|
}}
|
||||||
elements={[
|
elements={[
|
||||||
{
|
{
|
||||||
type: "dropdown",
|
type: 'dropdown',
|
||||||
title: translate("SettingsPreferencesTheme"),
|
title: translate('SettingsPreferencesTheme'),
|
||||||
data: {
|
data: {
|
||||||
value: settings.colorScheme,
|
value: settings.colorScheme,
|
||||||
defaultValue: "system",
|
defaultValue: 'system',
|
||||||
onSelect: (newColorScheme) => {
|
onSelect: (newColorScheme) => {
|
||||||
dispatch(
|
dispatch(
|
||||||
updateSettings({ colorScheme: newColorScheme as any })
|
updateSettings({
|
||||||
|
colorScheme: newColorScheme as LocalSettings['colorScheme'],
|
||||||
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
options: [
|
options: [
|
||||||
{ label: translate("dark"), value: "dark" },
|
{ label: translate('dark'), value: 'dark' },
|
||||||
{ label: translate("light"), value: "light" },
|
{ label: translate('light'), value: 'light' },
|
||||||
{ label: translate("system"), value: "system" },
|
{ label: translate('system'), value: 'system' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "dropdown",
|
type: 'dropdown',
|
||||||
title: translate("SettingsPreferencesLanguage"),
|
title: translate('SettingsPreferencesLanguage'),
|
||||||
data: {
|
data: {
|
||||||
value: language,
|
value: language,
|
||||||
defaultValue: DefaultLanguage,
|
defaultValue: DefaultLanguage,
|
||||||
@@ -59,25 +54,29 @@ const PreferencesView = () => {
|
|||||||
dispatch(useLanguage(itemValue as AvailableLanguages));
|
dispatch(useLanguage(itemValue as AvailableLanguages));
|
||||||
},
|
},
|
||||||
options: [
|
options: [
|
||||||
{ label: "Français", value: "fr" },
|
{ label: 'Français', value: 'fr' },
|
||||||
{ label: "English", value: "en" },
|
{ label: 'English', value: 'en' },
|
||||||
{ label: "Espanol", value: "sp" },
|
{ label: 'Espanol', value: 'sp' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "dropdown",
|
type: 'dropdown',
|
||||||
title: translate("SettingsPreferencesDifficulty"),
|
title: translate('SettingsPreferencesDifficulty'),
|
||||||
data: {
|
data: {
|
||||||
value: settings.difficulty,
|
value: settings.difficulty,
|
||||||
defaultValue: "medium",
|
defaultValue: 'medium',
|
||||||
onSelect: (itemValue) => {
|
onSelect: (itemValue) => {
|
||||||
dispatch(updateSettings({ difficulty: itemValue as any }));
|
dispatch(
|
||||||
|
updateSettings({
|
||||||
|
difficulty: itemValue as LocalSettings['difficulty'],
|
||||||
|
})
|
||||||
|
);
|
||||||
},
|
},
|
||||||
options: [
|
options: [
|
||||||
{ label: translate("easy"), value: "beg" },
|
{ label: translate('easy'), value: 'beg' },
|
||||||
{ label: translate("medium"), value: "inter" },
|
{ label: translate('medium'), value: 'inter' },
|
||||||
{ label: translate("hard"), value: "pro" },
|
{ label: translate('hard'), value: 'pro' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -86,13 +85,13 @@ const PreferencesView = () => {
|
|||||||
<ElementList
|
<ElementList
|
||||||
style={{
|
style={{
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
width: "90%",
|
width: '90%',
|
||||||
maxWidth: 850,
|
maxWidth: 850,
|
||||||
}}
|
}}
|
||||||
elements={[
|
elements={[
|
||||||
{
|
{
|
||||||
type: "toggle",
|
type: 'toggle',
|
||||||
title: translate("SettingsPreferencesColorblindMode"),
|
title: translate('SettingsPreferencesColorblindMode'),
|
||||||
data: {
|
data: {
|
||||||
value: settings.colorBlind,
|
value: settings.colorBlind,
|
||||||
onToggle: () => {
|
onToggle: () => {
|
||||||
@@ -105,13 +104,13 @@ const PreferencesView = () => {
|
|||||||
<ElementList
|
<ElementList
|
||||||
style={{
|
style={{
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
width: "90%",
|
width: '90%',
|
||||||
maxWidth: 850,
|
maxWidth: 850,
|
||||||
}}
|
}}
|
||||||
elements={[
|
elements={[
|
||||||
{
|
{
|
||||||
type: "range",
|
type: 'range',
|
||||||
title: translate("SettingsPreferencesMicVolume"),
|
title: translate('SettingsPreferencesMicVolume'),
|
||||||
data: {
|
data: {
|
||||||
value: settings.micVolume,
|
value: settings.micVolume,
|
||||||
min: 0,
|
min: 0,
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import { Center, Heading } from "native-base";
|
import { Center, Heading } from 'native-base';
|
||||||
import { translate } from "../../i18n/i18n";
|
import { translate } from '../../i18n/i18n';
|
||||||
import ElementList from "../../components/GtkUI/ElementList";
|
import ElementList from '../../components/GtkUI/ElementList';
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from 'react-redux';
|
||||||
import { RootState, useSelector } from "../../state/Store";
|
import { RootState, useSelector } from '../../state/Store';
|
||||||
import { updateSettings } from "../../state/SettingsSlice";
|
import { updateSettings } from '../../state/SettingsSlice';
|
||||||
import useUserSettings from "../../hooks/userSettings";
|
import useUserSettings from '../../hooks/userSettings';
|
||||||
import { LoadingView } from "../../components/Loading";
|
import { LoadingView } from '../../components/Loading';
|
||||||
|
|
||||||
const PrivacyView = () => {
|
const PrivacyView = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@@ -14,22 +14,22 @@ const PrivacyView = () => {
|
|||||||
const { settings: userSettings, updateSettings: updateUserSettings } = useUserSettings();
|
const { settings: userSettings, updateSettings: updateUserSettings } = useUserSettings();
|
||||||
|
|
||||||
if (!userSettings.data) {
|
if (!userSettings.data) {
|
||||||
return <LoadingView/>;
|
return <LoadingView />;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Center style={{ flex: 1 }}>
|
<Center style={{ flex: 1 }}>
|
||||||
<Heading style={{ textAlign: "center" }}>{translate("privBtn")}</Heading>
|
<Heading style={{ textAlign: 'center' }}>{translate('privBtn')}</Heading>
|
||||||
|
|
||||||
<ElementList
|
<ElementList
|
||||||
style={{
|
style={{
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
width: "90%",
|
width: '90%',
|
||||||
maxWidth: 850,
|
maxWidth: 850,
|
||||||
}}
|
}}
|
||||||
elements={[
|
elements={[
|
||||||
{
|
{
|
||||||
type: "toggle",
|
type: 'toggle',
|
||||||
title: translate("dataCollection"),
|
title: translate('dataCollection'),
|
||||||
data: {
|
data: {
|
||||||
value: settings.dataCollection,
|
value: settings.dataCollection,
|
||||||
onToggle: () =>
|
onToggle: () =>
|
||||||
@@ -39,8 +39,8 @@ const PrivacyView = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "toggle",
|
type: 'toggle',
|
||||||
title: translate("customAds"),
|
title: translate('customAds'),
|
||||||
data: {
|
data: {
|
||||||
value: settings.customAds,
|
value: settings.customAds,
|
||||||
onToggle: () =>
|
onToggle: () =>
|
||||||
@@ -48,12 +48,14 @@ const PrivacyView = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "toggle",
|
type: 'toggle',
|
||||||
title: translate("recommendations"),
|
title: translate('recommendations'),
|
||||||
data: {
|
data: {
|
||||||
value: userSettings.data.recommendations,
|
value: userSettings.data.recommendations,
|
||||||
onToggle: () =>
|
onToggle: () =>
|
||||||
updateUserSettings({ recommendations: !userSettings.data.recommendations })
|
updateUserSettings({
|
||||||
|
recommendations: !userSettings.data.recommendations,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|||||||
@@ -1,48 +1,43 @@
|
|||||||
import API from "../../API";
|
import API from '../../API';
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from 'react-redux';
|
||||||
import { unsetAccessToken } from "../../state/UserSlice";
|
import { unsetAccessToken } from '../../state/UserSlice';
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {
|
import { Column, Text, Button, Box, Flex, Center, Heading, Avatar, Popover } from 'native-base';
|
||||||
Column,
|
import TextButton from '../../components/TextButton';
|
||||||
Text,
|
import { LoadingView } from '../../components/Loading';
|
||||||
Button,
|
import ElementList from '../../components/GtkUI/ElementList';
|
||||||
Box,
|
import { translate } from '../../i18n/i18n';
|
||||||
Flex,
|
import { useQuery } from 'react-query';
|
||||||
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) => {
|
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 ProfileSettings = ({ navigation }: { navigation: any }) => {
|
||||||
const userQuery = useQuery(['user'], () => API.getUserInfo());
|
const userQuery = useQuery(['user'], () => API.getUserInfo());
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
if (!userQuery.data || userQuery.isLoading) {
|
if (!userQuery.data || userQuery.isLoading) {
|
||||||
return <LoadingView/>
|
return <LoadingView />;
|
||||||
}
|
}
|
||||||
const user = userQuery.data;
|
const user = userQuery.data;
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
paddingTop: 40,
|
paddingTop: 40,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Column
|
<Column
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: '100%',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Center>
|
<Center>
|
||||||
@@ -53,17 +48,17 @@ const ProfileSettings = ({ navigation }: { navigation: any }) => {
|
|||||||
<ElementList
|
<ElementList
|
||||||
style={{
|
style={{
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
width: "90%",
|
width: '90%',
|
||||||
maxWidth: 850,
|
maxWidth: 850,
|
||||||
}}
|
}}
|
||||||
elements={[
|
elements={[
|
||||||
{
|
{
|
||||||
type: "text",
|
type: 'text',
|
||||||
title: translate("email"),
|
title: translate('email'),
|
||||||
data: {
|
data: {
|
||||||
text: user.email || translate("NoAssociatedEmail"),
|
text: user.email || translate('NoAssociatedEmail'),
|
||||||
onPress: () => {
|
onPress: () => {
|
||||||
navigation.navigate("ChangeEmail");
|
navigation.navigate('ChangeEmail');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -73,54 +68,54 @@ const ProfileSettings = ({ navigation }: { navigation: any }) => {
|
|||||||
<ElementList
|
<ElementList
|
||||||
style={{
|
style={{
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
width: "90%",
|
width: '90%',
|
||||||
maxWidth: 850,
|
maxWidth: 850,
|
||||||
}}
|
}}
|
||||||
elements={[
|
elements={[
|
||||||
{
|
{
|
||||||
type: "text",
|
type: 'text',
|
||||||
title: translate("username"),
|
title: translate('username'),
|
||||||
data: {
|
data: {
|
||||||
text: user.name,
|
text: user.name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "text",
|
type: 'text',
|
||||||
title: "ID",
|
title: 'ID',
|
||||||
helperText: "This is your unique ID, be proud of it!",
|
helperText: 'This is your unique ID, be proud of it!',
|
||||||
data: {
|
data: {
|
||||||
text: user.id.toString(),
|
text: user.id.toString(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "text",
|
type: 'text',
|
||||||
title: translate("nbGamesPlayed"),
|
title: translate('nbGamesPlayed'),
|
||||||
data: {
|
data: {
|
||||||
text: user.data.gamesPlayed.toString(),
|
text: user.data.gamesPlayed.toString(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "text",
|
type: 'text',
|
||||||
title: "XP",
|
title: 'XP',
|
||||||
description: translate("XPDescription"),
|
description: translate('XPDescription'),
|
||||||
data: {
|
data: {
|
||||||
text: user.data.xp.toString(),
|
text: user.data.xp.toString(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "text",
|
type: 'text',
|
||||||
title: translate("userCreatedAt"),
|
title: translate('userCreatedAt'),
|
||||||
helperText:
|
helperText:
|
||||||
"La date de création est actuellement arbitraire car le serveur ne retourne pas cette information",
|
'La date de création est actuellement arbitraire car le serveur ne retourne pas cette information',
|
||||||
data: {
|
data: {
|
||||||
text: user.data.createdAt.toLocaleDateString(),
|
text: user.data.createdAt.toLocaleDateString(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "text",
|
type: 'text',
|
||||||
title: translate("premiumAccount"),
|
title: translate('premiumAccount'),
|
||||||
data: {
|
data: {
|
||||||
text: translate(user.premium ? "yes" : "no"),
|
text: translate(user.premium ? 'yes' : 'no'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
@@ -131,17 +126,17 @@ const ProfileSettings = ({ navigation }: { navigation: any }) => {
|
|||||||
<ElementList
|
<ElementList
|
||||||
style={{
|
style={{
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
width: "90%",
|
width: '90%',
|
||||||
maxWidth: 850,
|
maxWidth: 850,
|
||||||
}}
|
}}
|
||||||
elements={[
|
elements={[
|
||||||
{
|
{
|
||||||
type: "toggle",
|
type: 'toggle',
|
||||||
title: "Piano Magique",
|
title: 'Piano Magique',
|
||||||
description:
|
description:
|
||||||
"Fait apparaître de la lumière sur le piano pendant les parties",
|
'Fait apparaître de la lumière sur le piano pendant les parties',
|
||||||
helperText:
|
helperText:
|
||||||
"Vous devez posséder le module physique lumineux Chromacase pour pouvoir utiliser cette fonctionnalité",
|
'Vous devez posséder le module physique lumineux Chromacase pour pouvoir utiliser cette fonctionnalité',
|
||||||
disabled: true,
|
disabled: true,
|
||||||
data: {
|
data: {
|
||||||
value: false,
|
value: false,
|
||||||
@@ -149,20 +144,20 @@ const ProfileSettings = ({ navigation }: { navigation: any }) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "dropdown",
|
type: 'dropdown',
|
||||||
title: "Thème de piano",
|
title: 'Thème de piano',
|
||||||
disabled: true,
|
disabled: true,
|
||||||
data: {
|
data: {
|
||||||
value: "default",
|
value: 'default',
|
||||||
onSelect: () => {},
|
onSelect: () => {},
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
label: "Default",
|
label: 'Default',
|
||||||
value: "default",
|
value: 'default',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Catpuccino",
|
label: 'Catpuccino',
|
||||||
value: "catpuccino",
|
value: 'catpuccino',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -176,41 +171,39 @@ const ProfileSettings = ({ navigation }: { navigation: any }) => {
|
|||||||
<TextButton
|
<TextButton
|
||||||
onPress={() => dispatch(unsetAccessToken())}
|
onPress={() => dispatch(unsetAccessToken())}
|
||||||
translate={{
|
translate={{
|
||||||
translationKey: "signOutBtn",
|
translationKey: 'signOutBtn',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{user.isGuest && (
|
{user.isGuest && (
|
||||||
<Popover
|
<Popover
|
||||||
trigger={(triggerProps) => (
|
trigger={(triggerProps) => (
|
||||||
<Button {...triggerProps}>{translate("signOutBtn")}</Button>
|
<Button {...triggerProps}>{translate('signOutBtn')}</Button>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Popover.Content>
|
<Popover.Content>
|
||||||
<Popover.Arrow />
|
<Popover.Arrow />
|
||||||
<Popover.Body>
|
<Popover.Body>
|
||||||
<Heading size="md" mb={2}>
|
<Heading size="md" mb={2}>
|
||||||
{translate("Attention")}
|
{translate('Attention')}
|
||||||
</Heading>
|
</Heading>
|
||||||
<Text>
|
<Text>
|
||||||
{translate(
|
{translate('YouAreCurrentlyConnectedWithAGuestAccountWarning')}
|
||||||
"YouAreCurrentlyConnectedWithAGuestAccountWarning"
|
|
||||||
)}
|
|
||||||
</Text>
|
</Text>
|
||||||
<Button.Group variant="ghost" space={2}>
|
<Button.Group variant="ghost" space={2}>
|
||||||
<Button
|
<Button
|
||||||
onPress={() => dispatch(unsetAccessToken())}
|
onPress={() => dispatch(unsetAccessToken())}
|
||||||
colorScheme="red"
|
colorScheme="red"
|
||||||
>
|
>
|
||||||
{translate("signOutBtn")}
|
{translate('signOutBtn')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
navigation.navigate("GuestToUser");
|
navigation.navigate('GuestToUser');
|
||||||
}}
|
}}
|
||||||
colorScheme="green"
|
colorScheme="green"
|
||||||
>
|
>
|
||||||
{translate("signUpBtn")}
|
{translate('signUpBtn')}
|
||||||
</Button>
|
</Button>
|
||||||
</Button.Group>
|
</Button.Group>
|
||||||
</Popover.Body>
|
</Popover.Body>
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Center, Button, Text, Heading, Box } from "native-base";
|
import { Center, Text, Heading, Box } from 'native-base';
|
||||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
import { translate } from '../../i18n/i18n';
|
||||||
import { unsetAccessToken } from '../../state/UserSlice';
|
|
||||||
import { useDispatch } from "react-redux";
|
|
||||||
import { translate, Translate } from "../../i18n/i18n";
|
|
||||||
import createTabRowNavigator from '../../components/navigators/TabRowNavigator';
|
import createTabRowNavigator from '../../components/navigators/TabRowNavigator';
|
||||||
import { MaterialCommunityIcons, FontAwesome5 } from '@expo/vector-icons';
|
import { MaterialCommunityIcons, FontAwesome5 } from '@expo/vector-icons';
|
||||||
import ChangePasswordForm from '../../components/forms/changePasswordForm';
|
import ChangePasswordForm from '../../components/forms/changePasswordForm';
|
||||||
@@ -17,69 +14,66 @@ import { useQuery } from 'react-query';
|
|||||||
import API from '../../API';
|
import API from '../../API';
|
||||||
|
|
||||||
const handleChangeEmail = async (newEmail: string): Promise<string> => {
|
const handleChangeEmail = async (newEmail: string): Promise<string> => {
|
||||||
try {
|
await API.updateUserEmail(newEmail);
|
||||||
let response = await API.updateUserEmail(newEmail);
|
return translate('emailUpdated');
|
||||||
return translate('emailUpdated');
|
};
|
||||||
} catch (e) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleChangePassword = async (oldPassword: string, newPassword: string): Promise<string> => {
|
const handleChangePassword = async (oldPassword: string, newPassword: string): Promise<string> => {
|
||||||
try {
|
await API.updateUserPassword(oldPassword, newPassword);
|
||||||
let response = await API.updateUserPassword(oldPassword, newPassword);
|
return translate('passwordUpdated');
|
||||||
return translate('passwordUpdated');
|
};
|
||||||
} catch (e) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ChangePasswordView = () => {
|
export const ChangePasswordView = () => {
|
||||||
return (
|
return (
|
||||||
<Center style={{ flex: 1}}>
|
<Center style={{ flex: 1 }}>
|
||||||
<Heading paddingBottom={'2%'}>{translate('changePassword')}</Heading>
|
<Heading paddingBottom={'2%'}>{translate('changePassword')}</Heading>
|
||||||
<ChangePasswordForm onSubmit={(oldPassword, newPassword) => handleChangePassword(oldPassword, newPassword)}/>
|
<ChangePasswordForm
|
||||||
|
onSubmit={(oldPassword, newPassword) =>
|
||||||
|
handleChangePassword(oldPassword, newPassword)
|
||||||
|
}
|
||||||
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const ChangeEmailView = () => {
|
export const ChangeEmailView = () => {
|
||||||
return (
|
return (
|
||||||
<Center style={{ flex: 1}}>
|
<Center style={{ flex: 1 }}>
|
||||||
<Heading paddingBottom={'2%'}>{translate('changeEmail')}</Heading>
|
<Heading paddingBottom={'2%'}>{translate('changeEmail')}</Heading>
|
||||||
<ChangeEmailForm onSubmit={(oldEmail, newEmail) => handleChangeEmail(newEmail)}/>
|
<ChangeEmailForm onSubmit={(oldEmail, newEmail) => handleChangeEmail(newEmail)} />
|
||||||
</Center>
|
</Center>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const GoogleAccountView = () => {
|
export const GoogleAccountView = () => {
|
||||||
return (
|
return (
|
||||||
<Center style={{ flex: 1}}>
|
<Center style={{ flex: 1 }}>
|
||||||
<Text>GoogleAccount</Text>
|
<Text>GoogleAccount</Text>
|
||||||
</Center>
|
</Center>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const PianoSettingsView = () => {
|
export const PianoSettingsView = () => {
|
||||||
return (
|
return (
|
||||||
<Center style={{ flex: 1}}>
|
<Center style={{ flex: 1 }}>
|
||||||
<Text>Global settings for the virtual piano</Text>
|
<Text>Global settings for the virtual piano</Text>
|
||||||
</Center>
|
</Center>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const TabRow = createTabRowNavigator();
|
const TabRow = createTabRowNavigator();
|
||||||
|
|
||||||
type SetttingsNavigatorProps = {
|
type SetttingsNavigatorProps = {
|
||||||
screen?: 'Profile' |
|
screen?:
|
||||||
'Preferences' |
|
| 'Profile'
|
||||||
'Notifications' |
|
| 'Preferences'
|
||||||
'Privacy' |
|
| 'Notifications'
|
||||||
'ChangePassword' |
|
| 'Privacy'
|
||||||
'ChangeEmail' |
|
| 'ChangePassword'
|
||||||
'GoogleAccount' |
|
| 'ChangeEmail'
|
||||||
'PianoSettings'
|
| 'GoogleAccount'
|
||||||
}
|
| 'PianoSettings';
|
||||||
|
};
|
||||||
|
|
||||||
const SetttingsNavigator = (props?: SetttingsNavigatorProps) => {
|
const SetttingsNavigator = (props?: SetttingsNavigatorProps) => {
|
||||||
const userQuery = useQuery(['user'], () => API.getUserInfo());
|
const userQuery = useQuery(['user'], () => API.getUserInfo());
|
||||||
@@ -87,66 +81,106 @@ const SetttingsNavigator = (props?: SetttingsNavigatorProps) => {
|
|||||||
|
|
||||||
if (userQuery.isLoading) {
|
if (userQuery.isLoading) {
|
||||||
return (
|
return (
|
||||||
<Center style={{ flex: 1}}>
|
<Center style={{ flex: 1 }}>
|
||||||
<Text>Loading...</Text>
|
<Text>Loading...</Text>
|
||||||
</Center>
|
</Center>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TabRow.Navigator initialRouteName={props?.screen ?? 'InternalDefault'} contentStyle={{}} tabBarStyle={{}}>
|
<TabRow.Navigator
|
||||||
|
initialRouteName={props?.screen ?? 'InternalDefault'}
|
||||||
|
contentStyle={{}}
|
||||||
|
tabBarStyle={{}}
|
||||||
|
>
|
||||||
{/* I'm doing this to be able to land on the summary of settings when clicking on settings and directly to the
|
{/* 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 */}
|
wanted settings page if needed so I need to do special work with the 0 index */}
|
||||||
<TabRow.Screen name='InternalDefault' component={Box} />
|
<TabRow.Screen name="InternalDefault" component={Box} />
|
||||||
{user && user.isGuest &&
|
{user && user.isGuest && (
|
||||||
<TabRow.Screen name='GuestToUser' component={GuestToUserView} options={{
|
<TabRow.Screen
|
||||||
title: translate('SettingsCategoryGuest'),
|
name="GuestToUser"
|
||||||
|
component={GuestToUserView}
|
||||||
|
options={{
|
||||||
|
title: translate('SettingsCategoryGuest'),
|
||||||
|
iconProvider: FontAwesome5,
|
||||||
|
iconName: 'user-clock',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<TabRow.Screen
|
||||||
|
name="Profile"
|
||||||
|
component={ProfileSettings}
|
||||||
|
options={{
|
||||||
|
title: translate('SettingsCategoryProfile'),
|
||||||
iconProvider: FontAwesome5,
|
iconProvider: FontAwesome5,
|
||||||
iconName: "user-clock"
|
iconName: 'user',
|
||||||
}} />
|
}}
|
||||||
}
|
/>
|
||||||
<TabRow.Screen name='Profile' component={ProfileSettings} options={{
|
<TabRow.Screen
|
||||||
title: translate('SettingsCategoryProfile'),
|
name="Preferences"
|
||||||
iconProvider: FontAwesome5,
|
component={PreferencesView}
|
||||||
iconName: "user"
|
options={{
|
||||||
}} />
|
title: translate('SettingsCategoryPreferences'),
|
||||||
<TabRow.Screen name='Preferences' component={PreferencesView} options={{
|
iconProvider: FontAwesome5,
|
||||||
title: translate('SettingsCategoryPreferences'),
|
iconName: 'music',
|
||||||
iconProvider: FontAwesome5,
|
}}
|
||||||
iconName: "music"
|
/>
|
||||||
}} />
|
<TabRow.Screen
|
||||||
<TabRow.Screen name='Notifications' component={NotificationsView} options={{
|
name="Notifications"
|
||||||
title: translate('SettingsCategoryNotifications'),
|
component={NotificationsView}
|
||||||
iconProvider: FontAwesome5,
|
options={{
|
||||||
iconName: "bell"
|
title: translate('SettingsCategoryNotifications'),
|
||||||
}}/>
|
iconProvider: FontAwesome5,
|
||||||
<TabRow.Screen name='Privacy' component={PrivacyView} options={{
|
iconName: 'bell',
|
||||||
title: translate('SettingsCategoryPrivacy'),
|
}}
|
||||||
iconProvider: FontAwesome5,
|
/>
|
||||||
iconName: "lock"
|
<TabRow.Screen
|
||||||
}} />
|
name="Privacy"
|
||||||
<TabRow.Screen name='ChangePassword' component={ChangePasswordView} options={{
|
component={PrivacyView}
|
||||||
title: translate('SettingsCategorySecurity'),
|
options={{
|
||||||
iconProvider: FontAwesome5,
|
title: translate('SettingsCategoryPrivacy'),
|
||||||
iconName: "key"
|
iconProvider: FontAwesome5,
|
||||||
}}/>
|
iconName: 'lock',
|
||||||
<TabRow.Screen name='ChangeEmail' component={ChangeEmailView} options={{
|
}}
|
||||||
title: translate('SettingsCategoryEmail'),
|
/>
|
||||||
iconProvider: FontAwesome5,
|
<TabRow.Screen
|
||||||
iconName: "envelope"
|
name="ChangePassword"
|
||||||
}} />
|
component={ChangePasswordView}
|
||||||
<TabRow.Screen name='GoogleAccount' component={GoogleAccountView} options={{
|
options={{
|
||||||
title: translate('SettingsCategoryGoogle'),
|
title: translate('SettingsCategorySecurity'),
|
||||||
iconProvider: FontAwesome5,
|
iconProvider: FontAwesome5,
|
||||||
iconName: "google"
|
iconName: 'key',
|
||||||
}} />
|
}}
|
||||||
<TabRow.Screen name='PianoSettings' component={PianoSettingsView} options={{
|
/>
|
||||||
title: translate('SettingsCategoryPiano'),
|
<TabRow.Screen
|
||||||
iconProvider: MaterialCommunityIcons,
|
name="ChangeEmail"
|
||||||
iconName: "piano"
|
component={ChangeEmailView}
|
||||||
}} />
|
options={{
|
||||||
|
title: translate('SettingsCategoryEmail'),
|
||||||
|
iconProvider: FontAwesome5,
|
||||||
|
iconName: 'envelope',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TabRow.Screen
|
||||||
|
name="GoogleAccount"
|
||||||
|
component={GoogleAccountView}
|
||||||
|
options={{
|
||||||
|
title: translate('SettingsCategoryGoogle'),
|
||||||
|
iconProvider: FontAwesome5,
|
||||||
|
iconName: 'google',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TabRow.Screen
|
||||||
|
name="PianoSettings"
|
||||||
|
component={PianoSettingsView}
|
||||||
|
options={{
|
||||||
|
title: translate('SettingsCategoryPiano'),
|
||||||
|
iconProvider: MaterialCommunityIcons,
|
||||||
|
iconName: 'piano',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</TabRow.Navigator>
|
</TabRow.Navigator>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default SetttingsNavigator;
|
export default SetttingsNavigator;
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
const createExpoWebpackConfigAsync = require('@expo/webpack-config')
|
const createExpoWebpackConfigAsync = require('@expo/webpack-config');
|
||||||
|
|
||||||
module.exports = async function (env, argv) {
|
module.exports = async function (env, argv) {
|
||||||
const config = await createExpoWebpackConfigAsync(
|
const config = await createExpoWebpackConfigAsync(
|
||||||
{
|
{
|
||||||
...env,
|
...env,
|
||||||
babel: { dangerouslyAddModulePathsToTranspile: ['moti'] },
|
babel: { dangerouslyAddModulePathsToTranspile: ['moti'] },
|
||||||
},
|
},
|
||||||
argv
|
argv
|
||||||
)
|
);
|
||||||
|
|
||||||
config.resolve.alias['framer-motion'] = 'framer-motion/dist/framer-motion'
|
config.resolve.alias['framer-motion'] = 'framer-motion/dist/framer-motion';
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|||||||
447
front/yarn.lock
447
front/yarn.lock
@@ -1415,6 +1415,38 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb"
|
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb"
|
||||||
integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
|
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":
|
"@expo/bunyan@4.0.0", "@expo/bunyan@^4.0.0":
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@expo/bunyan/-/bunyan-4.0.0.tgz#be0c1de943c7987a9fbd309ea0b1acd605890c7b"
|
resolved "https://registry.yarnpkg.com/@expo/bunyan/-/bunyan-4.0.0.tgz#be0c1de943c7987a9fbd309ea0b1acd605890c7b"
|
||||||
@@ -1899,6 +1931,25 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@hapi/hoek" "^9.0.0"
|
"@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":
|
"@internationalized/date@^3.0.2":
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.0.2.tgz#1566a0bcbd82dce4dd54a5b26456bb701068cb89"
|
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"
|
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
|
||||||
integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==
|
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"
|
version "1.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
|
resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
|
||||||
integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
|
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"
|
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
|
||||||
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
|
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":
|
"@types/keyv@^3.1.4":
|
||||||
version "3.1.4"
|
version "3.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6"
|
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"
|
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91"
|
||||||
integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==
|
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@*":
|
"@types/source-list-map@*":
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9"
|
resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9"
|
||||||
@@ -4927,6 +4988,90 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/yargs-parser" "*"
|
"@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":
|
"@urql/core@2.3.6":
|
||||||
version "2.3.6"
|
version "2.3.6"
|
||||||
resolved "https://registry.yarnpkg.com/@urql/core/-/core-2.3.6.tgz#ee0a6f8fde02251e9560c5f17dce5cd90f948552"
|
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"
|
resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9"
|
||||||
integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==
|
integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==
|
||||||
|
|
||||||
acorn-jsx@^5.3.1:
|
acorn-jsx@^5.3.1, acorn-jsx@^5.3.2:
|
||||||
version "5.3.2"
|
version "5.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
|
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
|
||||||
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
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"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
|
||||||
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
|
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"
|
version "8.8.2"
|
||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a"
|
||||||
integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
|
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"
|
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
|
||||||
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
|
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"
|
version "6.12.6"
|
||||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
||||||
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
|
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"
|
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099"
|
||||||
integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==
|
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"
|
version "3.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f"
|
resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f"
|
||||||
integrity sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==
|
integrity sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==
|
||||||
@@ -5744,7 +5889,7 @@ array.prototype.flat@^1.2.1:
|
|||||||
es-abstract "^1.20.4"
|
es-abstract "^1.20.4"
|
||||||
es-shim-unscopables "^1.0.0"
|
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"
|
version "1.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz#1aae7903c2100433cb8261cd4ed310aab5c4a183"
|
resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz#1aae7903c2100433cb8261cd4ed310aab5c4a183"
|
||||||
integrity sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==
|
integrity sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==
|
||||||
@@ -5776,6 +5921,17 @@ array.prototype.reduce@^1.0.5:
|
|||||||
es-array-method-boxes-properly "^1.0.0"
|
es-array-method-boxes-properly "^1.0.0"
|
||||||
is-string "^1.0.7"
|
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:
|
arrify@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
|
resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
|
||||||
@@ -7435,7 +7591,7 @@ cross-fetch@^3.1.5:
|
|||||||
dependencies:
|
dependencies:
|
||||||
node-fetch "2.6.7"
|
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"
|
version "7.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
||||||
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
|
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"
|
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
|
||||||
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
|
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"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
|
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
|
||||||
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
|
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
|
||||||
@@ -8065,6 +8221,13 @@ dns-txt@^2.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
buffer-indexof "^1.0.0"
|
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:
|
doctrine@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
|
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
|
||||||
@@ -8572,7 +8735,40 @@ escodegen@^2.0.0:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
source-map "~0.6.1"
|
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"
|
version "5.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
|
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
|
||||||
integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
|
integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
|
||||||
@@ -8588,11 +8784,85 @@ eslint-scope@^4.0.3:
|
|||||||
esrecurse "^4.1.0"
|
esrecurse "^4.1.0"
|
||||||
estraverse "^4.1.1"
|
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:
|
esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
|
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
|
||||||
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
|
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:
|
esrecurse@^4.1.0, esrecurse@^4.3.0:
|
||||||
version "4.3.0"
|
version "4.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
|
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"
|
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
|
||||||
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
|
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
|
||||||
|
|
||||||
estraverse@^5.2.0:
|
estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0:
|
||||||
version "5.3.0"
|
version "5.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
|
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
|
||||||
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
|
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"
|
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
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:
|
fast-glob@^2.2.6:
|
||||||
version "2.2.7"
|
version "2.2.7"
|
||||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d"
|
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"
|
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
||||||
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
|
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
|
||||||
|
|
||||||
fast-levenshtein@~2.0.6:
|
fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
|
||||||
version "2.0.6"
|
version "2.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||||
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
|
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"
|
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e"
|
||||||
integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==
|
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:
|
file-loader@^6.2.0:
|
||||||
version "6.2.0"
|
version "6.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d"
|
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:
|
dependencies:
|
||||||
is-glob "^4.0.1"
|
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:
|
glob-promise@^3.4.0:
|
||||||
version "3.4.0"
|
version "3.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/glob-promise/-/glob-promise-3.4.0.tgz#b6b8f084504216f702dc2ce8c9bc9ac8866fdb20"
|
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"
|
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
|
||||||
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
|
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:
|
globalthis@^1.0.0, globalthis@^1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf"
|
resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf"
|
||||||
@@ -9839,7 +10135,7 @@ globby@11.0.1:
|
|||||||
merge2 "^1.3.0"
|
merge2 "^1.3.0"
|
||||||
slash "^3.0.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"
|
version "11.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
|
resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
|
||||||
integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
|
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"
|
resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e"
|
||||||
integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==
|
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:
|
graphql-tag@^2.10.1:
|
||||||
version "2.12.6"
|
version "2.12.6"
|
||||||
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1"
|
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"
|
caller-path "^2.0.0"
|
||||||
resolve-from "^3.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"
|
version "3.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
|
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
|
||||||
integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
|
integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
|
||||||
@@ -10973,7 +11274,7 @@ is-glob@^3.0.0, is-glob@^3.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-extglob "^2.1.0"
|
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"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
|
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
|
||||||
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
|
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
|
||||||
@@ -11060,7 +11361,7 @@ is-path-inside@^2.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
path-is-inside "^1.0.2"
|
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"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
|
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
|
||||||
integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
|
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"
|
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
|
||||||
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
|
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:
|
json3@^3.3.2:
|
||||||
version "3.3.3"
|
version "3.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81"
|
resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81"
|
||||||
@@ -12087,6 +12393,14 @@ jsonfile@^6.0.1:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
graceful-fs "^4.1.6"
|
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:
|
jszip@3.10.1:
|
||||||
version "3.10.1"
|
version "3.10.1"
|
||||||
resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2"
|
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"
|
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
|
||||||
integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==
|
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:
|
levn@~0.3.0:
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
|
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"
|
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
|
||||||
integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==
|
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"
|
version "3.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
|
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
|
||||||
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
|
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
|
||||||
@@ -13455,6 +13777,11 @@ native-base@^3.4.17:
|
|||||||
tinycolor2 "^1.4.2"
|
tinycolor2 "^1.4.2"
|
||||||
use-subscription "^1.8.0"
|
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:
|
natural-compare@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||||
@@ -13814,7 +14141,7 @@ object-visit@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
isobject "^3.0.0"
|
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"
|
version "4.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f"
|
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f"
|
||||||
integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==
|
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"
|
has-symbols "^1.0.3"
|
||||||
object-keys "^1.1.1"
|
object-keys "^1.1.1"
|
||||||
|
|
||||||
object.entries@^1.1.0:
|
object.entries@^1.1.0, object.entries@^1.1.6:
|
||||||
version "1.1.6"
|
version "1.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.6.tgz#9737d0e5b8291edd340a3e3264bb8a3b00d5fa23"
|
resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.6.tgz#9737d0e5b8291edd340a3e3264bb8a3b00d5fa23"
|
||||||
integrity sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==
|
integrity sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==
|
||||||
@@ -13833,7 +14160,7 @@ object.entries@^1.1.0:
|
|||||||
define-properties "^1.1.4"
|
define-properties "^1.1.4"
|
||||||
es-abstract "^1.20.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"
|
version "2.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.6.tgz#cdb04da08c539cffa912dcd368b886e0904bfa73"
|
resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.6.tgz#cdb04da08c539cffa912dcd368b886e0904bfa73"
|
||||||
integrity sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==
|
integrity sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==
|
||||||
@@ -13863,6 +14190,14 @@ object.getownpropertydescriptors@^2.1.0:
|
|||||||
es-abstract "^1.21.2"
|
es-abstract "^1.21.2"
|
||||||
safe-array-concat "^1.0.0"
|
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:
|
object.pick@^1.3.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747"
|
resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747"
|
||||||
@@ -13870,7 +14205,7 @@ object.pick@^1.3.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
isobject "^3.0.1"
|
isobject "^3.0.1"
|
||||||
|
|
||||||
object.values@^1.1.0:
|
object.values@^1.1.0, object.values@^1.1.6:
|
||||||
version "1.1.6"
|
version "1.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.6.tgz#4abbaa71eba47d63589d402856f908243eea9b1d"
|
resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.6.tgz#4abbaa71eba47d63589d402856f908243eea9b1d"
|
||||||
integrity sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==
|
integrity sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==
|
||||||
@@ -14005,6 +14340,18 @@ optionator@^0.8.1:
|
|||||||
type-check "~0.3.2"
|
type-check "~0.3.2"
|
||||||
word-wrap "~1.2.3"
|
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:
|
ora@3.4.0:
|
||||||
version "3.4.0"
|
version "3.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318"
|
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"
|
tar-fs "^2.0.0"
|
||||||
tunnel-agent "^0.6.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:
|
prelude-ls@~1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
||||||
integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==
|
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":
|
"prettier@>=2.2.1 <=2.3.0":
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.0.tgz#b6a5bf1284026ae640f17f7ff5658a7567fc0d18"
|
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.0.tgz#b6a5bf1284026ae640f17f7ff5658a7567fc0d18"
|
||||||
integrity sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==
|
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:
|
pretty-bytes@5.6.0, pretty-bytes@^5.1.0:
|
||||||
version "5.6.0"
|
version "5.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
|
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"
|
kleur "^3.0.3"
|
||||||
sisteransi "^1.0.5"
|
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"
|
version "15.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
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"
|
path-parse "^1.0.7"
|
||||||
supports-preserve-symlinks-flag "^1.0.0"
|
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:
|
resolve@~1.7.1:
|
||||||
version "1.7.1"
|
version "1.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3"
|
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:
|
dependencies:
|
||||||
lru-cache "^6.0.0"
|
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:
|
send@0.18.0, send@^0.18.0:
|
||||||
version "0.18.0"
|
version "0.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
|
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"
|
is-fullwidth-code-point "^2.0.0"
|
||||||
strip-ansi "^5.1.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"
|
version "4.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz#3bf85722021816dcd1bf38bb714915887ca79fd3"
|
resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz#3bf85722021816dcd1bf38bb714915887ca79fd3"
|
||||||
integrity sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==
|
integrity sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==
|
||||||
@@ -17128,6 +17508,11 @@ strip-indent@^3.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
min-indent "^1.0.0"
|
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:
|
strip-json-comments@~2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
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"
|
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92"
|
||||||
integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==
|
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"
|
version "1.14.1"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
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"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913"
|
||||||
integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==
|
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:
|
tty-browserify@0.0.0:
|
||||||
version "0.0.0"
|
version "0.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
|
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"
|
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
|
||||||
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
|
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:
|
type-check@~0.3.2:
|
||||||
version "0.3.2"
|
version "0.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
|
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"
|
resolved "https://registry.yarnpkg.com/wonka/-/wonka-6.1.2.tgz#2c66fa5b26a12f002a03619b988258313d0b5352"
|
||||||
integrity sha512-zNrXPMccg/7OEp9tSfFkMgTvhhowqasiSHdJ3eCZolXxVTV/aT6HUTofoZk9gwRbGoFey/Nss3JaZKUMKMbofg==
|
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"
|
version "1.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
||||||
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
|
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
|
||||||
|
|||||||
Reference in New Issue
Block a user