Repository: vangelov/calories-in Branch: master Commit: 2dd62babb5e8 Files: 340 Total size: 591.2 KB Directory structure: gitextract_ov6rt79l/ ├── .gitignore ├── .husky/ │ ├── .gitignore │ └── pre-commit ├── .prettierignore ├── .prettierrc ├── .vscode/ │ └── settings.json ├── LICENSE ├── README.md ├── package.json ├── public/ │ ├── browserconfig.xml │ ├── index.html │ ├── robots.txt │ └── site.webmanifest ├── src/ │ ├── App.tsx │ ├── diets/ │ │ ├── DietEditor/ │ │ │ ├── DndContextProvider.tsx │ │ │ ├── Form/ │ │ │ │ ├── About.tsx │ │ │ │ ├── Controls/ │ │ │ │ │ ├── ExportButton.tsx │ │ │ │ │ ├── MenuOrDrawer.tsx │ │ │ │ │ ├── Name.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── Footer.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── useDietFormEvents.ts │ │ │ │ └── useVariantFormActions.ts │ │ │ └── index.tsx │ │ ├── PdfDietEditor.tsx │ │ ├── dietForm.ts │ │ ├── index.ts │ │ ├── persistence/ │ │ │ ├── ExportModal/ │ │ │ │ ├── Content/ │ │ │ │ │ ├── Exporter/ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── usePdfExport.ts │ │ │ │ │ │ └── worker/ │ │ │ │ │ │ ├── custom.d.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── types.ts │ │ │ │ │ │ └── worker.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ ├── canExportDietForm.ts │ │ │ ├── hasMissingFoods.ts │ │ │ ├── index.ts │ │ │ ├── loadLastOrDefaultDietForm.ts │ │ │ ├── parseDietForm.ts │ │ │ ├── useDietImportErrors.tsx │ │ │ └── useImportDietForm.ts │ │ ├── types.ts │ │ ├── useDietFormStore.ts │ │ ├── useGetDietFormStatsTree.ts │ │ └── useScrollManager.ts │ ├── dom/ │ │ ├── animateScrollLeft.ts │ │ ├── index.ts │ │ ├── isElementInViewport.ts │ │ ├── useGetRefForId.ts │ │ └── useScrollTo.ts │ ├── foods/ │ │ ├── FoodInfo.tsx │ │ ├── FoodModal/ │ │ │ ├── Content/ │ │ │ │ ├── DeleteConfirmationModal.tsx │ │ │ │ ├── FoodFormProvider.tsx │ │ │ │ ├── Form/ │ │ │ │ │ ├── Footer.tsx │ │ │ │ │ ├── Header.tsx │ │ │ │ │ ├── Tabs/ │ │ │ │ │ │ ├── NutritionFactsFormFields.tsx │ │ │ │ │ │ ├── UrlField.tsx │ │ │ │ │ │ ├── VolumeFormFields.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── useSubmitFoodForm.ts │ │ │ │ │ └── useTabs.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── useDeleteFood.ts │ │ │ └── index.tsx │ │ ├── FoodsDrawer/ │ │ │ ├── Content/ │ │ │ │ ├── Header.tsx │ │ │ │ ├── MenuButtons.tsx │ │ │ │ ├── SelectedFoodsList/ │ │ │ │ │ ├── SelectedFoodItem.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── useFoodEvents.ts │ │ │ └── index.tsx │ │ ├── FoodsList/ │ │ │ ├── VirtualizedList/ │ │ │ │ ├── FoodItem/ │ │ │ │ │ ├── AnimateAppear.tsx │ │ │ │ │ ├── DisappearingBox.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── FoodItemRenderer.tsx │ │ │ │ ├── Inner.tsx │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── builtIn/ │ │ │ ├── bakedProducts.json │ │ │ ├── beef.json │ │ │ ├── beverages.json │ │ │ ├── dairyAndEggs.json │ │ │ ├── fatsAndOils.json │ │ │ ├── finfishAndShellFish.json │ │ │ ├── fruitsAndJuices.json │ │ │ ├── grainsAndPasta.json │ │ │ ├── index.ts │ │ │ ├── legumesAndLegumeProducts.json │ │ │ ├── nutAndSeedProducts.json │ │ │ ├── pork.json │ │ │ ├── poultry.json │ │ │ ├── saucesAndSoups.json │ │ │ ├── spicesAndHerbs.json │ │ │ ├── sweetsAndSnacks.json │ │ │ └── vegetables.json │ │ ├── foodForm.ts │ │ ├── foodVolumeForm.ts │ │ ├── index.ts │ │ ├── persistence/ │ │ │ ├── FoodsListModal/ │ │ │ │ ├── Content.tsx │ │ │ │ └── index.tsx │ │ │ ├── MissingFoodsModal.tsx │ │ │ ├── index.ts │ │ │ ├── loadFoods.ts │ │ │ └── useImportFoods.ts │ │ ├── types.ts │ │ └── useFoodsStore.ts │ ├── foods-categories/ │ │ ├── FoodCategoriesSelect.tsx │ │ ├── categories.json │ │ ├── index.ts │ │ └── types.ts │ ├── foods-filters/ │ │ ├── FoodsFilterPopoverOrModal/ │ │ │ ├── Content.tsx │ │ │ ├── Footer.tsx │ │ │ ├── Modal.tsx │ │ │ ├── Popover.tsx │ │ │ ├── Trigger.tsx │ │ │ └── index.tsx │ │ ├── foodsFilter.ts │ │ ├── index.ts │ │ ├── persistence/ │ │ │ ├── index.ts │ │ │ └── loadFoodsFilter.ts │ │ ├── useFilterFoods.ts │ │ └── useFoodsFilterStore.ts │ ├── form/ │ │ ├── duplicate.ts │ │ ├── index.ts │ │ ├── names.ts │ │ ├── types.ts │ │ ├── useFormError.ts │ │ └── useSelectInputText.ts │ ├── general/ │ │ ├── Badge.tsx │ │ ├── ContextMenuFlex.tsx │ │ ├── HFadeScroll/ │ │ │ ├── FadeBox.tsx │ │ │ ├── ScrollContainer.tsx │ │ │ └── index.tsx │ │ ├── Loader.tsx │ │ ├── Menu.tsx │ │ ├── MenuOrDrawer/ │ │ │ ├── Drawer/ │ │ │ │ ├── getDrawerButtons.tsx │ │ │ │ └── index.tsx │ │ │ ├── Menu/ │ │ │ │ ├── getMenuItems.tsx │ │ │ │ └── index.tsx │ │ │ ├── MenuOrDrawerItem.tsx │ │ │ ├── MenuOrDrawerSeparator.tsx │ │ │ ├── Trigger.tsx │ │ │ └── index.tsx │ │ ├── ResponsiveButton.tsx │ │ ├── ResponsiveIconButton.tsx │ │ ├── RightAligned.tsx │ │ ├── ScreenSizeProvider/ │ │ │ ├── context.ts │ │ │ └── index.tsx │ │ ├── Tooltip.tsx │ │ ├── TooltipCommandLabel.tsx │ │ ├── deepCopy.ts │ │ ├── getCtrlKeyName.ts │ │ ├── index.ts │ │ ├── minDelay.ts │ │ ├── stores.tsx │ │ ├── useElementHeight.ts │ │ ├── useOneTimeCheckStore.ts │ │ ├── useRunIfNotUnmounted.ts │ │ ├── useSameOrPreviousValue.ts │ │ └── useSelection.ts │ ├── icons/ │ │ ├── CalendarPlus.tsx │ │ └── index.ts │ ├── index.tsx │ ├── ingredients/ │ │ ├── IngredientsList/ │ │ │ ├── EmptyList.tsx │ │ │ ├── IngredientItem/ │ │ │ │ ├── MenuOrDrawer.tsx │ │ │ │ ├── MissingStatsLayout.tsx │ │ │ │ ├── Notes.tsx │ │ │ │ ├── PresenceAnimation.tsx │ │ │ │ ├── StatsLayout.tsx │ │ │ │ ├── getMenuOrDrawerItems.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── useIngredientsEvents.ts │ │ │ │ └── useNotesEvents.tsx │ │ │ └── index.tsx │ │ ├── PdfIngredientsList/ │ │ │ ├── PdfIngredientItem/ │ │ │ │ ├── FoodName.tsx │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── getIngredient.ts │ │ ├── index.ts │ │ ├── ingredientForm.ts │ │ ├── types.ts │ │ ├── useGetIngredientFormStatsTree.ts │ │ └── useIngredientsFormsActions.ts │ ├── layout/ │ │ ├── MainLayout.tsx │ │ ├── Page/ │ │ │ ├── ElementContainer.tsx │ │ │ ├── PageBody.tsx │ │ │ ├── PageFooter.tsx │ │ │ ├── PageHeader.tsx │ │ │ └── index.tsx │ │ ├── RightAligned.tsx │ │ ├── index.tsx │ │ └── useHasSideNavigation.ts │ ├── meals/ │ │ ├── MealsList/ │ │ │ ├── EmptyList.tsx │ │ │ ├── MealItem/ │ │ │ │ ├── Header/ │ │ │ │ │ ├── MenuOrDrawer.tsx │ │ │ │ │ ├── Name.tsx │ │ │ │ │ ├── getMenuOrDrawerItems.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── Notes.tsx │ │ │ │ ├── PresenceAnimation.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── useGetAndUpdateStats.ts │ │ │ │ └── useMealFormEvents.ts │ │ │ ├── MealsControls.tsx │ │ │ ├── index.tsx │ │ │ └── useScrollToAndFocusMeal.ts │ │ ├── PdfMealsList/ │ │ │ ├── Notes.tsx │ │ │ ├── PdfMealItem.tsx │ │ │ └── index.tsx │ │ ├── index.ts │ │ ├── mealForm.ts │ │ ├── types.ts │ │ ├── useGetMealFormStatsTree.ts │ │ └── useMealsFormsActions.ts │ ├── notes/ │ │ ├── EditNotesModal/ │ │ │ ├── Content/ │ │ │ │ ├── Form/ │ │ │ │ │ ├── Header.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── NotesFormProvider.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── notesForm.ts │ │ │ └── index.tsx │ │ ├── index.ts │ │ └── notesForm.ts │ ├── persistence/ │ │ ├── DownloadButton.tsx │ │ ├── file.ts │ │ ├── fixWhiteSpace.tsx │ │ ├── getUntitledFileName.ts │ │ ├── index.ts │ │ ├── useBlobUrl.ts │ │ ├── useImportFileError.ts │ │ └── useSaveValue.ts │ ├── portions/ │ │ ├── PortionsMenuOrDrawer/ │ │ │ ├── Drawer/ │ │ │ │ ├── PortionItem.tsx │ │ │ │ └── index.tsx │ │ │ ├── Menu.tsx │ │ │ ├── Trigger.tsx │ │ │ └── index.tsx │ │ ├── PortionsSelect.tsx │ │ ├── defaultPortions.ts │ │ ├── formatAmount.ts │ │ ├── getAmountFromPortionsToGrams.ts │ │ ├── getIngredientPortionDescription.ts │ │ ├── getPortionDescription.ts │ │ ├── getToGramsConversionFactor.ts │ │ ├── index.ts │ │ ├── types.ts │ │ ├── useGetAmount.ts │ │ ├── useGetToGramsConversionFactor.ts │ │ └── usePortionsStore.ts │ ├── react-app-env.d.ts │ ├── reportWebVitals.ts │ ├── setupTests.ts │ ├── stats/ │ │ ├── AmountInput.tsx │ │ ├── EnergyStat.tsx │ │ ├── PdfStat.tsx │ │ ├── PdfStatsLayout.tsx │ │ ├── Stat.tsx │ │ ├── StatValueDetail.tsx │ │ ├── StatsFormFields/ │ │ │ ├── MacrosFormFields.tsx │ │ │ ├── ReavealButton.tsx │ │ │ ├── StatFormField/ │ │ │ │ ├── ReadOnlyInput.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── types.ts │ │ │ │ └── useGetInputElement.tsx │ │ │ ├── VitaminsAndMineralsFormFields.tsx │ │ │ ├── index.tsx │ │ │ ├── useGetDailyValuePercent.ts │ │ │ └── useGetValue.ts │ │ ├── StatsLayout.tsx │ │ ├── amountAsNumber.ts │ │ ├── calculations/ │ │ │ ├── aggregateStats.ts │ │ │ ├── getDailyValuePercent.ts │ │ │ ├── getEnergiesEstimates.ts │ │ │ ├── getMacrosPercents.ts │ │ │ ├── getStatsTree.ts │ │ │ ├── index.ts │ │ │ └── roundMacrosPercents.ts │ │ ├── getUnit.ts │ │ ├── index.ts │ │ ├── objectFromNutritionDataKeys.ts │ │ ├── statsVariants.ts │ │ ├── types.ts │ │ ├── useMealsStatsStore.ts │ │ ├── useUpdateMealStats.ts │ │ └── useVariantStats.ts │ ├── theme/ │ │ ├── colors.ts │ │ ├── components/ │ │ │ ├── Alert.ts │ │ │ ├── Button.ts │ │ │ ├── Divider.ts │ │ │ ├── Input.ts │ │ │ ├── Textarea.ts │ │ │ └── index.ts │ │ ├── getComputedColorFromChakra.ts │ │ ├── index.ts │ │ └── styles.ts │ ├── undoRedo/ │ │ ├── UndoRedoButtons/ │ │ │ ├── RedoButton.tsx │ │ │ ├── UndoButton.tsx │ │ │ └── index.tsx │ │ ├── appLocation.ts │ │ ├── deltasStack.ts │ │ ├── index.ts │ │ ├── useDietFormVersionsStore.ts │ │ └── useKeyboard.ts │ └── variants/ │ ├── PdfVariantsList/ │ │ ├── PdfVariantItem.tsx │ │ └── index.tsx │ ├── VariantStats/ │ │ ├── EnergyStat.tsx │ │ ├── VariantStat.tsx │ │ └── index.tsx │ ├── VariantsDetailsModal/ │ │ ├── Content/ │ │ │ ├── FormFields.tsx │ │ │ ├── VariantsDetailsFormProvider.tsx │ │ │ ├── index.tsx │ │ │ ├── useVariantFormEvents.ts │ │ │ └── variantsDetailsForm.ts │ │ └── index.tsx │ ├── VariantsList/ │ │ ├── AddVariantButton.tsx │ │ ├── ScrollButtons.tsx │ │ ├── VariantItem/ │ │ │ ├── PresenceAnimation.tsx │ │ │ ├── getMenuOrDrawerItems.tsx │ │ │ ├── index.tsx │ │ │ ├── useScrollIntoView.ts │ │ │ └── useVariantFormEvents.ts │ │ ├── VariantNameModal/ │ │ │ ├── Content.tsx │ │ │ ├── VariantNameFormProvider.tsx │ │ │ ├── index.tsx │ │ │ ├── useSubmitVariantNameForm.ts │ │ │ └── variantNameForm.ts │ │ ├── VariantsMenuOrDrawer/ │ │ │ ├── Drawer/ │ │ │ │ ├── VariantItem.tsx │ │ │ │ └── index.tsx │ │ │ ├── Menu.tsx │ │ │ ├── Trigger.tsx │ │ │ └── index.tsx │ │ ├── index.tsx │ │ ├── useScrollState.ts │ │ └── useVariantFormEvents.ts │ ├── getVariantFormIndexAfterRemove.ts │ ├── index.ts │ ├── useGetVariantFormStatsTree.ts │ ├── useVariantsFormsActions.ts │ └── variantForm.ts └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /build # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* ================================================ FILE: .husky/.gitignore ================================================ _ ================================================ FILE: .husky/pre-commit ================================================ #!/bin/sh . "$(dirname "$0")/_/husky.sh" npx lint-staged ================================================ FILE: .prettierignore ================================================ **/node_modules **/dist **/package.json **/yarn.lock **/package-lock.json **/.eslintrc.json **/tsconfig.json ================================================ FILE: .prettierrc ================================================ { "arrowParens": "avoid", "singleQuote": true, "semi": false, "printWidth": 80 } ================================================ FILE: .vscode/settings.json ================================================ { "editor.formatOnPaste": true, "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode" } ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021 Vladimir Angelov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

Devices preview

Calories-In

A web-based meal plan editor for people who prepare all of their food.


Live version: https://calories-in.com Demo video: https://tella.video/calories-in-4onp
## Background The idea was born out of my experience of trying to find a better alternative to Google Sheets for calculating the macros of my meal plans. I wanted to be able to do this on desktop as it's more convenient but nothing really felt fast and simple enough. A huge inspiration for me has been [excalidraw.com](http://excalidraw.com). ## Notable libraries - [Chakra UI](https://chakra-ui.com/) - [React Beautiful Dnd](https://github.com/atlassian/react-beautiful-dnd) - [React-pdf](https://react-pdf.org/) - [Comlink-loader](https://github.com/GoogleChromeLabs/comlink-loader) - [React window](https://github.com/bvaughn/react-window) - [Fuse.js](https://fusejs.io/) - [Framer Motion](https://www.framer.com/motion/) - [Feather icons](https://feathericons.com/) ## Available Scripts In the project directory, you can run: ### `yarn start` Runs the app in the development mode.\ Open [http://localhost:3000](http://localhost:3000) to view it in the browser. ### `yarn build` Builds the app for production to the `build` folder.\ It correctly bundles React in production mode and optimizes the build for the best performance. ================================================ FILE: package.json ================================================ { "name": "energytab", "version": "0.1.0", "private": true, "dependencies": { "@chakra-ui/react": "^1.3.4", "@emotion/css": "^11.1.3", "@emotion/react": "^11.1.5", "@emotion/styled": "^11.1.5", "@hookform/resolvers": "^2.6.0", "@react-hook/resize-observer": "^1.2.0", "@react-pdf/renderer": "^2.0.19", "@szhsin/react-menu": "1.11.0", "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", "@types/jest": "^26.0.15", "@types/node": "^12.0.0", "@types/react": "^17.0.0", "@types/react-dom": "^17.0.0", "@types/react-window": "^1.8.3", "@types/smoothscroll-polyfill": "^0.3.1", "@types/uuid": "^8.3.0", "comlink-loader": "^2.0.0", "focus-visible": "^5.2.0", "format-quantity": "^1.0.1", "framer-motion": "^3.10.0", "fuse.js": "^6.4.6", "immer": "^9.0.5", "jsondiffpatch": "^0.4.1", "numeric-quantity": "^1.0.2", "pretty-bytes": "^5.6.0", "react": "^17.0.1", "react-beautiful-dnd": "13.0.0", "react-device-detect": "^1.17.0", "react-dom": "^17.0.1", "react-feather": "^2.0.9", "react-hook-form": "7.8.5", "react-merge-refs": "^1.1.0", "react-scripts": "4.0.3", "react-window": "^1.8.6", "scroll-polyfill": "^1.0.1", "source-map-explorer": "^2.5.2", "typescript": "^4.1.2", "uuid": "^8.3.2", "web-vitals": "^1.0.1", "yup": "^0.32.9" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", "format": "prettier --write src/**/*.{ts,tsx}", "lint": "tsc --noEmit && eslint src/**/*.{ts,tsx}", "prepare": "husky install", "analyze": "source-map-explorer 'build/static/js/*.js'" }, "lint-staged": { "src/**/*.{ts,tsx}": [ "yarn lint" ], "*.{ts,tsx}": "prettier --write" }, "eslintConfig": { "extends": [ "react-app", "react-app/jest" ] }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "devDependencies": { "@types/react-beautiful-dnd": "^13.0.0", "@types/react-custom-scroll": "^4.2.1", "@types/react-custom-scrollbars": "^4.0.7", "husky": "^6.0.0", "lint-staged": "^10.5.4", "prettier": "^2.2.1" } } ================================================ FILE: public/browserconfig.xml ================================================ #da532c ================================================ FILE: public/index.html ================================================ Calories-In
================================================ FILE: public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: public/site.webmanifest ================================================ { "name": "", "short_name": "", "icons": [ { "src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" } ], "theme_color": "#319795", "background_color": "#319795", "display": "standalone" } ================================================ FILE: src/App.tsx ================================================ import { ChakraProvider } from '@chakra-ui/react' import { MainLayout } from 'layout' import 'focus-visible/dist/focus-visible' import theme from 'theme' import { FoodsStoreProvider } from 'foods' import { loadFoods } from 'foods/persistence' import { OneTimeCheckStoreProvider, ScreenSizeProvider } from 'general' import { DietEditor } from 'diets' import { useState } from 'react' import { PortionsStoreProvider } from 'portions' import 'scroll-polyfill/auto' function App() { const [foods] = useState(loadFoods) return ( ) } export default App ================================================ FILE: src/diets/DietEditor/DndContextProvider.tsx ================================================ import { useDietFormActions } from 'diets' import { ReactNode } from 'react' import { DragDropContext, DropResult } from 'react-beautiful-dnd' type Props = { children: ReactNode } function DndContextProvider({ children }: Props) { const dietFormActions = useDietFormActions() const onDragEnd = (dropResult: DropResult) => { const { source, destination, type } = dropResult if (!destination) { return } if (type === 'variantsList') { dietFormActions.moveVariantForm(source.index, destination.index) } else if (type === 'mealsList') { dietFormActions.moveMealForm(source.index, destination.index) } else if (type === 'ingredientsList') { dietFormActions.moveIngredientForm( source.droppableId, source.index, destination.droppableId, destination.index ) } } return {children} } export default DndContextProvider ================================================ FILE: src/diets/DietEditor/Form/About.tsx ================================================ import { Modal, ModalOverlay, ModalContent, ModalHeader, ModalFooter, ModalBody, ModalCloseButton, Button, Text, ListItem, List, ListIcon, } from '@chakra-ui/react' import { CheckCircle } from 'react-feather' type Props = { isOpen: boolean onClose: () => void } function About({ isOpen, onClose }: Props) { return ( About Hi, I'm Vladimir, the person behind this project.
Calories-In {' '} is made for people who follow meal plans that involve preparing everything by yourself and gives them full control to fine tune the nutritional values.
The idea was born out of my experience of trying to find a better alternative to Google Sheets for calculating the macros of my own meal plans. I wanted to be able to do this on desktop as it's more convenient but nothing really felt fast and simple enough.
The main differences to other apps in this space are:
Faster search There are actually not that many foods you need when you prepare everything yourself. This means all of the food data can be downloaded beforehand which makes the search super fast. Of course you can add your own foods if you'd like.{' '}
Undo/Redo Building a plan from scratch or updating an existing one involves some back and forth choosing the right foods and adjusting their amounts. This is especially true if you want to be as close as possible to a specific calorie limit and have your macros be a certain percentages split.
Faster export Creating the PDF file for your meal plan is done entirely inside the browser. It does not involve generating and downloading it from a server. This means I can keep the cost of running the website low and you get your file in just a few seconds.
Simpler There are no other pages except the editor. Most of the other tools are bloated with additional features for professionals, such as managing clients, creating invoices, etc.
Fully mobile You can use your phone or tablet to build your meal plans right from your browser. If you add the app to your home screen it will look and feel almost like a native one.

) } export default About ================================================ FILE: src/diets/DietEditor/Form/Controls/ExportButton.tsx ================================================ import { ScreenSize, useScreenSize } from 'general' import { Button, ButtonProps, IconButton } from '@chakra-ui/react' import { Share } from 'react-feather' import { canExportDietForm } from 'diets/persistence' import { useDietForm } from 'diets' type Props = {} & ButtonProps function ExportButton({ ...rest }: Props) { const screenSize = useScreenSize() const dietForm = useDietForm() const canExport = canExportDietForm(dietForm) const commonProps: ButtonProps = { isDisabled: !canExport, ...rest, } if (screenSize >= ScreenSize.Medium) { return ( ) } return ( } {...commonProps} /> ) } export default ExportButton ================================================ FILE: src/diets/DietEditor/Form/Controls/MenuOrDrawer.tsx ================================================ import { chakra } from '@chakra-ui/react' import { Download, List, MoreHorizontal, Trash } from 'react-feather' import { MenuOrDrawer as MenuOrDrawerBase, MenuOrDrawerItem, MenuOrDrawerSeparator, ScreenSize, useScreenSize, } from 'general' const DownloadStyled = chakra(Download) const ListStyled = chakra(List) const TrashStyled = chakra(Trash) type Props = { onImport: () => void onClear: () => void onViewFoods: () => void } function MenuOrDrawer({ onImport, onClear, onViewFoods }: Props) { const screenSize = useScreenSize() const items = [ , } onClick={onImport} > Import meal plan , } onClick={onViewFoods} > Manage foods , ] if (screenSize <= ScreenSize.Small) { items.unshift( } onClick={onClear} > Clear ) } return ( } aria-label="More actions" > {items} ) } export default MenuOrDrawer ================================================ FILE: src/diets/DietEditor/Form/Controls/Name.tsx ================================================ import { useDietForm, useDietFormActions } from 'diets' import { Editable, EditableInput, EditablePreview } from '@chakra-ui/react' import { ChangeEvent, useEffect, useRef } from 'react' import getComputedColorFromChakra from 'theme/getComputedColorFromChakra' import { canExportDietForm } from 'diets/persistence' function Name() { const dietForm = useDietForm() const dietFormActions = useDietFormActions() const editablePreviewRef = useRef(null) const focusedForDietFieldIdMap = useRef>( {} ) useEffect(() => { if ( !canExportDietForm(dietForm) && editablePreviewRef.current && !focusedForDietFieldIdMap.current[dietForm.fieldId] ) { editablePreviewRef.current.focus() focusedForDietFieldIdMap.current[dietForm.fieldId] = true } }, [dietForm]) function onNameChange(event: ChangeEvent) { const { value } = event.target dietFormActions.updateDietForm({ name: value }) } const boxShadowColor = getComputedColorFromChakra('teal.400') return ( ) } export default Name ================================================ FILE: src/diets/DietEditor/Form/Controls/index.tsx ================================================ import { Flex, useDisclosure, Button } from '@chakra-ui/react' import { UndoRedoButtons, useKeyboard } from 'undoRedo' import { getDietForm, useDietFormActions } from 'diets' import { useImportDietForm, ExportModal } from 'diets/persistence' import { FoodsListModal, MissingFoodsModal, useImportFoods, } from 'foods/persistence' import { FoodsDrawer } from 'foods' import { Trash } from 'react-feather' import MenuOrDrawer from './MenuOrDrawer' import Name from './Name' import { ScreenSize, useScreenSize } from 'general' import ExportButton from './ExportButton' function Controls() { const dietFormActions = useDietFormActions() const exportModalDisclosure = useDisclosure() const missingFoodsModalDisclosure = useDisclosure() const { onLoadFromFile } = useImportDietForm({ missingFoodsModalDisclosure }) const foodsListModalDisclosure = useDisclosure() const importFoods = useImportFoods({ foodsListModalDisclosure }) const foodsDrawerDisclosure = useDisclosure() const screenSize = useScreenSize() useKeyboard() function onClear() { dietFormActions.setDietForm(getDietForm()) } return ( {screenSize >= ScreenSize.Medium && ( )} ) } export default Controls ================================================ FILE: src/diets/DietEditor/Form/Footer.tsx ================================================ import { Box, HStack, Button, Divider, Link, BoxProps } from '@chakra-ui/react' type Props = { onAbout: () => void } & BoxProps function Footer({ onAbout, ...rest }: Props) { return ( Terms Disclaimer ) } export default Footer ================================================ FILE: src/diets/DietEditor/Form/index.tsx ================================================ import { useDietForm, useScrollManager } from 'diets' import { DietFormVersionsStoreProvider } from 'undoRedo' import { useRef } from 'react' import { Page, PageHeader, PageBody } from 'layout' import { MealsList } from 'meals' import useDietFormEvents from './useDietFormEvents' import { Box, useDisclosure, Flex } from '@chakra-ui/react' import { ScreenSize, useElementHeight, useScreenSize } from 'general' import Controls from './Controls' import { FoodsDrawer } from 'foods' import { VariantsList, VariantStats } from 'variants' import useVariantFormEvents from './useVariantFormActions' import About from './About' import Footer from './Footer' function Form() { const horizontalScrollRef = useRef(null) const dietForm = useDietForm() const { variantsForms } = dietForm const selectedVariantForm = variantsForms[dietForm.selectedVariantFormIndex] const scrollManager = useScrollManager({ selectedVariantForm, horizontalScrollRef, }) const foodsDrawerDisclosure = useDisclosure() const dietFormEvents = useDietFormEvents({ scrollManager, foodsDrawerDisclosure, }) const { elementHeight: headerHeight, elementRef: headerRef, } = useElementHeight() const variantFormEvents = useVariantFormEvents({ scrollManager }) const screenSize = useScreenSize() const aboutModalDisclosure = useDisclosure() return ( {screenSize >= ScreenSize.Large && ( )}