From f15c6df20b008710c83fb93f681dc1cbc6a4821a Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Mon, 11 Jul 2022 00:04:13 +1200 Subject: [PATCH] Use stack navigation for homepage + EditSet --- App.tsx | 27 +++--- EditSet.tsx | 236 ++++++++++++++++++++++++++++++----------------- HomePage.tsx | 255 ++++++++------------------------------------------- SetItem.tsx | 27 ++---- SetsPage.tsx | 126 +++++++++++++++++++++++++ package.json | 2 +- set.ts | 10 +- time.ts | 3 +- yarn.lock | 32 +++++-- 9 files changed, 369 insertions(+), 349 deletions(-) create mode 100644 SetsPage.tsx diff --git a/App.tsx b/App.tsx index efadfa2..f7ba8ec 100644 --- a/App.tsx +++ b/App.tsx @@ -1,5 +1,5 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; -import {createMaterialTopTabNavigator} from '@react-navigation/material-top-tabs'; +import {createDrawerNavigator} from '@react-navigation/drawer'; import { DarkTheme, DefaultTheme, @@ -21,8 +21,8 @@ import HomePage from './HomePage'; import PlanPage from './PlanPage'; import SettingsPage from './SettingsPage'; -const Tab = createMaterialTopTabNavigator(); -export type RootStackParamList = { +const Drawer = createDrawerNavigator(); +export type DrawerParamList = { Home: {}; Settings: {}; Best: {}; @@ -64,19 +64,20 @@ const App = () => { {db && ( - - + ( + drawerIcon: ({focused}) => ( ), }} name="Home" component={HomePage} /> - ( + drawerIcon: ({focused}) => ( @@ -85,9 +86,9 @@ const App = () => { name="Plan" component={PlanPage} /> - ( + drawerIcon: ({focused}) => ( @@ -96,9 +97,9 @@ const App = () => { name="Best" component={BestPage} /> - ( + drawerIcon: ({focused}) => ( @@ -107,7 +108,7 @@ const App = () => { name="Settings" component={SettingsPage} /> - + )} diff --git a/EditSet.tsx b/EditSet.tsx index 7f932d2..552332d 100644 --- a/EditSet.tsx +++ b/EditSet.tsx @@ -1,98 +1,164 @@ -import React, {useState} from 'react'; -import {ScrollView, StyleSheet} from 'react-native'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import {RouteProp, useNavigation, useRoute} from '@react-navigation/native'; +import React, {useCallback, useContext, useEffect, useState} from 'react'; +import {NativeModules, ScrollView, StyleSheet} from 'react-native'; import DateTimePickerModal from 'react-native-modal-datetime-picker'; -import {Button, Dialog, Portal, TextInput} from 'react-native-paper'; +import {Button, TextInput} from 'react-native-paper'; +import {DatabaseContext} from './App'; +import {StackParams} from './HomePage'; +import {Plan} from './plan'; import Set from './set'; -import {format} from './time'; +import {DAYS, format} from './time'; -export default function EditSet({ - set, - setSet, - onSave, - title, - saveText, - show, - setShow, -}: { - onSave: () => void; - set?: Set; - setSet: (set?: Set) => void; - title: string; - saveText: string; - show: boolean; - setShow: (show: boolean) => void; -}) { +export default function EditSet() { + const {params} = useRoute>(); + const [name, setName] = useState(params.set.name); + const [reps, setReps] = useState(params.set.reps.toString()); + const [weight, setWeight] = useState(params.set.weight.toString()); + const [created, setCreated] = useState(new Date(params.set.created)); + const [unit, setUnit] = useState(params.set.unit); const [showDate, setShowDate] = useState(false); + const db = useContext(DatabaseContext); + const navigation = useNavigation(); + + const getTodaysPlan = useCallback(async (): Promise => { + const today = DAYS[new Date().getDay()]; + const [result] = await db.executeSql( + `SELECT * FROM plans WHERE days LIKE ? LIMIT 1`, + [`%${today}%`], + ); + return result.rows.raw(); + }, [db]); + + const getTodaysSets = useCallback(async (): Promise => { + const today = new Date().toISOString().split('T')[0]; + const [result] = await db.executeSql( + `SELECT * FROM sets WHERE created LIKE ? ORDER BY created DESC`, + [`${today}%`], + ); + return result.rows.raw(); + }, [db]); + + const predict = useCallback(async () => { + if ((await AsyncStorage.getItem('predictiveSets')) === 'false') return; + const todaysPlan = await getTodaysPlan(); + if (todaysPlan.length === 0) return; + const todaysSets = await getTodaysSets(); + const todaysWorkouts = todaysPlan[0].workouts.split(','); + if (todaysSets.length === 0) return setName(todaysWorkouts[0]); + const count = todaysSets.filter( + set => set.name === todaysSets[0].name, + ).length; + const maxSets = await AsyncStorage.getItem('maxSets'); + if (count < Number(maxSets)) { + setName(todaysSets[0].name); + setReps(todaysSets[0].reps.toString()); + setWeight(todaysSets[0].weight.toString()); + return setUnit(todaysSets[0].unit); + } + const nextWorkout = + todaysWorkouts[todaysWorkouts.indexOf(todaysSets[0].name!) + 1]; + if (!nextWorkout) return; + setName(nextWorkout); + }, []); + + useEffect(() => { + if (params.set.id) return; + predict(); + }, [predict]); const onConfirm = (created: Date) => { - setSet({...set, created: created.toISOString()}); + setCreated(created); setShowDate(false); }; + const update = useCallback(async () => { + console.log(`${EditSet.name}.update`, params.set); + await db.executeSql( + `UPDATE sets SET name = ?, reps = ?, weight = ?, created = ?, unit = ? WHERE id = ?`, + [name, reps, weight, created.toISOString(), unit, params.set.id], + ); + navigation.goBack(); + }, [params.set, name, reps, weight, created, unit, db]); + + const add = useCallback(async () => { + if (name === undefined || reps === '' || weight === '') return; + const insert = ` + INSERT INTO sets(name, reps, weight, created, unit) + VALUES (?,?,?,?,?) + `; + await db.executeSql(insert, [ + name, + reps, + weight, + created.toISOString(), + unit || 'kg', + ]); + notify(); + navigation.goBack(); + }, [name, reps, weight, created, unit, db]); + + const notify = useCallback(async () => { + const enabled = await AsyncStorage.getItem('alarmEnabled'); + if (enabled !== 'true') return; + const minutes = await AsyncStorage.getItem('minutes'); + const seconds = await AsyncStorage.getItem('seconds'); + const milliseconds = Number(minutes) * 60 * 1000 + Number(seconds) * 1000; + NativeModules.AlarmModule.timer(milliseconds); + }, []); + + const save = useCallback(async () => { + if (params.set.id) return update(); + return add(); + }, [update, add]); + return ( - - setShow(false)}> - {title} - - - setSet({...set, name})} - autoCorrect={false} - /> - setSet({...set, reps})} - /> - setSet({...set, weight})} - onSubmitEditing={onSave} - /> - setSet({...set, unit})} - onSubmitEditing={onSave} - /> - {set?.created && ( - <> - - setShowDate(false)} - date={new Date(set.created)} - /> - - )} - - - - - - - - + + + + + + + + setShowDate(false)} + date={created} + /> + + ); } diff --git a/HomePage.tsx b/HomePage.tsx index 5a03d8a..aab3a99 100644 --- a/HomePage.tsx +++ b/HomePage.tsx @@ -1,226 +1,49 @@ -import AsyncStorage from '@react-native-async-storage/async-storage'; -import React, {useCallback, useContext, useEffect, useState} from 'react'; -import {FlatList, NativeModules, StyleSheet, View} from 'react-native'; -import {List, Searchbar} from 'react-native-paper'; -import {DatabaseContext} from './App'; +import {DrawerNavigationProp} from '@react-navigation/drawer'; +import {TypedNavigator, useNavigation} from '@react-navigation/native'; +import {createNativeStackNavigator} from '@react-navigation/native-stack'; +import React from 'react'; +import {IconButton} from 'react-native-paper'; +import {DrawerParamList} from './App'; import EditSet from './EditSet'; -import MassiveFab from './MassiveFab'; -import {Plan} from './plan'; import Set from './set'; -import SetItem from './SetItem'; -import {DAYS} from './time'; +import SetsPage from './SetsPage'; -const limit = 15; +const Stack = createNativeStackNavigator(); +export type StackParams = { + Sets: {}; + EditSet: { + set: Set; + }; +}; export default function HomePage() { - const [sets, setSets] = useState(); - const [offset, setOffset] = useState(0); - const [edit, setEdit] = useState(); - const [showEdit, setShowEdit] = useState(false); - const [showNew, setShowNew] = useState(false); - const [newSet, setNewSet] = useState(); - const [search, setSearch] = useState(''); - const [refreshing, setRefreshing] = useState(false); - const [end, setEnd] = useState(false); - const db = useContext(DatabaseContext); - - const selectSets = ` - SELECT * from sets - WHERE name LIKE ? - ORDER BY created DESC - LIMIT ? OFFSET ? - `; - - const refresh = useCallback(async () => { - const [result] = await db.executeSql(selectSets, [`%${search}%`, limit, 0]); - if (!result) return setSets([]); - console.log(`${HomePage.name}.refresh:`, {search, limit}); - setSets(result.rows.raw()); - setOffset(0); - setEnd(false); - }, [search, db, selectSets]); - - const refreshLoader = useCallback(async () => { - setRefreshing(true); - refresh().finally(() => setRefreshing(false)); - }, [setRefreshing, refresh]); - - useEffect(() => { - refresh(); - }, [search, refresh]); - - const renderItem = useCallback( - ({item}: {item: Set}) => ( - - ), - [setEdit, refresh, setNewSet], - ); - - const update = useCallback(async () => { - console.log('HomePage.update', {edit}); - await db.executeSql( - `UPDATE sets SET name = ?, reps = ?, weight = ?, created = ?, unit = ? WHERE id = ?`, - [ - edit?.name, - edit?.reps, - edit?.weight, - edit?.created, - edit?.unit, - edit?.id, - ], - ); - setShowEdit(false); - await refresh(); - }, [edit, setShowEdit, refresh, db]); - - const add = useCallback(async () => { - if ( - newSet?.name === undefined || - newSet?.reps === 0 || - newSet?.weight === 0 - ) - return; - await db.executeSql( - `INSERT INTO sets(name, reps, weight, created, unit) VALUES (?,?,?,?,?)`, - [ - newSet?.name, - newSet?.reps, - newSet?.weight, - new Date().toISOString(), - newSet?.unit || 'kg', - ], - ); - setShowNew(false); - await refresh(); - const enabled = await AsyncStorage.getItem('alarmEnabled'); - if (enabled !== 'true') return; - const minutes = await AsyncStorage.getItem('minutes'); - const seconds = await AsyncStorage.getItem('seconds'); - const milliseconds = Number(minutes) * 60 * 1000 + Number(seconds) * 1000; - NativeModules.AlarmModule.timer(milliseconds); - }, [newSet, setShowNew, refresh, db]); - - const next = useCallback(async () => { - if (end) return; - setRefreshing(true); - const newOffset = offset + limit; - console.log(`${HomePage.name}.next:`, { - offset, - limit, - newOffset, - search, - }); - const [result] = await db - .executeSql(selectSets, [`%${search}%`, limit, newOffset]) - .finally(() => setRefreshing(false)); - if (result.rows.length === 0) return setEnd(true); - if (!sets) return; - setSets([...sets, ...result.rows.raw()]); - if (result.rows.length < limit) return setEnd(true); - setOffset(newOffset); - }, [search, end, offset, sets, db, selectSets]); - - const getTodaysPlan = useCallback(async (): Promise => { - const today = DAYS[new Date().getDay()]; - const [result] = await db.executeSql( - `SELECT * FROM plans WHERE days LIKE ? LIMIT 1`, - [`%${today}%`], - ); - return result.rows.raw(); - }, [db]); - - const getTodaysSets = useCallback(async (): Promise => { - const today = new Date().toISOString().split('T')[0]; - const [result] = await db.executeSql( - `SELECT * FROM sets WHERE created LIKE ? ORDER BY created DESC`, - [`${today}%`], - ); - return result.rows.raw(); - }, [db]); - - const onAdd = useCallback(async () => { - const created = new Date().toISOString(); - setNewSet({created}); - setShowNew(true); - if ((await AsyncStorage.getItem('predictiveSets')) === 'false') return; - const todaysPlan = await getTodaysPlan(); - if (todaysPlan.length === 0) return; - console.log(`${HomePage.name}.onAdd: todaysPlan =`, todaysPlan); - const todaysSets = await getTodaysSets(); - const todaysWorkouts = todaysPlan[0].workouts.split(','); - if (todaysSets.length === 0) - return setNewSet({created, name: todaysWorkouts[0]}); - console.log(`${HomePage.name}.onAdd: todaysSets =`, todaysSets); - const count = todaysSets.filter( - set => set.name === todaysSets[0].name, - ).length; - console.log(`${HomePage.name}.onAdd: count =`, count); - const maxSets = await AsyncStorage.getItem('maxSets'); - if (count < Number(maxSets)) - return setNewSet({...todaysSets[0], id: undefined, created}); - const nextWorkout = - todaysWorkouts[todaysWorkouts.indexOf(todaysSets[0].name!) + 1]; - if (!nextWorkout) - return setNewSet({...todaysSets[0], id: undefined, created}); - console.log(`${HomePage.name}.onAdd: nextWorkout =`, nextWorkout); - setNewSet({created, name: nextWorkout}); - }, [getTodaysPlan, getTodaysSets, setNewSet, setShowNew]); + const navigation = useNavigation>(); return ( - - - - } - renderItem={renderItem} - keyExtractor={set => set.id!.toString()} - onEndReached={next} - refreshing={refreshing} - onRefresh={refreshLoader} + + + { + navigation.setOptions({ + headerLeft: () => ( + + ), + title: 'Set', + }); + }, + beforeRemove: () => { + navigation.setOptions({ + headerLeft: () => ( + + ), + title: 'Home', + }); + }, + }} /> - - - - - - + ); } - -const styles = StyleSheet.create({ - container: { - flexGrow: 1, - padding: 10, - paddingBottom: '10%', - }, -}); diff --git a/SetItem.tsx b/SetItem.tsx index 1381b17..99f842a 100644 --- a/SetItem.tsx +++ b/SetItem.tsx @@ -1,27 +1,22 @@ +import {NavigationProp, useNavigation} from '@react-navigation/native'; import React, {useCallback, useContext, useState} from 'react'; import {GestureResponderEvent} from 'react-native'; import {List, Menu} from 'react-native-paper'; import {DatabaseContext} from './App'; +import {StackParams} from './HomePage'; import Set from './set'; export default function SetItem({ item, - setEdit, - setShowEdit, onRemove, - setNewSet, - setShowNew, }: { item: Set; - setEdit: (set: Set) => void; - setNewSet: (set: Set) => void; - setShowEdit: (show: boolean) => void; - setShowNew: (show: boolean) => void; onRemove: () => void; }) { const [showMenu, setShowMenu] = useState(false); const [anchor, setAnchor] = useState({x: 0, y: 0}); const db = useContext(DatabaseContext); + const navigation = useNavigation>(); const remove = useCallback(async () => { await db.executeSql(`DELETE FROM sets WHERE id = ?`, [item.id]); @@ -30,12 +25,11 @@ export default function SetItem({ }, [setShowMenu, db, onRemove, item.id]); const copy = useCallback(() => { - const set = {...item}; - delete set.id; - setNewSet(set); - setShowMenu(false); - setShowNew(true); - }, [setNewSet, setShowMenu, item, setShowNew]); + const set: Set = {...item}; + set.created = new Date().toISOString(); + set.id = 0; + navigation.navigate('EditSet', {set}); + }, [navigation]); const longPress = useCallback( (e: GestureResponderEvent) => { @@ -48,10 +42,7 @@ export default function SetItem({ return ( <> { - setEdit(item); - setShowEdit(true); - }} + onPress={() => navigation.navigate('EditSet', {set: item})} title={item.name} description={`${item.reps} x ${item.weight}${item.unit}`} onLongPress={longPress} diff --git a/SetsPage.tsx b/SetsPage.tsx new file mode 100644 index 0000000..f496108 --- /dev/null +++ b/SetsPage.tsx @@ -0,0 +1,126 @@ +import { + NavigationProp, + useFocusEffect, + useNavigation, +} from '@react-navigation/native'; +import React, {useCallback, useContext, useEffect, useState} from 'react'; +import {FlatList, StyleSheet, View} from 'react-native'; +import {List, Searchbar} from 'react-native-paper'; +import {DatabaseContext} from './App'; +import {StackParams} from './HomePage'; +import MassiveFab from './MassiveFab'; +import Set from './set'; +import SetItem from './SetItem'; + +const limit = 15; + +export default function SetsPage() { + const [sets, setSets] = useState(); + const [offset, setOffset] = useState(0); + const [search, setSearch] = useState(''); + const [refreshing, setRefreshing] = useState(false); + const [end, setEnd] = useState(false); + const db = useContext(DatabaseContext); + const navigation = useNavigation>(); + + const selectSets = ` + SELECT * from sets + WHERE name LIKE ? + ORDER BY created DESC + LIMIT ? OFFSET ? + `; + + const refresh = useCallback(async () => { + const [result] = await db.executeSql(selectSets, [`%${search}%`, limit, 0]); + if (!result) return setSets([]); + console.log(`${SetsPage.name}.refresh:`, {search, limit}); + setSets(result.rows.raw()); + setOffset(0); + setEnd(false); + }, [search, db, selectSets]); + + useFocusEffect( + useCallback(() => { + refresh(); + }, [refresh]), + ); + + const refreshLoader = useCallback(async () => { + setRefreshing(true); + refresh().finally(() => setRefreshing(false)); + }, [setRefreshing, refresh]); + + useEffect(() => { + if (!search) return; + refresh(); + }, [search, refresh]); + + const renderItem = useCallback( + ({item}: {item: Set}) => ( + + ), + [refresh], + ); + + const next = useCallback(async () => { + if (end) return; + setRefreshing(true); + const newOffset = offset + limit; + console.log(`${SetsPage.name}.next:`, { + offset, + limit, + newOffset, + search, + }); + const [result] = await db + .executeSql(selectSets, [`%${search}%`, limit, newOffset]) + .finally(() => setRefreshing(false)); + if (result.rows.length === 0) return setEnd(true); + if (!sets) return; + setSets([...sets, ...result.rows.raw()]); + if (result.rows.length < limit) return setEnd(true); + setOffset(newOffset); + }, [search, end, offset, sets, db, selectSets]); + + const onAdd = useCallback(async () => { + const set: Set = { + created: new Date().toISOString(), + name: '', + id: 0, + reps: 0, + weight: 0, + unit: 'kg', + }; + navigation.navigate('EditSet', {set}); + }, [navigation]); + + return ( + + + + } + renderItem={renderItem} + keyExtractor={set => set.id!.toString()} + onEndReached={next} + refreshing={refreshing} + onRefresh={refreshLoader} + /> + + + ); +} + +const styles = StyleSheet.create({ + container: { + flexGrow: 1, + padding: 10, + paddingBottom: '10%', + }, +}); diff --git a/package.json b/package.json index 6d9cd15..b2db941 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "@babel/preset-env": "^7.1.6", "@react-native-async-storage/async-storage": "^1.17.7", "@react-native-community/datetimepicker": "^6.2.0", - "@react-navigation/material-top-tabs": "^6.2.1", + "@react-navigation/drawer": "^6.4.3", "@react-navigation/native": "^6.0.10", "@react-navigation/native-stack": "^6.6.2", "@types/d3-shape": "^3.1.0", diff --git a/set.ts b/set.ts index 5a28091..5b0b4c8 100644 --- a/set.ts +++ b/set.ts @@ -1,8 +1,8 @@ export default interface Set { - id?: number; - name?: string; - reps?: number | string; - weight?: number | string; - created?: string; + id: number; + name: string; + reps: number; + weight: number; + created: string; unit?: string; } diff --git a/time.ts b/time.ts index 73b2d82..cf14dd5 100644 --- a/time.ts +++ b/time.ts @@ -23,8 +23,7 @@ export const MONTH = [ 'December', ]; -export function format(iso: string) { - const date = new Date(iso); +export function format(date: Date) { const mm = MONTH[date.getMonth()]; const dd = date.getDate().toString(); const day = DAYS[date.getDay()]; diff --git a/yarn.lock b/yarn.lock index 8874c60..45d57e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1636,18 +1636,24 @@ query-string "^7.0.0" react-is "^16.13.0" +"@react-navigation/drawer@^6.4.3": + version "6.4.3" + resolved "https://registry.yarnpkg.com/@react-navigation/drawer/-/drawer-6.4.3.tgz#cc00a3d5814b62f9ab6476d2677217ba81af4ea3" + integrity sha512-qdOwZxrJe05uaDzpLPhEvXqvt0iop0AGlaGYpc3nnG44KYpzpym8/Zxoy6dIw0pn5ZPU0afvKqm+UxS2Uoz+ZQ== + dependencies: + "@react-navigation/elements" "^1.3.4" + color "^4.2.3" + warn-once "^0.1.0" + "@react-navigation/elements@^1.3.3": version "1.3.3" resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-1.3.3.tgz#9f56b650a9a1a8263a271628be7342c8121d1788" integrity sha512-Lv2lR7si5gNME8dRsqz57d54m4FJtrwHRjNQLOyQO546ZxO+g864cSvoLC6hQedQU0+IJnPTsZiEI2hHqfpEpw== -"@react-navigation/material-top-tabs@^6.2.1": - version "6.2.1" - resolved "https://registry.yarnpkg.com/@react-navigation/material-top-tabs/-/material-top-tabs-6.2.1.tgz#747d17bd0138a7d50c791e9cce1adc350904f67d" - integrity sha512-fGy2+/7cUAyrZUPUhzKUttA4avK5Z2FrNk0LRI04hRlAVqs5DYpnaQGkegGeOGwKmnxaVCjCVPr+KoEroyqduw== - dependencies: - color "^3.1.3" - warn-once "^0.1.0" +"@react-navigation/elements@^1.3.4": + version "1.3.4" + resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-1.3.4.tgz#abb48136508c1e60862dc14a101ce02ff685a337" + integrity sha512-O0jICpjn3jskVo4yiWzZozmj7DZy1ZBbn3O7dbenuUjZSj/cscjwaapmZZFGcI/IMmjmx8UTKsybhCFEIbGf3g== "@react-navigation/native-stack@^6.6.2": version "6.6.2" @@ -2879,7 +2885,7 @@ color-name@^1.0.0, color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.5.2, color-string@^1.6.0: +color-string@^1.5.2, color-string@^1.6.0, color-string@^1.9.0: version "1.9.1" resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== @@ -2895,7 +2901,7 @@ color@^2.0.1: color-convert "^1.9.1" color-string "^1.5.2" -color@^3.1.2, color@^3.1.3: +color@^3.1.2: version "3.2.1" resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== @@ -2903,6 +2909,14 @@ color@^3.1.2, color@^3.1.3: color-convert "^1.9.3" color-string "^1.6.0" +color@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== + dependencies: + color-convert "^2.0.1" + color-string "^1.9.0" + colorette@^1.0.7: version "1.4.0" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40"