From 1cab99d4e9a69df3bae3dc54828c363189b95bac Mon Sep 17 00:00:00 2001 From: hazlazuardi Date: Sun, 6 Nov 2022 19:32:15 +1000 Subject: [PATCH] docs: fix all jsdocs warnings from linter --- .eslintrc.json | 15 +++- App.js | 6 ++ package-lock.json | 136 ++++++++++++++++++++++++++++++++ package.json | 4 +- src/components/BottomTabBar.js | 77 +++++++++++------- src/components/OgcisumButton.js | 17 ++-- src/components/OgcisumText.js | 8 ++ src/context/Context.js | 83 +++++++++++++++---- src/hooks/useFetch.js | 8 +- src/hooks/useWhyDidYouUpdate.js | 5 ++ src/screens/Map.js | 22 ++++-- src/screens/NowPlaying.js | 101 +++++++++++++++++------- src/screens/Profile.js | 55 +++++++------ yarn.lock | 43 +++++++++- 14 files changed, 468 insertions(+), 112 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 6ff86b9..cf56282 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -40,7 +40,11 @@ "es2021": true }, "plugins": ["react", "react-native", "react-hooks"], - "extends": ["eslint:recommended", "plugin:react/recommended"], + "extends": [ + "eslint:recommended", + "plugin:react/recommended", + "plugin:jsdoc/recommended" + ], "rules": { "react-native/no-unused-styles": 2, "react-native/split-platform-components": 2, @@ -54,6 +58,13 @@ "ignoreClassNames": false, "ignoreStyleProperties": false } - ] + ], + "react/no-multi-comp": [2, { "ignoreStateless": true }], + "react/jsx-pascal-case": 2, + "jsx-quotes": ["error", "prefer-double"], + "no-multi-spaces": "error" + }, + "globals": { + "JSX": "readonly" } } diff --git a/App.js b/App.js index adee85e..6c898ee 100644 --- a/App.js +++ b/App.js @@ -4,6 +4,12 @@ import { NavigationContainer } from '@react-navigation/native'; import BottomTabBar from './src/components/BottomTabBar'; import StoreProvider from './src/context/Context'; +/** + * Function as the starting point of the App. + * + * @returns {JSX.Element} App components with Store, Navigation, + * and Bottom Tab Bar. + */ export default function App() { return ( diff --git a/package-lock.json b/package-lock.json index c542930..8c9cb65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "eslint": "^8.26.0", "eslint-config-standard": "^17.0.0", "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jsdoc": "^39.6.2", "eslint-plugin-n": "^15.4.0", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-react": "^7.31.10", @@ -2031,6 +2032,20 @@ "node": ">=0.8.0" } }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.36.0.tgz", + "integrity": "sha512-u0XZyvUF6Urb2cSivSXA8qXIpT/CxkHcdtZKoWusAzgzmsTWpg0F2FpWXsolHmMUyVY3dLWaoy+0ccJ5uf2QjA==", + "dev": true, + "dependencies": { + "comment-parser": "1.3.1", + "esquery": "^1.4.0", + "jsdoc-type-pratt-parser": "~3.1.0" + }, + "engines": { + "node": "^14 || ^16 || ^17 || ^18 || ^19" + } + }, "node_modules/@eslint/eslintrc": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", @@ -4602,6 +4617,15 @@ "node": "^12.20.0 || >=14" } }, + "node_modules/comment-parser": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.3.1.tgz", + "integrity": "sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==", + "dev": true, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -5602,6 +5626,54 @@ "eslint": ">=5" } }, + "node_modules/eslint-plugin-jsdoc": { + "version": "39.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-39.6.2.tgz", + "integrity": "sha512-dvgY/W7eUFoAIIiaWHERIMI61ZWqcz9YFjEeyTzdPlrZc3TY/3aZm5aB91NUoTLWYZmO/vFlYSuQi15tF7uE5A==", + "dev": true, + "dependencies": { + "@es-joy/jsdoccomment": "~0.36.0", + "comment-parser": "1.3.1", + "debug": "^4.3.4", + "escape-string-regexp": "^4.0.0", + "esquery": "^1.4.0", + "semver": "^7.3.8", + "spdx-expression-parse": "^3.0.1" + }, + "engines": { + "node": "^14 || ^16 || ^17 || ^18 || ^19" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/eslint-plugin-n": { "version": "15.4.0", "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.4.0.tgz", @@ -8686,6 +8758,15 @@ "signal-exit": "^3.0.2" } }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.1.0.tgz", + "integrity": "sha512-MgtD0ZiCDk9B+eI73BextfRrVQl0oyzRG8B2BjORts6jbunj4ScKPcyXGTbB6eXL4y9TzxCm6hyeLq/2ASzNdw==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/jsdom": { "version": "16.7.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", @@ -14638,6 +14719,17 @@ "@types/hammerjs": "^2.0.36" } }, + "@es-joy/jsdoccomment": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.36.0.tgz", + "integrity": "sha512-u0XZyvUF6Urb2cSivSXA8qXIpT/CxkHcdtZKoWusAzgzmsTWpg0F2FpWXsolHmMUyVY3dLWaoy+0ccJ5uf2QjA==", + "dev": true, + "requires": { + "comment-parser": "1.3.1", + "esquery": "^1.4.0", + "jsdoc-type-pratt-parser": "~3.1.0" + } + }, "@eslint/eslintrc": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", @@ -16517,6 +16609,12 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz", "integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==" }, + "comment-parser": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.3.1.tgz", + "integrity": "sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==", + "dev": true + }, "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -17334,6 +17432,38 @@ "dev": true, "requires": {} }, + "eslint-plugin-jsdoc": { + "version": "39.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-39.6.2.tgz", + "integrity": "sha512-dvgY/W7eUFoAIIiaWHERIMI61ZWqcz9YFjEeyTzdPlrZc3TY/3aZm5aB91NUoTLWYZmO/vFlYSuQi15tF7uE5A==", + "dev": true, + "requires": { + "@es-joy/jsdoccomment": "~0.36.0", + "comment-parser": "1.3.1", + "debug": "^4.3.4", + "escape-string-regexp": "^4.0.0", + "esquery": "^1.4.0", + "semver": "^7.3.8", + "spdx-expression-parse": "^3.0.1" + }, + "dependencies": { + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, "eslint-plugin-n": { "version": "15.4.0", "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.4.0.tgz", @@ -19380,6 +19510,12 @@ } } }, + "jsdoc-type-pratt-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.1.0.tgz", + "integrity": "sha512-MgtD0ZiCDk9B+eI73BextfRrVQl0oyzRG8B2BjORts6jbunj4ScKPcyXGTbB6eXL4y9TzxCm6hyeLq/2ASzNdw==", + "dev": true + }, "jsdom": { "version": "16.7.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", diff --git a/package.json b/package.json index df74bf1..c500b70 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "ios": "react-native run-ios", "start": "react-native start", "test": "jest", - "lint": "eslint ." + "lint": "eslint src", + "lint-fix": "eslint src --fix" }, "dependencies": { "@react-native-community/geolocation": "^3.0.2", @@ -35,6 +36,7 @@ "eslint": "^8.26.0", "eslint-config-standard": "^17.0.0", "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jsdoc": "^39.6.2", "eslint-plugin-n": "^15.4.0", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-react": "^7.31.10", diff --git a/src/components/BottomTabBar.js b/src/components/BottomTabBar.js index d7dd50b..5718671 100644 --- a/src/components/BottomTabBar.js +++ b/src/components/BottomTabBar.js @@ -16,8 +16,13 @@ import LinearGradient from 'react-native-linear-gradient'; const Tab = createBottomTabNavigator(); const { height } = Dimensions.get('screen'); -/** This is main component for Bottom Tab Bar */ -function BottomTabBar({ navigation }) { +/** + * This is main component for Bottom Tab Bar + * + * @returns {JSX.Element} - React Component for Bottom Tab Bar + * and Screens for this app. + */ +function BottomTabBar() { return ( } + children={() => } options={tabOptions(icons.tabMapPurple)} /> } + children={() => } options={tabOptions(icons.logoWhite, true)} /> } + children={() => } options={tabOptions(icons.tabProfilePurple)} /> @@ -59,7 +64,13 @@ BottomTabBar.propTypes = { navigation: PropTypes.object, }; -/** This function specify the tab options for each distinct tab */ +/** + * This function specify the tab options for each distinct tab + * + * @param {number} icon - Icon for each Tab to show. + * @param {boolean} isLogo - Flag to indicate if a Tab is for the Logo. + * @returns {object} - Options for Tab Screen options prop. + */ function tabOptions(icon, isLogo) { return { tabBarIcon: ({ focused, size }) => ( @@ -75,7 +86,16 @@ function tabOptions(icon, isLogo) { }; } -/** This is a Tab Icon component to be passed to tabBarIcon. */ +/** + * This is a Tab Icon component to be passed to tabBarIcon. + * + * @param {object} props - Object containing props for this component. + * @param {boolean} props.focused — Flag to indicate which Tab is focused. + * @param {number} props.icon - Icon for each Tab to show. + * @param {boolean} props.isLogo - Flag to indicate if a Tab is for the Logo. + * @param {number} props.size — Size of the Tab. + * @returns {JSX.Element} - React Component for each Tab. + */ function TabIcon({ focused, icon, isLogo, size }) { /** Retrieve live locations from Context. */ const { liveLocations } = useLocation(); @@ -123,29 +143,28 @@ function TabIcon({ focused, icon, isLogo, size }) { ); } - if (isLogo) { - return ( - - - - - {isNearAndHasRecordingData && ( - - There's Music Nearby - - )} + + return ( + + + - ); - } + {isNearAndHasRecordingData && ( + + There's Music Nearby + + )} + + ); } TabIcon.propTypes = { diff --git a/src/components/OgcisumButton.js b/src/components/OgcisumButton.js index 012a84a..43ddc65 100644 --- a/src/components/OgcisumButton.js +++ b/src/components/OgcisumButton.js @@ -6,13 +6,16 @@ import { useTheme } from '../context/Context'; import { sizes } from '../data/theme'; /** + * This is a React Component for OgcisumButton. * - * @param {string} text — text in the button - * @param {function} onPress — function invoked when the button is pressed - * @param {boolean} fullWidth — specify whether the button should be full width or not - * @returns React component for ButtonIOS + * @param {object} props - props for this component. + * @param {string} props.text — text in the button + * @param {Function} props.onPress — function invoked when the button is pressed + * @param {boolean} props.fullWidth — specify whether the button should be full width or not + * @param {boolean} props.disabled — specify whether the button should be full width or not + * @returns {JSX.Element} React component for OgcisumButton */ -export default function ButtonIOS({ text, onPress, fullWidth, disabled }) { +function OgcisumButton({ text, onPress, fullWidth, disabled }) { const { themeColors } = useTheme(); /** Object containing styles that use dynamic conditions. */ @@ -43,7 +46,7 @@ export default function ButtonIOS({ text, onPress, fullWidth, disabled }) { ); } -ButtonIOS.propTypes = { +OgcisumButton.propTypes = { text: PropTypes.string, onPress: PropTypes.func, fullWidth: PropTypes.bool, @@ -68,3 +71,5 @@ const styles = StyleSheet.create({ paddingVertical: sizes.padding / 2, }, }); + +export default OgcisumButton; diff --git a/src/components/OgcisumText.js b/src/components/OgcisumText.js index 7c6bafe..f16860c 100644 --- a/src/components/OgcisumText.js +++ b/src/components/OgcisumText.js @@ -5,6 +5,14 @@ import PropTypes from 'prop-types'; import { useTheme } from '../context/Context'; import { sizes } from '../data/theme'; +/** + * This is a React Component for OgcisumText. + * + * @param {object} props - props for this component. + * @param {string} props.variant — specify the variant of the Text. + * @param {string} props.text — text for the Text component. + * @returns {JSX.Element} React component for OgcisumText. + */ function OgcisumText({ variant, text }) { const { themeColors } = useTheme(); diff --git a/src/context/Context.js b/src/context/Context.js index d193458..aec8bd8 100644 --- a/src/context/Context.js +++ b/src/context/Context.js @@ -17,7 +17,6 @@ import icons from '../data/icons'; import { colors } from '../data/theme'; import useFetch from '../hooks/useFetch'; -import useWhyDidYouUpdate from '../hooks/useWhyDidYouUpdate'; const LocationContext = createContext(null); const LocationDispatchContext = createContext(null); @@ -26,16 +25,25 @@ const SamplesToLocationsContext = createContext(null); const ProfileContext = createContext(null); const ThemeContext = createContext(null); -/** Function as async function for custom hook useFetch. */ +/** Async function for custom hook useFetch. */ const samplesFetcher = async () => { return fetch(SAMPLES_URL).then((res) => res.json()); }; -/** Function as async function for custom hook useFetch. */ +/** Async function for custom hook useFetch. */ const samplesToLocationsFetcher = async () => { return fetch(SAMPLES_TO_LOCATIONS_URL).then((res) => res.json()); }; +/** + * React component for the Context API that acts as + * the Store Provider. + * + * @param {object} props - Object containing props for this component. + * @param {JSX.Element} props.children — React components that can- + * access data in this Store. + * @returns {JSX.Element} React component for Providers of data. + */ function StoreProvider({ children }) { /** Reducer to store live locations and update it using dispatch. */ const [profile, dispatchProfile] = useReducer(profileReducer, initialProfile); @@ -46,6 +54,9 @@ function StoreProvider({ children }) { /** This is to fetch locations from API and store it into a state. */ const [musicLocations, setMusicLocations] = useState(); useEffect(() => { + /** + * + */ async function getLocations() { await fetch(LOCATION_URL) .then((res) => res.json()) @@ -71,14 +82,24 @@ function StoreProvider({ children }) { /** This is to fetch samples_to_locations using custom hooks from API and store it into a state. */ const { value: samplesToLocationsResponse, - execute: fetchSTL, - status: statusSTL, + execute: fetchSharedSamples, + status: statusSharedSamples, } = useFetch(samplesToLocationsFetcher, true); const samplesToLocations = samplesToLocationsResponse?.samples_to_locations; const [recordingData, setRecordingData] = useState(null); const [hasRecordingData, setHasRecordingData] = useState(false); useEffect(() => { + /** + * This is a function to get samples from locations. + * This function is inside a useEffect as a best practice since- + * nothing uses this function anywhere else. + * + * @param {object} nearLoc - Object containing nearby location data. + * @param {object} allSam - Object containing samples data. + * @param {object} allStl - Object containing samples to locations data. + * @returns {Array} Array of filtered samples based on location. + */ function getSamplesFromLocations(nearLoc, allSam, allStl) { const filteredSamplesToLocations = allStl .filter((stl) => stl.locations_id === nearLoc.id) @@ -116,7 +137,7 @@ function StoreProvider({ children }) { } }, [nearbyLocation, samples, samplesToLocations]); - useWhyDidYouUpdate('Context', { ...liveLocations }); + // useWhyDidYouUpdate('Context', { ...liveLocations }); /** Detect device's color scheme. */ const colorScheme = useColorScheme(); @@ -135,8 +156,8 @@ function StoreProvider({ children }) { value={{ recordingData, hasRecordingData, - fetchSTL, - statusSTL, + fetchSharedSamples, + statusSharedSamples, }} > {children} @@ -152,37 +173,69 @@ StoreProvider.propTypes = { children: PropTypes.element, }; -/** Custom hook to get live locations from context. */ +/** + * Custom hook to get live locations from context. + * + * @returns {object} Context object responsible for Location data. + */ export function useLocation() { return useContext(LocationContext); } -/** Custom hook to update live locations from context. */ +/** + * Custom hook to update live locations from context. + * + * @returns {object} Context object responsible for + * Location Dispatch action. + */ export function useLocationDispatch() { return useContext(LocationDispatchContext); } -/** Custom hook to get samples from context. */ +/** + * Custom hook to get samples from context. + * + * @returns {object} Context object responsible for Samples data. + */ export function useSamples() { return useContext(SamplesContext); } -/** Custom hook to get samples_to_locations from context. */ +/** + * Custom hook to get samples_to_locations from context. + * + * @returns {object} Context object responsible for Samples + * to Locations data. + */ export function useSamplesToLocations() { return useContext(SamplesToLocationsContext); } -/** Custom hook to get profile from context. */ +/** + * Custom hook to get profile from context. + * + * @returns {object} Context object responsible for Profile data. + */ export function useProfile() { return useContext(ProfileContext); } -/** Custom hook to get colors and icons from context. */ +/** + * Custom hook to get colors and icons from context. + * + * @returns {object} Context object responsible for color scheme data. + */ export function useTheme() { return useContext(ThemeContext); } -/** Function as reducer for profile. */ +/** + * Function as reducer for profile. + * + * @param {object} state - Object containing Profile data. + * @param {object} action - Object containing dispatch data. + * @returns {object} Updated Profile data from dispatch payload. + */ function profileReducer(state, action) { switch (action.type) { case 'setPhoto': { diff --git a/src/hooks/useFetch.js b/src/hooks/useFetch.js index 473eb35..6a4bd78 100644 --- a/src/hooks/useFetch.js +++ b/src/hooks/useFetch.js @@ -2,7 +2,13 @@ // https://usehooks.com import { useState, useEffect, useCallback } from 'react'; -// Usage +/** + * + * @param {Function} asyncFunction - Async function to use. + * @param {boolean} immediate - Flag to indicate whether to run asynFunction + * immediately or not. + * @returns {object} Object containing response status, data, and error. + */ export default function useFetch(asyncFunction, immediate = true) { const [status, setStatus] = useState('idle'); const [value, setValue] = useState(null); diff --git a/src/hooks/useWhyDidYouUpdate.js b/src/hooks/useWhyDidYouUpdate.js index 38712d4..b89dd4f 100644 --- a/src/hooks/useWhyDidYouUpdate.js +++ b/src/hooks/useWhyDidYouUpdate.js @@ -1,6 +1,11 @@ import { useEffect, useRef } from 'react'; // Hook +/** + * + * @param {string} name - Name of anything you want to check. + * @param {object} props - Object containing props for this component. + */ export default function useWhyDidYouUpdate(name, props) { // Get a mutable ref object where we can store props ... // ... for comparison next time this hook runs. diff --git a/src/screens/Map.js b/src/screens/Map.js index af6f269..05e41e3 100644 --- a/src/screens/Map.js +++ b/src/screens/Map.js @@ -13,7 +13,8 @@ import { useLocation, useLocationDispatch, useTheme } from '../context/Context'; /** * This is the main component of Map page - * @returns React component for Map page + * + * @returns {JSX.Element} React component for Map page. */ export default function Map() { /** Ignore all warning logs related to Geolocation */ @@ -39,7 +40,8 @@ export default function Map() { /** use live location setter from Context */ const setLiveLocations = useLocationDispatch(); - /** useEffect to watch user's position in real time. + /** + * useEffect to watch user's position in real time. * It also store the current user location into * live location's userLocation state in Context. */ @@ -65,11 +67,21 @@ export default function Map() { }; }, [locationPermission, musicLocations, setLiveLocations]); - /** useEffect to calculate nearest location from user. - * It also store the current nearest location into - * live location's nearbyLocation state in Context. + /** + * useEffect to calculate nearest location from user and + * store the current nearest location into live location's + * nearbyLocation state in Context. */ useEffect(() => { + /** + * This is a function to get nearest location relative to user. + * This function is inside a useEffect as a best practice since- + * nothing uses this function anywhere else. + * + * @param {object} userCoordinate - Object containing user coordinate. + * @param {Array} musicCoordinates - Array containing all music locations data. + * @returns {object} Object containing nearest location data. + */ function calculateDistance(userCoordinate, musicCoordinates) { const nearestLocations = musicCoordinates ?.map((location) => { diff --git a/src/screens/NowPlaying.js b/src/screens/NowPlaying.js index d9c56c2..7d757f9 100644 --- a/src/screens/NowPlaying.js +++ b/src/screens/NowPlaying.js @@ -26,10 +26,15 @@ import OgcisumText from '../components/OgcisumText'; /** * React component for main Now Playing page * - * @return {JSX.Element} React component for main Now Playing page. + * @returns {JSX.Element} React component for main Now Playing page. */ function NowPlaying() { - const { recordingData, hasRecordingData, fetchSTL, statusSTL } = useSamples(); + const { + recordingData, + hasRecordingData, + fetchSharedSamples, + statusSharedSamples, + } = useSamples(); const { liveLocations } = useLocation(); const { nearbyLocation } = liveLocations; @@ -41,26 +46,31 @@ function NowPlaying() { * When the user pull the screen down, this function * will be invoked. * - * @callback onRefresh - * */ - const onRefresh = useCallback(() => { + * @callback onRefreshPull + */ + const onRefreshPull = useCallback(() => { setRefreshing(true); - fetchSTL(); - if (statusSTL === 'success') { + fetchSharedSamples(); + if (statusSharedSamples === 'success') { setRefreshing(false); } - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - /** Reload webView when the screen changes */ + /** Flag for current focus state of the screen. */ const isFocused = useIsFocused(); + + /** + * Effect for reloading the WebView. + * When the user changes screen, this + * effect will run. + */ useEffect(() => { if (isFocused) { webViewRef.current.reload(); } }, [isFocused]); - /** useState to store webView loaded or actioned */ + /** useState to store webView loaded and actioned */ const [webViewState, setWebViewState] = useState({ loaded: false, actioned: false, @@ -68,20 +78,27 @@ function NowPlaying() { const webViewRef = useRef(); /** - * Function for onPress function on OgcisumButton + * Function for onLoad function on WebView. + * When the WebView is loaded, this function + * will be invoked. * * @callback onLoadWebview - * */ + */ const onLoadWebview = useCallback(() => { setWebViewState({ ...webViewState, loaded: true, }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [webViewState.loaded, webViewState.actioned]); - /** This function is invoked when play button is pressed (onPress) */ - function handleActionPress() { + /** + * Function for onPress function on PlayButton. + * When the Play Button is pressed, this function + * will be invoked. + * + * @callback onPressPlay + */ + const onPressPlay = useCallback(() => { const stringifiedSamples = JSON.stringify(recordingData); if (!webViewState.actioned) { webViewRef.current.injectJavaScript(`setupParts(${stringifiedSamples})`); @@ -93,7 +110,7 @@ function NowPlaying() { ...webViewState, actioned: !webViewState.actioned, }); - } + }, [webViewState.actioned, webViewState.loaded]); /** This is to set style dynamically depending on color scheme */ const { themeColors } = useTheme(); @@ -109,7 +126,7 @@ function NowPlaying() { refreshControl={ @@ -151,6 +168,14 @@ function NowPlaying() { ); } +/** + * React component for each Profile list item. + * + * @param {object} props - Object containing props for this component. + * @param {boolean} props.isUser - Flag to indicate whether the item is- + * showing user profile or not. + * @returns {JSX.Element} React component for Profile list item. + */ function ProfileListItem({ isUser }) { const { profile } = useProfile(); const { themeIcons } = useTheme(); @@ -182,6 +207,11 @@ ProfileListItem.propTypes = { isUser: PropTypes.bool, }; +/** + * React component for Profile list section. + * + * @returns {JSX.Element} React component for Profile list section. + */ function ProfileLists() { return ( @@ -192,7 +222,15 @@ function ProfileLists() { ); } -function PlayButton({ webViewState, handlePlay }) { +/** + * React component for Play Button in MusicPlayer section. + * + * @param {object} props - Object containing props for this component. + * @param {object} props.webViewState - Object containing WebView state. + * @param {Function} props.onPressPlay - Callback function invoked when user press Play. + * @returns {JSX.Element} React component for Play Button in MusicPlayer section. + */ +function PlayButton({ webViewState, onPressPlay }) { let buttonText; if (webViewState.loaded && !webViewState.actioned) { buttonText = 'Play Music'; @@ -204,7 +242,7 @@ function PlayButton({ webViewState, handlePlay }) { return ( @@ -213,10 +251,19 @@ function PlayButton({ webViewState, handlePlay }) { PlayButton.propTypes = { webViewState: PropTypes.object, - handlePlay: PropTypes.func, + onPressPlay: PropTypes.func, }; -function MusicPlayer({ nearbyLocation, webViewState, handlePlay }) { +/** + * React component for MusicPlayer section. + * + * @param {object} props - Object containing props for this component. + * @param {object} props.nearbyLocation - Object containing nearest location data. + * @param {object} props.webViewState - Object containing WebView state. + * @param {Function} props.onPressPlay - Callback function invoked when user press Play. + * @returns {JSX.Element} React component for MusicPlayer section. + */ +function MusicPlayer({ nearbyLocation, webViewState, onPressPlay }) { const { themeIcons } = useTheme(); return ( @@ -238,7 +285,7 @@ function MusicPlayer({ nearbyLocation, webViewState, handlePlay }) { {webViewState && ( - + )} @@ -248,7 +295,7 @@ function MusicPlayer({ nearbyLocation, webViewState, handlePlay }) { MusicPlayer.propTypes = { nearbyLocation: PropTypes.object, webViewState: PropTypes.object, - handlePlay: PropTypes.func, + onPressPlay: PropTypes.func, }; const styles = StyleSheet.create({ @@ -261,13 +308,11 @@ const styles = StyleSheet.create({ display: 'flex', flexDirection: 'row', }, - groupView: { paddingBottom: sizes.padding, }, headerContainer: { height: sizes.headerHeight, - // backgroundColor: 'red', }, headerIcon: { height: '55%', @@ -277,7 +322,6 @@ const styles = StyleSheet.create({ alignItems: 'center', flex: 1, justifyContent: 'center', - // backgroundColor: 'green', }, headerTextContainer: { flex: 3, @@ -309,7 +353,6 @@ const styles = StyleSheet.create({ }, section: { paddingVertical: sizes.padding, - // backgroundColor: 'green', }, }); diff --git a/src/screens/Profile.js b/src/screens/Profile.js index f861d67..57369e2 100644 --- a/src/screens/Profile.js +++ b/src/screens/Profile.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { SafeAreaView, @@ -16,14 +16,14 @@ import PropTypes from 'prop-types'; import { launchImageLibrary } from 'react-native-image-picker'; import KeyboardAvoidingView from 'react-native/Libraries/Components/Keyboard/KeyboardAvoidingView'; -import ButtonIOS from '../components/OgcisumButton'; +import OgcisumButton from '../components/OgcisumButton'; import { useProfile, useTheme } from '../context/Context'; import { colors, sizes } from '../data/theme'; /** * Main React component for Profile page * - * @return {JSX.Element} React component for Profile page + * @returns {JSX.Element} React component for Profile page */ export default function Profile() { const { profile, dispatchProfile: dispatch } = useProfile(); @@ -32,11 +32,11 @@ export default function Profile() { const hasPhoto = typeof profile.photo.uri !== 'undefined'; /** - * Function for onPress function on ButtonIOS + * Async Function for onPress function on OgcisumButton * - * @callback handleChangePress - * */ - async function handleChangePress() { + * @callback onPressChangePhoto + */ + async function onPressChangePhoto() { await launchImageLibrary() .then((result) => { if (typeof result.assets[0] === 'object') { @@ -57,7 +57,6 @@ export default function Profile() { themeTextColor: { color: themeColors.textColor }, }); - /** Main React Component for Profile page */ return ( @@ -69,9 +68,9 @@ export default function Profile() { Mirror, Mirror On The Wall... - - + @@ -82,15 +81,25 @@ export default function Profile() { /** * React component for Keyboard by wrapping the children and Text Input - * obtain Profile name - * @param {JSX.Element} children - React component(s) that are wrapped by this component - * @return {JSX.Element} React component for Keyboard and TextInput + * obtain Profile name. + * + * @param {JSX.Element} children - React component(s) that are wrapped by this component. + * @returns {JSX.Element} React component for Keyboard and TextInput. */ function KeyboardView({ children }) { const { dispatchProfile } = useProfile(); - function handleChange(value) { + /** + * Function for onChangeText function on Text Input. + * When the user type on the Text Field, this function + * will be invoked. + * + * @callback onChangeName + * @param {string} value - Text obtained from TextInput. + */ + const onChangeName = useCallback((value) => { dispatchProfile({ type: 'setName', name: value }); - } + }, []); + const { themeColors } = useTheme(); const dynamicStyles = StyleSheet.create({ @@ -126,7 +135,7 @@ function KeyboardView({ children }) { placeholder="Enter Your Name" placeholderTextColor={themeColors.fgColor} style={dynamicStyles.textInput} - onChangeText={(value) => handleChange(value)} + onChangeText={(value) => onChangeName(value)} /> @@ -142,11 +151,13 @@ KeyboardView.propTypes = { }; /** - * Main React component for Profile page - * @param {Object} photo - Object containing Photo data - * @param {JSX.Element} children - React component(s) that are wrapped by this component - * @param {handleChangePress} onPress - A callback to handle onPress event - * @return {JSX.Element} React component for Profile page + * React component for Profile Photo section. + * + * @param {object} props - props for this component. + * @param {object} props.photo - Object containing Photo data. + * @param {JSX.Element} props.children - React component(s) that are wrapped by this component. + * @param {Function} props.onPress - A callback to handle onPress event. + * @returns {JSX.Element} React component for Profile Photo section. */ function Photo({ photo, children, onPress }) { const hasPhoto = typeof photo.uri !== 'undefined'; diff --git a/yarn.lock b/yarn.lock index dd987ef..0fa058f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1070,6 +1070,15 @@ dependencies: "@types/hammerjs" "^2.0.36" +"@es-joy/jsdoccomment@~0.36.0": + "integrity" "sha512-u0XZyvUF6Urb2cSivSXA8qXIpT/CxkHcdtZKoWusAzgzmsTWpg0F2FpWXsolHmMUyVY3dLWaoy+0ccJ5uf2QjA==" + "resolved" "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.36.0.tgz" + "version" "0.36.0" + dependencies: + "comment-parser" "1.3.1" + "esquery" "^1.4.0" + "jsdoc-type-pratt-parser" "~3.1.0" + "@eslint/eslintrc@^1.3.3": "integrity" "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==" "resolved" "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz" @@ -2635,6 +2644,11 @@ "resolved" "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz" "version" "2.13.0" +"comment-parser@1.3.1": + "integrity" "sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==" + "resolved" "https://registry.npmjs.org/comment-parser/-/comment-parser-1.3.1.tgz" + "version" "1.3.1" + "commondir@^1.0.1": "integrity" "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" "resolved" "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz" @@ -2791,7 +2805,7 @@ dependencies: "ms" "^2.1.1" -"debug@^4.1.0", "debug@^4.1.1", "debug@^4.3.2", "debug@4": +"debug@^4.1.0", "debug@^4.1.1", "debug@^4.3.2", "debug@^4.3.4", "debug@4": "integrity" "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==" "resolved" "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" "version" "4.3.4" @@ -3144,6 +3158,19 @@ "resolved" "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.4.1.tgz" "version" "22.4.1" +"eslint-plugin-jsdoc@^39.6.2": + "integrity" "sha512-dvgY/W7eUFoAIIiaWHERIMI61ZWqcz9YFjEeyTzdPlrZc3TY/3aZm5aB91NUoTLWYZmO/vFlYSuQi15tF7uE5A==" + "resolved" "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-39.6.2.tgz" + "version" "39.6.2" + dependencies: + "@es-joy/jsdoccomment" "~0.36.0" + "comment-parser" "1.3.1" + "debug" "^4.3.4" + "escape-string-regexp" "^4.0.0" + "esquery" "^1.4.0" + "semver" "^7.3.8" + "spdx-expression-parse" "^3.0.1" + "eslint-plugin-n@^15.0.0", "eslint-plugin-n@^15.4.0": "integrity" "sha512-MkoKy9/lfd52TAXK4fkABgCp0aglk82Q3viy2UOWIEpTVE/Cem5P/UAxMBA4vSw7Gy+2egPqImE9euitLGp5aw==" "resolved" "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.4.0.tgz" @@ -4831,6 +4858,11 @@ "temp" "^0.8.4" "write-file-atomic" "^2.3.0" +"jsdoc-type-pratt-parser@~3.1.0": + "integrity" "sha512-MgtD0ZiCDk9B+eI73BextfRrVQl0oyzRG8B2BjORts6jbunj4ScKPcyXGTbB6eXL4y9TzxCm6hyeLq/2ASzNdw==" + "resolved" "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.1.0.tgz" + "version" "3.1.0" + "jsdom@^16.4.0": "integrity" "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==" "resolved" "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz" @@ -6609,6 +6641,13 @@ dependencies: "lru-cache" "^6.0.0" +"semver@^7.3.8": + "integrity" "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==" + "resolved" "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz" + "version" "7.3.8" + dependencies: + "lru-cache" "^6.0.0" + "semver@2 || 3 || 4 || 5": "integrity" "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" "resolved" "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" @@ -6831,7 +6870,7 @@ "resolved" "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz" "version" "2.3.0" -"spdx-expression-parse@^3.0.0": +"spdx-expression-parse@^3.0.0", "spdx-expression-parse@^3.0.1": "integrity" "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==" "resolved" "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz" "version" "3.0.1"