diff --git a/.eslintrc.js b/.eslintrc.js index 7e8be13..f694bf2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,14 +4,30 @@ module.exports = { // A temporary hack related to IDE not resolving correct package.json 'import/no-extraneous-dependencies': 'off', 'import/no-unresolved': 'error', + 'import/no-cycle': 'off', + 'import/prefer-default-export': 'off', // Since React 17 and typescript 4.1 you can safely disable the rule 'react/react-in-jsx-scope': 'off', 'react/jsx-props-no-spreading': 'off', - 'import/no-cycle': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', 'react/require-default-props': [2, { functions: 'defaultArguments' }], - 'import/prefer-default-export': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + }, + ], + '@typescript-eslint/naming-convention': [ + 'error', + { + format: null, + selector: 'variable', + leadingUnderscore: 'allow', + }, + ], }, parserOptions: { ecmaVersion: 2020, diff --git a/assets/icon.icns b/assets/icon.icns index 33d14a3..7a5b4f8 100644 Binary files a/assets/icon.icns and b/assets/icon.icns differ diff --git a/assets/icon.ico b/assets/icon.ico index 111184b..6e4c777 100644 Binary files a/assets/icon.ico and b/assets/icon.ico differ diff --git a/package-lock.json b/package-lock.json index b63e702..f61710b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1112,10 +1112,10 @@ "@chakra-ui/utils": "1.10.4" } }, - "@codiga/codiga-components": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@codiga/codiga-components/-/codiga-components-1.0.5.tgz", - "integrity": "sha512-khsatjVGvnZk/aSk2HiAsfWJTsbZZS8rWmGLnwv4A14e+upPInJ/KfDeJiGMkyYziE7bJq0e1SBfOlbdlWzGIA==" + "@codiga/components": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@codiga/components/-/components-1.1.0.tgz", + "integrity": "sha512-KkzhYI4cBaaiMgK8vkaoa+OQCCYruBGwVTOV9R2ilzMp6bIGr8JR4aWUXcuOJOFFv+FRIrplLW6e9NRlH1dGAQ==" }, "@cspotcode/source-map-support": { "version": "0.8.1", diff --git a/package.json b/package.json index 342123c..dd6f63d 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "@apollo/client": "^3.6.9", "@chakra-ui/icons": "^1.1.7", "@chakra-ui/react": "^1.8.8", - "@codiga/codiga-components": "^1.0.5", + "@codiga/components": "^1.1.0", "@electron/remote": "^2.0.8", "@emotion/react": "^11.10.0", "@emotion/styled": "^11.10.0", diff --git a/src/main/main.ts b/src/main/main.ts index 4bb408a..95b6345 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -46,7 +46,7 @@ if (isDebug) { const installExtensions = async () => { const installer = require('electron-devtools-installer'); const forceDownload = !!process.env.UPGRADE_EXTENSIONS; - const extensions = ['REACT_DEVELOPER_TOOLS']; + const extensions = ['REACT_DEVELOPER_TOOLS', 'APOLLO_DEVELOPER_TOOLS']; return installer .default( @@ -140,7 +140,11 @@ app mainWindow?.minimize(); }); ipcMain.on('maximizeApp', () => { - mainWindow?.maximize(); + if (mainWindow?.isMaximized()) { + mainWindow?.unmaximize(); + } else { + mainWindow?.maximize(); + } }); ipcMain.on('closeApp', () => { mainWindow?.close(); diff --git a/src/main/preload.ts b/src/main/preload.ts index 149f127..ccdddf6 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -7,6 +7,7 @@ export type Channels = | 'closeApp'; contextBridge.exposeInMainWorld('electron', { + isMac: process.platform === 'darwin', ipcRenderer: { sendMessage(channel: Channels, args: unknown[]) { ipcRenderer.send(channel, args); diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 2b915d2..19bf9a5 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -1,6 +1,6 @@ import { ApolloProvider } from '@apollo/client'; import { ChakraProvider } from '@chakra-ui/react'; -import { theme } from '@codiga/codiga-components'; +import { theme } from '@codiga/components'; import { MemoryRouter as Router, Routes, Route } from 'react-router-dom'; // PAGES @@ -24,6 +24,8 @@ import Filters from './components/Filters/Filters'; import { UserProvider } from './components/UserContext'; import { ThemeProvider } from './components/ThemeContext'; import { FiltersProvider } from './components/FiltersContext'; +import ViewSnippet from './pages/ViewSnippet'; +import ViewCookbookSnippets from './pages/ViewCookbookSnippets'; export default function App() { return ( @@ -31,30 +33,41 @@ export default function App() { - - - - - + + + - } /> + }> + } /> + } /> + } + /> + } /> + } + /> + } /> + } + /> + - } /> } + path="/view-snippet/:snippetId" + element={} /> - } /> } + path="/view-cookbook/:cookbookId" + element={} /> - } /> - } /> - - - + + + diff --git a/src/renderer/components/AvatarAndName/AvatarAndName.tsx b/src/renderer/components/AvatarAndName/AvatarAndName.tsx new file mode 100644 index 0000000..74db1a0 --- /dev/null +++ b/src/renderer/components/AvatarAndName/AvatarAndName.tsx @@ -0,0 +1,30 @@ +import { Flex, Text } from '@chakra-ui/react'; +import { Avatar } from '@codiga/components'; +import { PublicUser } from '../../types/userTypes'; +import { getAvatarUrl } from '../../utils/userUtils'; +import UserLink from '../UserLink'; + +type AvatarAndNameProps = { + owner?: PublicUser; +}; + +export default function AvatarAndName({ owner = {} }: AvatarAndNameProps) { + return ( + + + + + + + ); +} diff --git a/src/renderer/components/AvatarAndName/index.tsx b/src/renderer/components/AvatarAndName/index.tsx new file mode 100644 index 0000000..ed59e15 --- /dev/null +++ b/src/renderer/components/AvatarAndName/index.tsx @@ -0,0 +1 @@ +export { default } from './AvatarAndName'; diff --git a/src/renderer/components/BackButton/BackButton.tsx b/src/renderer/components/BackButton/BackButton.tsx new file mode 100644 index 0000000..51f77be --- /dev/null +++ b/src/renderer/components/BackButton/BackButton.tsx @@ -0,0 +1,22 @@ +import { IconButton } from '@chakra-ui/react'; +import { ChevronLeftIcon } from '@codiga/components'; +import { useNavigate } from 'react-router-dom'; + +export default function BackButton() { + const navigate = useNavigate(); + + return ( + navigate(-1)} + h="28px" + minW="28px" + fontSize="12px" + icon={} + aria-label="go back" + _focus={{ + boxShadow: 'none', + }} + /> + ); +} diff --git a/src/renderer/components/BackButton/index.tsx b/src/renderer/components/BackButton/index.tsx new file mode 100644 index 0000000..27d1ce8 --- /dev/null +++ b/src/renderer/components/BackButton/index.tsx @@ -0,0 +1 @@ +export { default } from './BackButton'; diff --git a/src/renderer/components/Code/Code.tsx b/src/renderer/components/Code/Code.tsx new file mode 100644 index 0000000..b045008 --- /dev/null +++ b/src/renderer/components/Code/Code.tsx @@ -0,0 +1,235 @@ +import { useEffect } from 'react'; +import { + Flex, + LinkBox, + IconButton, + useClipboard, + useColorModeValue, + useToken, + Tooltip, + Text, + Link, + Box, + Menu, + MenuButton, + Portal, + MenuList, + MenuItem, +} from '@chakra-ui/react'; +import { + BubbleIcon, + Code as CodigaCode, + CodeContent, + CopyIcon, + PencilIcon, + useToast, +} from '@codiga/components'; + +import { getSnippetUrl } from '../../utils/urlUtils'; +import useCodeView, { CodeViewsType } from '../../hooks/useCodeView'; +import { APP_URL } from '../../lib/config'; +import { AssistantRecipeWithStats } from '../../types/assistantTypes'; +import { decodeIndent } from '../../utils/codeUtils'; +import CodeViewToggler from './CodeViewToggler'; +import { useUser } from '../UserContext'; + +type CodeProps = { + recipe: AssistantRecipeWithStats; +}; + +export default function Code({ recipe }: CodeProps) { + const toast = useToast(); + const { id: userId } = useUser(); + + const [codeView, setCodeView] = useCodeView('preview'); + + const neutral100 = useToken('colors', 'neutral.100'); + const bg = useColorModeValue('white', neutral100); + + const code = + codeView === 'preview' + ? decodeIndent(recipe?.presentableFormat) + : decodeIndent(recipe?.code); + const imports = recipe?.imports?.join('\n'); + const codeForCopy = imports ? `${imports}\n${code}` : code; + + const { hasCopied, onCopy } = useClipboard(codeForCopy); + + useEffect(() => { + if (hasCopied) { + toast({ status: 'success', description: 'Snippet copied' }); + } + }, [hasCopied, toast]); + + const commentsCount = Number(recipe.commentsCount); + const lines = code.split('\n').length; + const lineMaxDigits = lines.toString().length; + const minWidth = lineMaxDigits < 3 ? '2.7em' : `${lineMaxDigits}.25em`; + + return ( + + + + span:first-child > .linenumber:first-child': + { + paddingTop: '0.5em !important', + }, + 'code[class*="language-"] .linenumber': { + border: '0 !important', + background: 'transparent !important', + fontStyle: 'normal !important', + }, + }} + > + + setCodeView(value as CodeViewsType), + }} + /> + + + } + onClick={onCopy} + aria-label="Copy Snippet" + /> + + + + + + + {commentsCount} + + + } + aria-label="Comment on Snippet" + /> + + + {userId && recipe.owner && userId === recipe.owner.id && ( + + + ••• + + + + + + Edit Snippet + + + + + + )} + + + + {code} + + + + + + ); +} diff --git a/src/renderer/components/Code/CodeLoading.tsx b/src/renderer/components/Code/CodeLoading.tsx new file mode 100644 index 0000000..3e3e014 --- /dev/null +++ b/src/renderer/components/Code/CodeLoading.tsx @@ -0,0 +1,54 @@ +import { Flex, Skeleton, VStack } from '@chakra-ui/react'; + +export default function CodeLoading() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/renderer/components/SearchResults/CodeViewToggler.tsx b/src/renderer/components/Code/CodeViewToggler.tsx similarity index 95% rename from src/renderer/components/SearchResults/CodeViewToggler.tsx rename to src/renderer/components/Code/CodeViewToggler.tsx index 8da342d..fb77805 100644 --- a/src/renderer/components/SearchResults/CodeViewToggler.tsx +++ b/src/renderer/components/Code/CodeViewToggler.tsx @@ -47,8 +47,13 @@ const RadioButton = ({ color="neutral.100" bg="neutral.0" tabIndex={0} + pos="relative" + _active={{ + zIndex: 2, + }} _checked={{ bg: 'neutral.50', + zIndex: 1, }} _focus={{ boxShadow: 'outline', diff --git a/src/renderer/components/CodeCount/CodeCount.tsx b/src/renderer/components/CodeCount/CodeCount.tsx new file mode 100644 index 0000000..dca4ee9 --- /dev/null +++ b/src/renderer/components/CodeCount/CodeCount.tsx @@ -0,0 +1,17 @@ +import { Flex, Text } from '@chakra-ui/react'; +import { CodeIcon } from '@codiga/components'; + +type CodeCountProps = { + count?: number; +}; + +export default function CodeCount({ count = 0 }: CodeCountProps) { + return ( + + + + {count} + + + ); +} diff --git a/src/renderer/components/CodeCount/index.tsx b/src/renderer/components/CodeCount/index.tsx new file mode 100644 index 0000000..04e688d --- /dev/null +++ b/src/renderer/components/CodeCount/index.tsx @@ -0,0 +1 @@ +export { default } from './CodeCount'; diff --git a/src/renderer/components/CookbookTable/CookbookTable.tsx b/src/renderer/components/CookbookTable/CookbookTable.tsx index fc9f5b0..88d32ba 100644 --- a/src/renderer/components/CookbookTable/CookbookTable.tsx +++ b/src/renderer/components/CookbookTable/CookbookTable.tsx @@ -9,22 +9,19 @@ import { Td as ChakraTd, TableCellProps, Link, + LinkBox, + LinkOverlay, } from '@chakra-ui/react'; -import { - LockIcon, - Avatar, - UsersIcon, - CodeIcon, - Logos, -} from '@codiga/codiga-components'; +import { UsersIcon, Logos } from '@codiga/components'; +import { Link as RouterLink } from 'react-router-dom'; -import { getCookbookUrl, getGroupUrl } from '../../utils/urlUtils'; -import { getAvatarUrl } from '../../utils/userUtils'; +import { getGroupUrl } from '../../utils/urlUtils'; import { AssistantCookbook } from '../../types/assistantTypes'; -import { PageTypes } from '../../types/pageTypes'; import FavoriteCookbook from '../Favorite/FavoriteCookbook'; -import UserLink from '../UserLink'; -import VotesCurrent from '../VotesCurrent'; +import PrivacyAndVotes from '../PrivacyAndVotes'; +import FormattedDate from '../FormattedDate'; +import AvatarAndName from '../AvatarAndName'; +import CodeCount from '../CodeCount'; const Td = (props: TableCellProps) => ( ( type CookbookTableProps = { cookbooks: AssistantCookbook[]; - page: PageTypes; }; -export default function CookbookTable({ cookbooks, page }: CookbookTableProps) { +export default function CookbookTable({ cookbooks }: CookbookTableProps) { return ( - - + + {cookbooks.map((cookbook) => { return ( - - {cookbook.name} - + + {cookbook.groups && cookbook.groups.length > 0 && ( )} + + + + - + ); })} diff --git a/src/renderer/components/CookbookTable/CookbookTableEmpty.tsx b/src/renderer/components/CookbookTable/CookbookTableEmpty.tsx index 6e51b68..087fce8 100644 --- a/src/renderer/components/CookbookTable/CookbookTableEmpty.tsx +++ b/src/renderer/components/CookbookTable/CookbookTableEmpty.tsx @@ -1,5 +1,5 @@ import { Link } from '@chakra-ui/react'; -import { EmptyState } from '@codiga/codiga-components'; +import { EmptyState } from '@codiga/components'; import { APP_URL } from '../../lib/config'; export default function CookbookTableEmpty() { diff --git a/src/renderer/components/CookbookTable/CookbookTableEmptyFiltered.tsx b/src/renderer/components/CookbookTable/CookbookTableEmptyFiltered.tsx index e716163..c97c383 100644 --- a/src/renderer/components/CookbookTable/CookbookTableEmptyFiltered.tsx +++ b/src/renderer/components/CookbookTable/CookbookTableEmptyFiltered.tsx @@ -1,9 +1,9 @@ import { Button, Flex, Link } from '@chakra-ui/react'; -import { EmptyState } from '@codiga/codiga-components'; +import { EmptyState } from '@codiga/components'; import { APP_URL } from '../../lib/config'; import { useFilters } from '../FiltersContext'; -export default function CookbookTableEmptyFiltereed() { +export default function CookbookTableEmptyFiltered() { const { resetAllFilters } = useFilters(); return ( diff --git a/src/renderer/components/CookbookTable/CookbookTableError.tsx b/src/renderer/components/CookbookTable/CookbookTableError.tsx index 9e5cec8..e5a6e73 100644 --- a/src/renderer/components/CookbookTable/CookbookTableError.tsx +++ b/src/renderer/components/CookbookTable/CookbookTableError.tsx @@ -1,5 +1,5 @@ import { Link } from '@chakra-ui/react'; -import { EmptyState } from '@codiga/codiga-components'; +import { EmptyState } from '@codiga/components'; import { APP_URL } from '../../lib/config'; export default function CookbookTableError() { diff --git a/src/renderer/components/CookbookTable/CookbookTableLoading.tsx b/src/renderer/components/CookbookTable/CookbookTableLoading.tsx index 881e517..fac7879 100644 --- a/src/renderer/components/CookbookTable/CookbookTableLoading.tsx +++ b/src/renderer/components/CookbookTable/CookbookTableLoading.tsx @@ -15,15 +15,22 @@ const Td = (props: TableCellProps) => ( export default function CookbookTableLoading() { return ( - - + +
@@ -100,6 +99,7 @@ export default function CookbookTable({ cookbooks, page }: CookbookTableProps) { href={`${getGroupUrl( cookbook.groups[0].id! )}/cookbooks`} + _focus={{ boxShadow: 'none' }} > {cookbook.groups[0].name} @@ -107,56 +107,31 @@ export default function CookbookTable({ cookbooks, page }: CookbookTableProps) { - - - - - - + - - - - {cookbook.isPublic ? 'Public' : 'Private'} - - - + - - - {new Date(cookbook.creationTimestampMs!).toDateString()} - - + - - - - {cookbook?.recipesCount} - - +
{[1, 2, 3, 4, 5, 6, 7].map((num, i) => ( - diff --git a/src/renderer/components/PrivacyAndVotes/PrivacyAndVotes.tsx b/src/renderer/components/PrivacyAndVotes/PrivacyAndVotes.tsx new file mode 100644 index 0000000..a97f218 --- /dev/null +++ b/src/renderer/components/PrivacyAndVotes/PrivacyAndVotes.tsx @@ -0,0 +1,31 @@ +import { Flex, Text } from '@chakra-ui/react'; +import { LockIcon } from '@codiga/components'; +import VotesCurrent from '../VotesCurrent'; + +type PrivacyAndNotesProps = { + isPublic?: boolean; + upvotes?: number; + downvotes?: number; +}; + +export default function PrivacyAndVotes({ + isPublic = true, + upvotes = 0, + downvotes = 0, +}: PrivacyAndNotesProps) { + return ( + + + + {isPublic ? 'Public' : 'Private'} + + + + ); +} diff --git a/src/renderer/components/PrivacyAndVotes/index.tsx b/src/renderer/components/PrivacyAndVotes/index.tsx new file mode 100644 index 0000000..be5fa1a --- /dev/null +++ b/src/renderer/components/PrivacyAndVotes/index.tsx @@ -0,0 +1 @@ +export { default } from './PrivacyAndVotes'; diff --git a/src/renderer/components/SearchResults/SearchResults.tsx b/src/renderer/components/SearchResults/SearchResults.tsx deleted file mode 100644 index ed64747..0000000 --- a/src/renderer/components/SearchResults/SearchResults.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { Box, Flex } from '@chakra-ui/react'; -import { useState } from 'react'; -import { - AssistantRecipeWithStats, - RecipeSummary, -} from 'renderer/types/assistantTypes'; -import SearchResultsCode from './SearchResultsCode'; -import SearchResultsList from './SearchResultsList'; -import SearchResultsListItem from './SearchResultsListItem'; - -type SearchResultsProps = { - results: AssistantRecipeWithStats[]; -}; - -export default function SearchResults({ results }: SearchResultsProps) { - const [snippetInFocus, setSnippetInFocus] = useState(results[0] || {}); - - const changeSnippetInFocus = (recipe: RecipeSummary) => { - setSnippetInFocus(recipe); - }; - - return ( - - - {results.map((result) => ( - - ))} - - - - {results[0] ? : null} - - - - ); -} diff --git a/src/renderer/components/SearchResults/SearchResultsCode.tsx b/src/renderer/components/SearchResults/SearchResultsCode.tsx deleted file mode 100644 index fcfaea0..0000000 --- a/src/renderer/components/SearchResults/SearchResultsCode.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import { - Flex, - LinkBox, - IconButton, - useClipboard, - useColorModeValue, - useToken, - Tooltip, - Text, - Link, -} from '@chakra-ui/react'; -import { - BubbleIcon, - Code, - CodeContent, - CopyIcon, - useToast, -} from '@codiga/codiga-components'; -import { useEffect } from 'react'; -import useCodeView, { CodeViewsType } from '../../hooks/useCodeView'; -import { APP_URL } from '../../lib/config'; -import { AssistantRecipeWithStats } from '../../types/assistantTypes'; -import { decodeIndent } from '../../utils/codeUtils'; -import CodeViewToggler from './CodeViewToggler'; - -type SearchResultsCodeProps = { - recipe: AssistantRecipeWithStats; -}; - -export default function SearchResultsCode({ recipe }: SearchResultsCodeProps) { - const toast = useToast(); - const [codeView, setCodeView] = useCodeView('preview'); - - const neutral100 = useToken('colors', 'neutral.100'); - const bg = useColorModeValue('white', neutral100); - - const code = - codeView === 'preview' - ? decodeIndent(recipe?.presentableFormat) - : decodeIndent(recipe?.code); - const imports = recipe?.imports?.join('\n'); - const codeForCopy = imports ? `${imports}\n${code}` : code; - - const { hasCopied, onCopy } = useClipboard(codeForCopy); - - useEffect(() => { - if (hasCopied) { - toast({ status: 'success', description: 'Snippet copied' }); - } - }, [hasCopied, toast]); - - const commentsCount = Number(recipe.commentsCount); - const lines = code.split('\n').length; - const lineMaxDigits = lines.toString().length; - const minWidth = lineMaxDigits < 3 ? '2.7em' : `${lineMaxDigits}.25em`; - - return ( - - span:first-child > .linenumber:first-child': - { - paddingTop: '0.5em !important', - }, - 'code[class*="language-"] .linenumber': { - border: '0 !important', - background: 'transparent !important', - fontStyle: 'normal !important', - }, - }} - > - - setCodeView(value as CodeViewsType), - }} - /> - - - } - onClick={onCopy} - aria-label="Copy Snippet" - /> - - - - - - - {commentsCount} - - - } - aria-label="Comment on Snippet" - /> - - - - - {code} - - - - ); -} diff --git a/src/renderer/components/SearchResults/SearchResultsCodeLoading.tsx b/src/renderer/components/SearchResults/SearchResultsCodeLoading.tsx deleted file mode 100644 index bff221c..0000000 --- a/src/renderer/components/SearchResults/SearchResultsCodeLoading.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Skeleton, VStack } from '@chakra-ui/react'; - -export default function SearchResultsCodeLoading() { - return ( - - - - - - - - - - - - - - - - - - ); -} diff --git a/src/renderer/components/SearchResults/SearchResultsEmpty.tsx b/src/renderer/components/SearchResults/SearchResultsEmpty.tsx index ae83941..673caff 100644 --- a/src/renderer/components/SearchResults/SearchResultsEmpty.tsx +++ b/src/renderer/components/SearchResults/SearchResultsEmpty.tsx @@ -1,5 +1,5 @@ import { Button, Flex, Link } from '@chakra-ui/react'; -import { EmptyState } from '@codiga/codiga-components'; +import { EmptyState } from '@codiga/components'; import { APP_URL } from '../../lib/config'; import { useFilters } from '../FiltersContext'; diff --git a/src/renderer/components/SearchResults/SearchResultsError.tsx b/src/renderer/components/SearchResults/SearchResultsError.tsx index df38f00..b9832e9 100644 --- a/src/renderer/components/SearchResults/SearchResultsError.tsx +++ b/src/renderer/components/SearchResults/SearchResultsError.tsx @@ -1,5 +1,5 @@ import { Link } from '@chakra-ui/react'; -import { EmptyState } from '@codiga/codiga-components'; +import { EmptyState } from '@codiga/components'; import { APP_URL } from '../../lib/config'; export default function SearchResultsError() { diff --git a/src/renderer/components/SearchResults/SearchResultsLoading.tsx b/src/renderer/components/SearchResults/SearchResultsLoading.tsx deleted file mode 100644 index e264224..0000000 --- a/src/renderer/components/SearchResults/SearchResultsLoading.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Flex } from '@chakra-ui/react'; -import SearchResultsCodeLoading from './SearchResultsCodeLoading'; -import SearchResultsList from './SearchResultsList'; -import SearchResultsListItemLoading from './SearchResultsListItemLoading'; - -export default function SearchResultsLoading() { - return ( - - - {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map((num) => ( - - ))} - - - - - - ); -} diff --git a/src/renderer/components/SearchResults/index.tsx b/src/renderer/components/SearchResults/index.tsx deleted file mode 100644 index 43ac09d..0000000 --- a/src/renderer/components/SearchResults/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SearchResults'; diff --git a/src/renderer/components/SnippetResults/SnippetResults.tsx b/src/renderer/components/SnippetResults/SnippetResults.tsx new file mode 100644 index 0000000..69f5ed1 --- /dev/null +++ b/src/renderer/components/SnippetResults/SnippetResults.tsx @@ -0,0 +1,35 @@ +import { Flex } from '@chakra-ui/react'; +import useUrlQuery from '../../hooks/useUrlQuery'; +import { AssistantRecipeWithStats } from '../../types/assistantTypes'; +import Code from '../Code/Code'; +import SnippetResultsList from './SnippetResultsList'; +import SnippetResultsListItem from './SnippetResultsListItem'; + +type SnippetResultsProps = { + results: AssistantRecipeWithStats[]; +}; + +export default function SnippetResults({ results }: SnippetResultsProps) { + const query = useUrlQuery(); + const currentSnippetId = query.get('currentSnippetId'); + + const currentSnippet = currentSnippetId + ? results.find((recipe) => String(recipe.id) === currentSnippetId) + : results[0] || {}; + + return ( + + + {results.map((result) => ( + + ))} + + + {currentSnippet?.id ? : null} + + ); +} diff --git a/src/renderer/components/SearchResults/SearchResultsList.tsx b/src/renderer/components/SnippetResults/SnippetResultsList.tsx similarity index 56% rename from src/renderer/components/SearchResults/SearchResultsList.tsx rename to src/renderer/components/SnippetResults/SnippetResultsList.tsx index dc1eb71..dd2a1db 100644 --- a/src/renderer/components/SearchResults/SearchResultsList.tsx +++ b/src/renderer/components/SnippetResults/SnippetResultsList.tsx @@ -1,13 +1,13 @@ import { ReactNode } from 'react'; import { Flex } from '@chakra-ui/react'; -type SearchResultsListProps = { +type SnippetResultsListProps = { children: ReactNode; }; -export default function SearchResultsList({ +export default function SnippetResultsList({ children, -}: SearchResultsListProps) { +}: SnippetResultsListProps) { return ( {children} diff --git a/src/renderer/components/SearchResults/SearchResultsListItem.tsx b/src/renderer/components/SnippetResults/SnippetResultsListItem.tsx similarity index 64% rename from src/renderer/components/SearchResults/SearchResultsListItem.tsx rename to src/renderer/components/SnippetResults/SnippetResultsListItem.tsx index 5f5b81b..58c0e11 100644 --- a/src/renderer/components/SearchResults/SearchResultsListItem.tsx +++ b/src/renderer/components/SnippetResults/SnippetResultsListItem.tsx @@ -1,30 +1,34 @@ -import { Flex, Text } from '@chakra-ui/react'; -import { ChartBarsIcon, DotIcon, Logo, Tags } from '@codiga/codiga-components'; -import { - AssistantRecipeWithStats, - RecipeSummary, -} from '../../types/assistantTypes'; +import { Flex, LinkBox, LinkOverlay, Text } from '@chakra-ui/react'; +import { ChartBarsIcon, DotIcon, Logo, Tags } from '@codiga/components'; +import { Link as RouterLink } from 'react-router-dom'; + +import { AssistantRecipeWithStats } from '../../types/assistantTypes'; import FavoriteSnippet from '../Favorite/FavoriteSnippet'; -import Votes from './Votes'; +import Votes from '../Votes'; -type SearchResultsListItemProps = { +type SnippetResultsListItemProps = { recipe: AssistantRecipeWithStats; - changeSnippetInFocus: (recipe: RecipeSummary) => void; + isCurrentSnippet: boolean; }; -export default function SearchResultsListItem({ +export default function SnippetResultsListItem({ recipe, - changeSnippetInFocus, -}: SearchResultsListItemProps) { + isCurrentSnippet, +}: SnippetResultsListItemProps) { return ( - changeSnippetInFocus(recipe)} - cursor="pointer" - tabIndex={0} > - {recipe.name} + + {recipe.name} + 0 && ( )} - + ); } diff --git a/src/renderer/components/SearchResults/SearchResultsListItemLoading.tsx b/src/renderer/components/SnippetResults/SnippetResultsListItemLoading.tsx similarity index 87% rename from src/renderer/components/SearchResults/SearchResultsListItemLoading.tsx rename to src/renderer/components/SnippetResults/SnippetResultsListItemLoading.tsx index 6eb5fef..2ceda5c 100644 --- a/src/renderer/components/SearchResults/SearchResultsListItemLoading.tsx +++ b/src/renderer/components/SnippetResults/SnippetResultsListItemLoading.tsx @@ -1,13 +1,13 @@ import { Flex, Skeleton, SkeletonCircle } from '@chakra-ui/react'; -import { DotIcon } from '@codiga/codiga-components'; +import { DotIcon } from '@codiga/components'; -export default function SearchResultsListItemLoading() { +export default function SnippetResultsListItemLoading() { return ( + + {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map((num) => ( + + ))} + + + + + ); +} diff --git a/src/renderer/components/SnippetTable/SnippetTable.tsx b/src/renderer/components/SnippetTable/SnippetTable.tsx index 1e9f5f8..97d5dab 100644 --- a/src/renderer/components/SnippetTable/SnippetTable.tsx +++ b/src/renderer/components/SnippetTable/SnippetTable.tsx @@ -7,41 +7,50 @@ import { Tr, Tbody, Td as ChakraTd, - Tag, TableCellProps, Link, + LinkBox, + LinkOverlay, } from '@chakra-ui/react'; -import { LockIcon, Logo, Avatar, UsersIcon } from '@codiga/codiga-components'; +import { Logo, UsersIcon, Tags } from '@codiga/components'; +import { Link as RouterLink } from 'react-router-dom'; -import { getAvatarUrl } from '../../utils/userUtils'; -import { getGroupUrl, getSnippetUrl } from '../../utils/urlUtils'; +import { getGroupUrl } from '../../utils/urlUtils'; import { AssistantRecipeWithStats } from '../../types/assistantTypes'; -import { PageTypes } from '../../types/pageTypes'; import FavoriteSnippet from '../Favorite/FavoriteSnippet'; -import UserLink from '../UserLink'; -import VotesCurrent from '../VotesCurrent'; +import PrivacyAndVotes from '../PrivacyAndVotes'; +import FormattedDate from '../FormattedDate/FormattedDate'; +import AvatarAndName from '../AvatarAndName/AvatarAndName'; const Td = (props: TableCellProps) => ( ); type SnippetTableProps = { - page: PageTypes; recipes: AssistantRecipeWithStats[]; }; -export default function SnippetTable({ page, recipes }: SnippetTableProps) { +export default function SnippetTable({ recipes }: SnippetTableProps) { return ( - - + +
{recipes.map((recipe) => { return ( - + + + {recipe.groups && recipe.groups.length > 0 && ( )} + - + + + - + ); })} diff --git a/src/renderer/components/SnippetTable/SnippetTableEmpty.tsx b/src/renderer/components/SnippetTable/SnippetTableEmpty.tsx index 6297ce7..e54799b 100644 --- a/src/renderer/components/SnippetTable/SnippetTableEmpty.tsx +++ b/src/renderer/components/SnippetTable/SnippetTableEmpty.tsx @@ -1,5 +1,5 @@ import { Link } from '@chakra-ui/react'; -import { EmptyState } from '@codiga/codiga-components'; +import { EmptyState } from '@codiga/components'; import { APP_URL } from '../../lib/config'; export default function SnippetTableEmpty() { diff --git a/src/renderer/components/SnippetTable/SnippetTableEmptyFiltered.tsx b/src/renderer/components/SnippetTable/SnippetTableEmptyFiltered.tsx index 2463a0d..1af4768 100644 --- a/src/renderer/components/SnippetTable/SnippetTableEmptyFiltered.tsx +++ b/src/renderer/components/SnippetTable/SnippetTableEmptyFiltered.tsx @@ -1,5 +1,5 @@ import { Button, Flex, Link } from '@chakra-ui/react'; -import { EmptyState } from '@codiga/codiga-components'; +import { EmptyState } from '@codiga/components'; import { APP_URL } from '../../lib/config'; import { useFilters } from '../FiltersContext'; diff --git a/src/renderer/components/SnippetTable/SnippetTableError.tsx b/src/renderer/components/SnippetTable/SnippetTableError.tsx index 77af3f8..1cf5d37 100644 --- a/src/renderer/components/SnippetTable/SnippetTableError.tsx +++ b/src/renderer/components/SnippetTable/SnippetTableError.tsx @@ -1,5 +1,5 @@ import { Link } from '@chakra-ui/react'; -import { EmptyState } from '@codiga/codiga-components'; +import { EmptyState } from '@codiga/components'; import { APP_URL } from '../../lib/config'; export default function SnippetTableError() { diff --git a/src/renderer/components/SnippetTable/SnippetTableLoading.tsx b/src/renderer/components/SnippetTable/SnippetTableLoading.tsx index 19ecb25..4d7b141 100644 --- a/src/renderer/components/SnippetTable/SnippetTableLoading.tsx +++ b/src/renderer/components/SnippetTable/SnippetTableLoading.tsx @@ -15,15 +15,22 @@ const Td = (props: TableCellProps) => ( export default function SnippetTableLoading() { return ( - - + +
+ + + + {recipe.name} + + + + + @@ -68,6 +100,7 @@ export default function SnippetTable({ page, recipes }: SnippetTableProps) { href={`${getGroupUrl( recipe.groups[0].id! )}/snippets`} + _focus={{ boxShadow: 'none' }} > {recipe.groups[0].name} @@ -75,89 +108,33 @@ export default function SnippetTable({ page, recipes }: SnippetTableProps) { - - - - {recipe.name} - - - - - - - - - - - + - - - - {recipe.isPublic ? 'Public' : 'Private'} - - - + - - - {new Date(recipe.creationTimestampMs!).toDateString()} - - + - - {recipe.tags?.slice(0, 1).map((tag) => ( - - {tag} - - ))} - {(recipe.tags || []).length - 1 > 0 ? ( - +{(recipe.tags || []).length - 1} - ) : null} - + {recipe?.tags && recipe?.tags.length > 0 && ( + + )}
{[1, 2, 3, 4, 5, 6, 7].map((num, i) => ( { // CHAKRA'S THEME const { setColorMode } = useColorMode(); // USER'S LOCAL THEME PREFERENCE - // @ts-ignore - const [cacheTheme, cacheStorageTheme, hydrateValue] = useLocalStorage( + const [_, cacheStorageTheme, hydrateValue] = useLocalStorage( CODIGA_THEME, Theme.THEME_LIGHT ) as [ThemeType, CacheStorageThemeType, () => string]; diff --git a/src/renderer/components/UserLink/UserLink.tsx b/src/renderer/components/UserLink/UserLink.tsx index b3a40ed..34f2528 100644 --- a/src/renderer/components/UserLink/UserLink.tsx +++ b/src/renderer/components/UserLink/UserLink.tsx @@ -1,5 +1,5 @@ import { Link } from '@chakra-ui/react'; -import { ExternalLinkIcon } from '@codiga/codiga-components'; +import { ExternalLinkIcon } from '@codiga/components'; import { PublicUser } from '../../types/userTypes'; import { getUserUrl } from '../../utils/urlUtils'; @@ -12,7 +12,12 @@ export default function UserLink({ owner = {} }: UserLinkProps) { if (hasSlug && slug) { return ( - + {displayName || 'Anonymous'} diff --git a/src/renderer/components/ViewCookbookSnippets/ViewCookbookSnippetsEmpty.tsx b/src/renderer/components/ViewCookbookSnippets/ViewCookbookSnippetsEmpty.tsx new file mode 100644 index 0000000..c97cc46 --- /dev/null +++ b/src/renderer/components/ViewCookbookSnippets/ViewCookbookSnippetsEmpty.tsx @@ -0,0 +1,28 @@ +import { Flex, Link } from '@chakra-ui/react'; +import { EmptyState } from '@codiga/components'; +import { useParams } from 'react-router-dom'; +import { APP_URL } from '../../lib/config'; + +export default function ViewCookbookSnippetsEmpty() { + const params = useParams(); + + return ( + + + + Add Snippet to Cookbook + + + + ); +} diff --git a/src/renderer/components/ViewCookbookSnippets/ViewCookbookSnippetsEmptyFiltered.tsx b/src/renderer/components/ViewCookbookSnippets/ViewCookbookSnippetsEmptyFiltered.tsx new file mode 100644 index 0000000..2679d12 --- /dev/null +++ b/src/renderer/components/ViewCookbookSnippets/ViewCookbookSnippetsEmptyFiltered.tsx @@ -0,0 +1,38 @@ +import { Button, Flex, Link } from '@chakra-ui/react'; +import { EmptyState } from '@codiga/components'; +import { useParams } from 'react-router-dom'; +import { APP_URL } from '../../lib/config'; + +type ViewCookbookSnippetsEmptyFilteredProps = { + clearSearch: () => void; +}; + +export default function ViewCookbookSnippetsEmptyFiltered({ + clearSearch, +}: ViewCookbookSnippetsEmptyFilteredProps) { + const params = useParams(); + + return ( + + + + + + Add Snippet to Cookbook + + + + ); +} diff --git a/src/renderer/components/ViewCookbookSnippets/ViewCookbookSnippetsError.tsx b/src/renderer/components/ViewCookbookSnippets/ViewCookbookSnippetsError.tsx new file mode 100644 index 0000000..7b78a16 --- /dev/null +++ b/src/renderer/components/ViewCookbookSnippets/ViewCookbookSnippetsError.tsx @@ -0,0 +1,29 @@ +import { Flex, Link } from '@chakra-ui/react'; +import { EmptyState } from '@codiga/components'; +import { Link as RouterLink } from 'react-router-dom'; +import { APP_URL } from '../../lib/config'; + +export default function ViewCookbookSnippetsError() { + return ( + + + + Go Home + + + Contact Support + + + + ); +} diff --git a/src/renderer/components/ViewCookbookSnippets/ViewCookbookSnippetsLoading.tsx b/src/renderer/components/ViewCookbookSnippets/ViewCookbookSnippetsLoading.tsx new file mode 100644 index 0000000..c55be64 --- /dev/null +++ b/src/renderer/components/ViewCookbookSnippets/ViewCookbookSnippetsLoading.tsx @@ -0,0 +1,21 @@ +import { HStack, Skeleton } from '@chakra-ui/react'; + +export default function ViewCookbookSnippetsLoading() { + return ( + + + + + + + + + ); +} diff --git a/src/renderer/components/ViewSnippet/ViewSnippetError.tsx b/src/renderer/components/ViewSnippet/ViewSnippetError.tsx new file mode 100644 index 0000000..90f2a03 --- /dev/null +++ b/src/renderer/components/ViewSnippet/ViewSnippetError.tsx @@ -0,0 +1,25 @@ +import { Flex, Link } from '@chakra-ui/react'; +import { EmptyState } from '@codiga/components'; +import { APP_URL } from '../../lib/config'; + +export default function ViewSnippetError() { + return ( + + + + Contact Support + + + + ); +} diff --git a/src/renderer/components/ViewSnippet/ViewSnippetLoading.tsx b/src/renderer/components/ViewSnippet/ViewSnippetLoading.tsx new file mode 100644 index 0000000..d1f66d6 --- /dev/null +++ b/src/renderer/components/ViewSnippet/ViewSnippetLoading.tsx @@ -0,0 +1,26 @@ +import { Box, HStack, Skeleton } from '@chakra-ui/react'; +import CodeLoading from '../Code/CodeLoading'; + +export default function ViewSnippetLoading() { + return ( + + + + + + + + + + + + + ); +} diff --git a/src/renderer/components/SearchResults/Votes.tsx b/src/renderer/components/Votes/Votes.tsx similarity index 84% rename from src/renderer/components/SearchResults/Votes.tsx rename to src/renderer/components/Votes/Votes.tsx index 82d2a13..3e35fa7 100644 --- a/src/renderer/components/SearchResults/Votes.tsx +++ b/src/renderer/components/Votes/Votes.tsx @@ -1,6 +1,9 @@ +import { useRef } from 'react'; +import { useInView } from 'framer-motion'; import { useMutation, useQuery } from '@apollo/client'; import { Flex, FlexProps, IconButton, Text, Tooltip } from '@chakra-ui/react'; -import { DownVoteIcon, UpVoteIcon, useToast } from '@codiga/codiga-components'; +import { DownVoteIcon, UpVoteIcon, useToast } from '@codiga/components'; + import { useUser } from '../UserContext'; import { AddVoteMutationVariables, @@ -18,27 +21,30 @@ type VotesProps = FlexProps & { downvotes: number; }; -const Votes = ({ +export default function Votes({ upvotes, downvotes, entityId, entityType = 'Recipe', ...props -}: VotesProps) => { +}: VotesProps) { const toast = useToast(); const { id: userId } = useUser(); + const ref = useRef(null); + const isInView = useInView(ref); + const { data, refetch } = useQuery(GET_RECIPE_VOTES_QUERY, { - skip: !userId, + skip: !userId || !isInView, variables: { recipeId: entityId, }, }); - const isUpVoted = Boolean(data?.votesData.isUpVoted); - const isDownVoted = Boolean(data?.votesData.isDownVoted); - const upVoteCount = Number(data?.votesData.upvotes); - const downVoteCount = Number(data?.votesData.downvotes); + const isUpVoted = Boolean(data?.votesData?.isUpVoted); + const isDownVoted = Boolean(data?.votesData?.isDownVoted); + const upVoteCount = Number(data?.votesData?.upvotes || upvotes); + const downVoteCount = Number(data?.votesData?.downvotes || downvotes); const voteText = data ? upVoteCount - downVoteCount : upvotes - downvotes; const [addVote] = useMutation(ADD_VOTE); @@ -89,7 +95,7 @@ const Votes = ({ const countColor = isUpVoted || isDownVoted ? 'rose.50' : undefined; return ( - + ); -}; - -export default Votes; +} diff --git a/src/renderer/components/Votes/index.tsx b/src/renderer/components/Votes/index.tsx new file mode 100644 index 0000000..c8d0774 --- /dev/null +++ b/src/renderer/components/Votes/index.tsx @@ -0,0 +1 @@ +export { default } from './Votes'; diff --git a/src/renderer/components/VotesCurrent/VotesCurrent.tsx b/src/renderer/components/VotesCurrent/VotesCurrent.tsx index 792e157..c26493e 100644 --- a/src/renderer/components/VotesCurrent/VotesCurrent.tsx +++ b/src/renderer/components/VotesCurrent/VotesCurrent.tsx @@ -1,5 +1,5 @@ import { Flex, Text } from '@chakra-ui/react'; -import { DownVoteIcon, UpVoteIcon } from '@codiga/codiga-components'; +import { DownVoteIcon, UpVoteIcon } from '@codiga/components'; type VotesCurrentProps = { upvotes?: number; diff --git a/src/renderer/graphql/client.ts b/src/renderer/graphql/client.ts index d28476a..510024d 100644 --- a/src/renderer/graphql/client.ts +++ b/src/renderer/graphql/client.ts @@ -9,7 +9,7 @@ import { setContext } from '@apollo/client/link/context'; import DebounceLink from 'apollo-link-debounce'; import { API_URL, TOKEN } from '../lib/config'; -const DEFAULT_DEBOUNCE_TIMEOUT = 500; +const DEFAULT_DEBOUNCE_TIMEOUT = 300; const Link = ApolloLink.from([ new DebounceLink(DEFAULT_DEBOUNCE_TIMEOUT), diff --git a/src/renderer/graphql/mutations.ts b/src/renderer/graphql/mutations.ts index 8dd2c0c..2d34a21 100644 --- a/src/renderer/graphql/mutations.ts +++ b/src/renderer/graphql/mutations.ts @@ -1,5 +1,5 @@ import { gql } from '@apollo/client'; -import { UserPreferenceKeyType } from 'renderer/types/userTypes'; +import { UserPreferenceKeyType } from '../types/userTypes'; export type RemoveUserPreferenceVariables = { key: UserPreferenceKeyType; diff --git a/src/renderer/graphql/queries.ts b/src/renderer/graphql/queries.ts index 17784d2..d6ba5da 100644 --- a/src/renderer/graphql/queries.ts +++ b/src/renderer/graphql/queries.ts @@ -3,7 +3,7 @@ import { AssistantRecipeWithStats, LanguageEnumeration, LibraryWithAllEnumeration, -} from 'renderer/types/assistantTypes'; +} from '../types/assistantTypes'; export const CHECK_USER = gql` query checkUser { @@ -399,3 +399,118 @@ export const GET_RECIPE_VOTES_QUERY = gql` } } `; + +export const GET_RECIPE = gql` + query getRecipe($recipeId: Long!) { + recipe: assistantRecipe(id: $recipeId) { + id + code + name + tags + uses + imports + upvotes + language + keywords + downvotes + isUpVoted + isDownVoted + description + isSubscribed + commentsCount + averageRating + presentableFormat + creationTimestampMs + owner { + id + slug + displayName + } + cookbook { + id + name + } + dependencyConstraints { + name + } + } + } +`; + +export const GET_COOKBOOK_INFO = gql` + query getCookbookRecipes($cookbookId: Long!) { + cookbook: assistantCookbook(id: $cookbookId) { + id + name + isPublic + isSubscribed + recipesCount + creationTimestampMs + groups { + id + name + } + owner { + id + hasSlug + slug + displayName + } + upvotes + downvotes + languages + } + } +`; + +export const GET_COOKBOOK_RECIPES = gql` + query getCookbookRecipes( + $cookbookId: Long! + $howmany: Long! + $skip: Long! + $name: String + $orderBy: AssistantRecipeQueryOrderBy + $desc: Boolean + ) { + cookbook: assistantCookbook(id: $cookbookId) { + recipes( + howmany: $howmany + skip: $skip + name: $name + orderBy: $orderBy + desc: $desc + ) { + id + name + code + tags + uses + imports + upvotes + language + keywords + downvotes + isUpVoted + isDownVoted + description + isSubscribed + commentsCount + averageRating + presentableFormat + creationTimestampMs + owner { + id + slug + displayName + } + cookbook { + id + name + } + dependencyConstraints { + name + } + } + } + } +`; diff --git a/src/renderer/hooks/useQueryVariables.ts b/src/renderer/hooks/useQueryVariables.ts new file mode 100644 index 0000000..1607c88 --- /dev/null +++ b/src/renderer/hooks/useQueryVariables.ts @@ -0,0 +1,93 @@ +import { useUser } from '../components/UserContext'; +import { + GET_USER_RECIPES_VARIABLES, + GET_USER_SUBSCRIBED_RECIPES_VARIABLES, + GET_SHARED_RECIPES_VARIABLES, + GET_USER_COOKBOOKS_VARIABLES, + GET_USER_SUBSCRIBED_COOKBOOKS_VARIABLES, + GET_SHARED_COOKBOOKS_VARIABLES, +} from '../graphql/variables'; +import { Language } from '../lib/constants'; +import { LanguageEnumeration } from '../types/assistantTypes'; +import { useFilters } from '../components/FiltersContext'; + +type QueryTypes = + | 'home' + | 'my-snippets' + | 'favorite-snippets' + | 'team-snippets' + | 'my-cookbooks' + | 'favorite-cookbooks' + | 'team-cookbooks'; + +export default function useQueryVariables(query: QueryTypes) { + const filters = useFilters(); + const { id: userId } = useUser(); + + switch (query) { + case 'home': + return { + howmany: 100, + skip: 0, + languages: + filters.language && filters.language !== Language.ALL_LANGUAGES + ? ([filters.language] as LanguageEnumeration[]) + : null, + dependencies: filters.library ? [filters.library] : null, + term: filters.searchTerm || null, + tags: filters.tags ? [filters.tags] : null, + onlyPrivate: filters.privacy === 'private' && !!userId ? true : null, + onlyPublic: filters.privacy === 'public' && !!userId ? true : null, + onlySubscribed: userId ? filters.isSubscribed || null : null, + }; + + case 'my-snippets': + return { + ...GET_USER_RECIPES_VARIABLES, + name: filters.searchTerm || null, + language: + filters.language && filters.language !== Language.ALL_LANGUAGES + ? filters.language + : null, + tag: filters.tags || null, + }; + + case 'favorite-snippets': + return { + ...GET_USER_SUBSCRIBED_RECIPES_VARIABLES, + name: filters.searchTerm || null, + }; + + case 'team-snippets': + return { + ...GET_SHARED_RECIPES_VARIABLES, + name: filters.searchTerm || null, + languages: + filters.language && filters.language !== Language.ALL_LANGUAGES + ? [filters.language] + : null, + tag: filters.tags || null, + }; + + case 'my-cookbooks': + return { + ...GET_USER_COOKBOOKS_VARIABLES, + name: filters.searchTerm || null, + }; + + case 'favorite-cookbooks': + return { + ...GET_USER_SUBSCRIBED_COOKBOOKS_VARIABLES, + name: filters.searchTerm || null, + }; + + case 'team-cookbooks': + return { + ...GET_SHARED_COOKBOOKS_VARIABLES, + name: filters.searchTerm || null, + }; + + default: + return {}; + } +} diff --git a/src/renderer/hooks/useUrlQuery.ts b/src/renderer/hooks/useUrlQuery.ts new file mode 100644 index 0000000..b498826 --- /dev/null +++ b/src/renderer/hooks/useUrlQuery.ts @@ -0,0 +1,8 @@ +import { useMemo } from 'react'; +import { useLocation } from 'react-router-dom'; + +export default function useUrlQuery() { + const { search } = useLocation(); + + return useMemo(() => new URLSearchParams(search), [search]); +} diff --git a/src/renderer/lib/constants.ts b/src/renderer/lib/constants.ts index 669599d..2792938 100644 --- a/src/renderer/lib/constants.ts +++ b/src/renderer/lib/constants.ts @@ -1,4 +1,4 @@ -import { RecipeVariableType } from 'renderer/types/assistantTypes'; +import { RecipeVariableType } from '../types/assistantTypes'; export enum Language { LANGUAGE_UNKNOWN = 'Unknown', @@ -456,3 +456,4 @@ export const SSO_PROVIDERS_LOWER = SSO_PROVIDERS.map((val) => ) as Lowercase[]; export const REVALIDATE_USER_PAGE_IN_SECONDS = 60; +export const PAGE_QUERY_POLL_INTERVAL_IN_MS = 10000; diff --git a/src/renderer/pages/FavoriteCookbooks.tsx b/src/renderer/pages/FavoriteCookbooks.tsx index 0654d94..155e495 100644 --- a/src/renderer/pages/FavoriteCookbooks.tsx +++ b/src/renderer/pages/FavoriteCookbooks.tsx @@ -1,25 +1,24 @@ import { useQuery } from '@apollo/client'; import { GET_USER_SUBSCRIBED_COOKBOOKS } from '../graphql/queries'; -import { GET_USER_SUBSCRIBED_COOKBOOKS_VARIABLES } from '../graphql/variables'; import { AssistantCookbook } from '../types/assistantTypes'; import CookbookTableLoading from '../components/CookbookTable/CookbookTableLoading'; import CookbookTableError from '../components/CookbookTable/CookbookTableError'; import CookbookTableEmpty from '../components/CookbookTable/CookbookTableEmpty'; -import CookbookTableEmptyFiltereed from '../components/CookbookTable/CookbookTableEmptyFiltered'; +import CookbookTableEmptyFiltered from '../components/CookbookTable/CookbookTableEmptyFiltered'; import CookbookTable from '../components/CookbookTable/CookbookTable'; import { useFilters } from '../components/FiltersContext'; -import filterBy from '../components/Filters/filterBy'; +import useQueryVariables from '../hooks/useQueryVariables'; +import { PAGE_QUERY_POLL_INTERVAL_IN_MS } from '../lib/constants'; export default function FavoriteCookbooks() { const filters = useFilters(); + const variables = useQueryVariables('favorite-cookbooks'); const { data, loading, error } = useQuery<{ user: { cookbooks: AssistantCookbook[] }; }>(GET_USER_SUBSCRIBED_COOKBOOKS, { - variables: { - ...GET_USER_SUBSCRIBED_COOKBOOKS_VARIABLES, - name: filters.searchTerm, - }, + variables, + pollInterval: PAGE_QUERY_POLL_INTERVAL_IN_MS, context: { debounceKey: 'favorite-cookbooks', }, @@ -27,15 +26,6 @@ export default function FavoriteCookbooks() { const userCookbooks = data?.user?.cookbooks || []; - // check the recipe against the search filters - const filteredCookbooks = userCookbooks.filter((cookbook) => { - if (!filterBy.name(filters, cookbook.name)) return false; - // if (!filterBy.language(filters, cookbook.language)) return false; - if (!filterBy.privacy(filters, cookbook.isPublic)) return false; - if (!filterBy.isSubscribed(filters, cookbook.isSubscribed)) return false; - return true; - }); - if (error) { return ; } @@ -44,13 +34,13 @@ export default function FavoriteCookbooks() { return ; } - if (userCookbooks.length === 0) { - return ; + if (userCookbooks.length === 0 && !filters.isEmpty) { + return ; } - if (filteredCookbooks.length === 0) { - return ; + if (userCookbooks.length === 0) { + return ; } - return ; + return ; } diff --git a/src/renderer/pages/FavoriteSnippets.tsx b/src/renderer/pages/FavoriteSnippets.tsx index 3588701..a6f674f 100644 --- a/src/renderer/pages/FavoriteSnippets.tsx +++ b/src/renderer/pages/FavoriteSnippets.tsx @@ -1,25 +1,24 @@ import { useQuery } from '@apollo/client'; import { GET_USER_SUBSCRIBED_RECIPES } from '../graphql/queries'; -import { GET_USER_SUBSCRIBED_RECIPES_VARIABLES } from '../graphql/variables'; import { AssistantRecipeWithStats } from '../types/assistantTypes'; import SnippetTableLoading from '../components/SnippetTable/SnippetTableLoading'; import SnippetTableError from '../components/SnippetTable/SnippetTableError'; import SnippetTableEmpty from '../components/SnippetTable/SnippetTableEmpty'; import SnippetTableEmptyFiltered from '../components/SnippetTable/SnippetTableEmptyFiltered'; import SnippetTable from '../components/SnippetTable/SnippetTable'; -import filterBy from '../components/Filters/filterBy'; import { useFilters } from '../components/FiltersContext'; +import useQueryVariables from '../hooks/useQueryVariables'; +import { PAGE_QUERY_POLL_INTERVAL_IN_MS } from '../lib/constants'; export default function MySnippets() { const filters = useFilters(); + const variables = useQueryVariables('favorite-snippets'); const { data, loading, error } = useQuery<{ user: { recipes: AssistantRecipeWithStats[] }; }>(GET_USER_SUBSCRIBED_RECIPES, { - variables: { - ...GET_USER_SUBSCRIBED_RECIPES_VARIABLES, - name: filters.searchTerm, - }, + variables, + pollInterval: PAGE_QUERY_POLL_INTERVAL_IN_MS, context: { debounceKey: 'favorite-snippets', }, @@ -27,17 +26,6 @@ export default function MySnippets() { const userFavoriteRecipes = data?.user?.recipes || []; - // check the recipe against the search filters - const filteredRecipes = userFavoriteRecipes.filter((recipe) => { - if (!filterBy.name(filters, recipe.name)) return false; - if (!filterBy.language(filters, recipe.language)) return false; - if (!filterBy.library(filters, recipe.dependencyConstraints)) return false; - if (!filterBy.tags(filters, recipe.tags)) return false; - if (!filterBy.privacy(filters, recipe.isPublic)) return false; - if (!filterBy.isSubscribed(filters, recipe.isSubscribed)) return false; - return true; - }); - if (error) { return ; } @@ -46,13 +34,13 @@ export default function MySnippets() { return ; } - if (userFavoriteRecipes.length === 0) { - return ; + if (userFavoriteRecipes.length === 0 && !filters.isEmpty) { + return ; } - if (filteredRecipes.length === 0) { - return ; + if (userFavoriteRecipes.length === 0) { + return ; } - return ; + return ; } diff --git a/src/renderer/pages/Home.tsx b/src/renderer/pages/Home.tsx index 06f4aa1..b376c45 100644 --- a/src/renderer/pages/Home.tsx +++ b/src/renderer/pages/Home.tsx @@ -1,37 +1,25 @@ import { useQuery } from '@apollo/client'; -import { useFilters } from '../components/FiltersContext'; -import SearchResults from '../components/SearchResults'; +import useQueryVariables from '../hooks/useQueryVariables'; import SearchResultsEmpty from '../components/SearchResults/SearchResultsEmpty'; import SearchResultsError from '../components/SearchResults/SearchResultsError'; -import SearchResultsLoading from '../components/SearchResults/SearchResultsLoading'; +import SnippetResults from '../components/SnippetResults/SnippetResults'; +import SnippetResultsLoading from '../components/SnippetResults/SnippetResultsLoading'; import { GetRecipesSemanticallyData, GetRecipesSemanticallyVariables, GET_RECIPES_SEMANTICALLY, } from '../graphql/queries'; -import { Language } from '../lib/constants'; +import { PAGE_QUERY_POLL_INTERVAL_IN_MS } from '../lib/constants'; export default function Home() { - const filters = useFilters(); + const variables = useQueryVariables('home'); const { data, loading, error } = useQuery< GetRecipesSemanticallyData, GetRecipesSemanticallyVariables >(GET_RECIPES_SEMANTICALLY, { - variables: { - howmany: 100, - skip: 0, - languages: - filters.language && filters.language !== Language.ALL_LANGUAGES - ? [filters.language] - : null, - dependencies: filters.library ? [filters.library] : null, - term: filters.searchTerm || null, - tags: filters.tags ? [filters.tags] : null, - onlyPrivate: filters.privacy === 'private' ? true : null, - onlyPublic: filters.privacy === 'public' ? true : null, - onlySubscribed: filters.isSubscribed || null, - }, + variables: variables as GetRecipesSemanticallyVariables, + pollInterval: PAGE_QUERY_POLL_INTERVAL_IN_MS, context: { debounceKey: 'search', }, @@ -44,12 +32,12 @@ export default function Home() { } if (loading) { - return ; + return ; } if (results.length === 0) { return ; } - return ; + return ; } diff --git a/src/renderer/pages/MyCookbooks.tsx b/src/renderer/pages/MyCookbooks.tsx index cdd99d0..75f5ddc 100644 --- a/src/renderer/pages/MyCookbooks.tsx +++ b/src/renderer/pages/MyCookbooks.tsx @@ -1,25 +1,24 @@ import { useQuery } from '@apollo/client'; -import { GET_USER_COOKBOOKS_VARIABLES } from '../graphql/variables'; import { GET_USER_COOKBOOKS } from '../graphql/queries'; import { AssistantCookbook } from '../types/assistantTypes'; import CookbookTableLoading from '../components/CookbookTable/CookbookTableLoading'; import CookbookTableError from '../components/CookbookTable/CookbookTableError'; import CookbookTableEmpty from '../components/CookbookTable/CookbookTableEmpty'; -import CookbookTableEmptyFiltereed from '../components/CookbookTable/CookbookTableEmptyFiltered'; +import CookbookTableEmptyFiltered from '../components/CookbookTable/CookbookTableEmptyFiltered'; import CookbookTable from '../components/CookbookTable/CookbookTable'; import { useFilters } from '../components/FiltersContext'; -import filterBy from '../components/Filters/filterBy'; +import useQueryVariables from '../hooks/useQueryVariables'; +import { PAGE_QUERY_POLL_INTERVAL_IN_MS } from '../lib/constants'; export default function MyCookbooks() { const filters = useFilters(); + const variables = useQueryVariables('my-cookbooks'); const { data, loading, error } = useQuery<{ user: { cookbooks: AssistantCookbook[] }; }>(GET_USER_COOKBOOKS, { - variables: { - ...GET_USER_COOKBOOKS_VARIABLES, - name: filters.searchTerm, - }, + variables, + pollInterval: PAGE_QUERY_POLL_INTERVAL_IN_MS, context: { debounceKey: 'my-cookbooks', }, @@ -27,15 +26,6 @@ export default function MyCookbooks() { const userCookbooks = data?.user?.cookbooks || []; - // check the recipe against the search filters - const filteredCookbooks = userCookbooks.filter((cookbook) => { - if (!filterBy.name(filters, cookbook.name)) return false; - // if (!filterBy.language(filters, cookbook.language)) return false; - if (!filterBy.privacy(filters, cookbook.isPublic)) return false; - if (!filterBy.isSubscribed(filters, cookbook.isSubscribed)) return false; - return true; - }); - if (error) { return ; } @@ -44,13 +34,13 @@ export default function MyCookbooks() { return ; } - if (userCookbooks.length === 0) { - return ; + if (userCookbooks.length === 0 && !filters.isEmpty) { + return ; } - if (filteredCookbooks.length === 0) { - return ; + if (userCookbooks.length === 0) { + return ; } - return ; + return ; } diff --git a/src/renderer/pages/MySnippets.tsx b/src/renderer/pages/MySnippets.tsx index cc8b183..7fc77f1 100644 --- a/src/renderer/pages/MySnippets.tsx +++ b/src/renderer/pages/MySnippets.tsx @@ -1,31 +1,24 @@ import { useQuery } from '@apollo/client'; import { GET_USER_RECIPES } from '../graphql/queries'; -import { GET_USER_RECIPES_VARIABLES } from '../graphql/variables'; import { AssistantRecipeWithStats } from '../types/assistantTypes'; import SnippetTableLoading from '../components/SnippetTable/SnippetTableLoading'; import SnippetTableError from '../components/SnippetTable/SnippetTableError'; import SnippetTableEmpty from '../components/SnippetTable/SnippetTableEmpty'; +import SnippetTableEmptyFiltered from '../components/SnippetTable/SnippetTableEmptyFiltered'; import SnippetTable from '../components/SnippetTable/SnippetTable'; import { useFilters } from '../components/FiltersContext'; -import filterBy from '../components/Filters/filterBy'; -import SnippetTableEmptyFiltered from '../components/SnippetTable/SnippetTableEmptyFiltered'; -import { Language } from '../lib/constants'; +import useQueryVariables from '../hooks/useQueryVariables'; +import { PAGE_QUERY_POLL_INTERVAL_IN_MS } from '../lib/constants'; export default function MySnippets() { const filters = useFilters(); + const variables = useQueryVariables('my-snippets'); const { data, loading, error } = useQuery<{ user: { recipes: AssistantRecipeWithStats[] }; }>(GET_USER_RECIPES, { - variables: { - ...GET_USER_RECIPES_VARIABLES, - name: filters.searchTerm, - language: - filters.language && filters.language !== Language.ALL_LANGUAGES - ? filters.language - : null, - tag: filters.tags, - }, + variables, + pollInterval: PAGE_QUERY_POLL_INTERVAL_IN_MS, context: { debounceKey: 'my-snippets', }, @@ -33,17 +26,6 @@ export default function MySnippets() { const userRecipes = data?.user?.recipes || []; - // check the recipe against the search filters - const filteredRecipes = userRecipes.filter((recipe) => { - if (!filterBy.name(filters, recipe.name)) return false; - if (!filterBy.language(filters, recipe.language)) return false; - if (!filterBy.library(filters, recipe.dependencyConstraints)) return false; - if (!filterBy.tags(filters, recipe.tags)) return false; - if (!filterBy.privacy(filters, recipe.isPublic)) return false; - if (!filterBy.isSubscribed(filters, recipe.isSubscribed)) return false; - return true; - }); - if (error) { return ; } @@ -52,13 +34,13 @@ export default function MySnippets() { return ; } - if (userRecipes.length === 0) { - return ; + if (userRecipes.length === 0 && !filters.isEmpty) { + return ; } - if (filteredRecipes.length === 0) { - return ; + if (userRecipes.length === 0) { + return ; } - return ; + return ; } diff --git a/src/renderer/pages/TeamCookbooks.tsx b/src/renderer/pages/TeamCookbooks.tsx index 9e740f5..fbe9c7f 100644 --- a/src/renderer/pages/TeamCookbooks.tsx +++ b/src/renderer/pages/TeamCookbooks.tsx @@ -1,25 +1,24 @@ import { useQuery } from '@apollo/client'; import { GET_SHARED_COOKBOOKS } from '../graphql/queries'; -import { GET_SHARED_COOKBOOKS_VARIABLES } from '../graphql/variables'; import { AssistantCookbook } from '../types/assistantTypes'; import CookbookTableLoading from '../components/CookbookTable/CookbookTableLoading'; import CookbookTableError from '../components/CookbookTable/CookbookTableError'; import CookbookTableEmpty from '../components/CookbookTable/CookbookTableEmpty'; -import CookbookTableEmptyFiltereed from '../components/CookbookTable/CookbookTableEmptyFiltered'; +import CookbookTableEmptyFiltered from '../components/CookbookTable/CookbookTableEmptyFiltered'; import CookbookTable from '../components/CookbookTable/CookbookTable'; import { useFilters } from '../components/FiltersContext'; -import filterBy from '../components/Filters/filterBy'; +import useQueryVariables from '../hooks/useQueryVariables'; +import { PAGE_QUERY_POLL_INTERVAL_IN_MS } from '../lib/constants'; export default function TeamCookbooks() { const filters = useFilters(); + const variables = useQueryVariables('team-cookbooks'); const { data, loading, error } = useQuery<{ cookbooks: AssistantCookbook[]; }>(GET_SHARED_COOKBOOKS, { - variables: { - ...GET_SHARED_COOKBOOKS_VARIABLES, - name: filters.searchTerm, - }, + variables, + pollInterval: PAGE_QUERY_POLL_INTERVAL_IN_MS, context: { debounceKey: 'team-cookbooks', }, @@ -27,14 +26,6 @@ export default function TeamCookbooks() { const userCookbooks = data?.cookbooks || []; - // check the recipe against the search filters - const filteredCookbooks = userCookbooks.filter((cookbook) => { - if (!filterBy.name(filters, cookbook.name)) return false; - if (!filterBy.privacy(filters, cookbook.isPublic)) return false; - if (!filterBy.isSubscribed(filters, cookbook.isSubscribed)) return false; - return true; - }); - if (error) { return ; } @@ -43,13 +34,13 @@ export default function TeamCookbooks() { return ; } - if (userCookbooks.length === 0) { - return ; + if (userCookbooks.length === 0 && !filters.isEmpty) { + return ; } - if (filteredCookbooks.length === 0) { - return ; + if (userCookbooks.length === 0) { + return ; } - return ; + return ; } diff --git a/src/renderer/pages/TeamSnippets.tsx b/src/renderer/pages/TeamSnippets.tsx index f12e389..4500a0f 100644 --- a/src/renderer/pages/TeamSnippets.tsx +++ b/src/renderer/pages/TeamSnippets.tsx @@ -1,31 +1,24 @@ import { useQuery } from '@apollo/client'; import { GET_SHARED_RECIPES } from '../graphql/queries'; -import { GET_SHARED_RECIPES_VARIABLES } from '../graphql/variables'; import { AssistantRecipeWithStats } from '../types/assistantTypes'; import SnippetTableLoading from '../components/SnippetTable/SnippetTableLoading'; import SnippetTableError from '../components/SnippetTable/SnippetTableError'; import SnippetTableEmpty from '../components/SnippetTable/SnippetTableEmpty'; -import SnippetTableEmptyFiltereed from '../components/SnippetTable/SnippetTableEmptyFiltered'; +import SnippetTableEmptyFiltered from '../components/SnippetTable/SnippetTableEmptyFiltered'; import SnippetTable from '../components/SnippetTable/SnippetTable'; -import filterBy from '../components/Filters/filterBy'; import { useFilters } from '../components/FiltersContext'; -import { Language } from '../lib/constants'; +import useQueryVariables from '../hooks/useQueryVariables'; +import { PAGE_QUERY_POLL_INTERVAL_IN_MS } from '../lib/constants'; export default function TeamSnippets() { const filters = useFilters(); + const variables = useQueryVariables('team-snippets'); const { data, loading, error } = useQuery<{ recipes: AssistantRecipeWithStats[]; }>(GET_SHARED_RECIPES, { - variables: { - ...GET_SHARED_RECIPES_VARIABLES, - name: filters.searchTerm, - languages: - filters.language && filters.language !== Language.ALL_LANGUAGES - ? [filters.language] - : null, - tag: filters.tags, - }, + variables, + pollInterval: PAGE_QUERY_POLL_INTERVAL_IN_MS, context: { debounceKey: 'team-snippets', }, @@ -33,17 +26,6 @@ export default function TeamSnippets() { const teamRecipes = data?.recipes || []; - // check the recipe against the search filters - const filteredRecipes = teamRecipes.filter((recipe) => { - if (!filterBy.name(filters, recipe.name)) return false; - if (!filterBy.language(filters, recipe.language)) return false; - if (!filterBy.library(filters, recipe.dependencyConstraints)) return false; - if (!filterBy.tags(filters, recipe.tags)) return false; - if (!filterBy.privacy(filters, recipe.isPublic)) return false; - if (!filterBy.isSubscribed(filters, recipe.isSubscribed)) return false; - return true; - }); - if (error) { return ; } @@ -52,13 +34,13 @@ export default function TeamSnippets() { return ; } - if (teamRecipes.length === 0) { - return ; + if (teamRecipes.length === 0 && !filters.isEmpty) { + return ; } - if (filteredRecipes.length === 0) { - return ; + if (teamRecipes.length === 0) { + return ; } - return ; + return ; } diff --git a/src/renderer/pages/ViewCookbookSnippets.tsx b/src/renderer/pages/ViewCookbookSnippets.tsx new file mode 100644 index 0000000..f8fa231 --- /dev/null +++ b/src/renderer/pages/ViewCookbookSnippets.tsx @@ -0,0 +1,131 @@ +/* eslint-disable no-nested-ternary */ +import { useState } from 'react'; +import { useQuery } from '@apollo/client'; +import { useParams } from 'react-router-dom'; +import { Box, Flex, HStack, Input, Link, Text } from '@chakra-ui/react'; + +import { getCookbookUrl } from '../utils/urlUtils'; +import { GET_COOKBOOK_INFO, GET_COOKBOOK_RECIPES } from '../graphql/queries'; +import { GET_USER_RECIPES_VARIABLES } from '../graphql/variables'; +import { PAGE_QUERY_POLL_INTERVAL_IN_MS } from '../lib/constants'; +import ViewCookbookSnippetsError from '../components/ViewCookbookSnippets/ViewCookbookSnippetsError'; +import ViewCookbookSnippetsLoading from '../components/ViewCookbookSnippets/ViewCookbookSnippetsLoading'; +import ViewCookbookSnippetsEmpty from '../components/ViewCookbookSnippets/ViewCookbookSnippetsEmpty'; +import ViewCookbookSnippetsEmptyFiltered from '../components/ViewCookbookSnippets/ViewCookbookSnippetsEmptyFiltered'; +import BackButton from '../components/BackButton'; +import FavoriteCookbook from '../components/Favorite/FavoriteCookbook'; +import AvatarAndName from '../components/AvatarAndName'; +import PrivacyAndVotes from '../components/PrivacyAndVotes'; +import FormattedDate from '../components/FormattedDate'; +import SnippetResults from '../components/SnippetResults/SnippetResults'; +import SnippetResultsLoading from '../components/SnippetResults/SnippetResultsLoading'; + +export default function ViewCookbookSnippets() { + const params = useParams(); + const [searchTerm, setSearchTerm] = useState(''); + + const { + data: cookbookInfoData, + loading: cookbookInfoLoading, + error: cookbookInfoError, + } = useQuery(GET_COOKBOOK_INFO, { + pollInterval: PAGE_QUERY_POLL_INTERVAL_IN_MS, + variables: { + cookbookId: Number(params.cookbookId), + }, + }); + + const { + data: cookbookSnippetData, + loading: cookbookSnippetLoading, + error: cookbookSnippetError, + } = useQuery(GET_COOKBOOK_RECIPES, { + pollInterval: PAGE_QUERY_POLL_INTERVAL_IN_MS, + variables: { + cookbookId: Number(params.cookbookId), + ...GET_USER_RECIPES_VARIABLES, + name: searchTerm || null, + }, + context: { + debounceKey: 'view-cookbook-snippets', + }, + }); + + const cookbook = cookbookInfoData?.cookbook; + const snippets = cookbookSnippetData?.cookbook?.recipes || []; + + if (cookbookInfoError || cookbook === null || cookbookSnippetError) { + return ; + } + + return ( + + {/* INFO SECTION */} + {cookbookInfoLoading ? ( + + ) : ( + + + + + + + {cookbook.name} + + + + + + + + + + + + + setSearchTerm(e.target.value)} + /> + + + )} + + {cookbookSnippetLoading ? ( + + ) : snippets.length > 0 ? ( + + ) : searchTerm ? ( + setSearchTerm('')} + /> + ) : ( + + )} + + ); +} diff --git a/src/renderer/pages/ViewSnippet.tsx b/src/renderer/pages/ViewSnippet.tsx new file mode 100644 index 0000000..9d65d84 --- /dev/null +++ b/src/renderer/pages/ViewSnippet.tsx @@ -0,0 +1,86 @@ +import { useParams } from 'react-router-dom'; +import { Box, Flex, HStack, Link, Text } from '@chakra-ui/react'; +import { Logo, Tags } from '@codiga/components'; +import { useQuery } from '@apollo/client'; + +import { getSnippetUrl } from '../utils/urlUtils'; +import { GET_RECIPE } from '../graphql/queries'; +import FavoriteSnippet from '../components/Favorite/FavoriteSnippet'; +import ViewSnippetError from '../components/ViewSnippet/ViewSnippetError'; +import ViewSnippetLoading from '../components/ViewSnippet/ViewSnippetLoading'; +import BackButton from '../components/BackButton'; +import PrivacyAndVotes from '../components/PrivacyAndVotes'; +import FormattedDate from '../components/FormattedDate'; +import AvatarAndName from '../components/AvatarAndName'; +import Code from '../components/Code/Code'; + +export default function ViewSnippet() { + const params = useParams(); + + const { data, loading, error } = useQuery(GET_RECIPE, { + variables: { + recipeId: Number(params.snippetId), + }, + }); + + const recipe = data?.recipe; + + if (loading) { + return ; + } + + if (error || !recipe) { + return ; + } + + return ( + + {/* INFO SECTION */} + + + + + + + + {recipe.name} + + + + + + + + + + + + {recipe?.tags && recipe?.tags.length > 0 && ( + + )} + + + {/* CODE */} + + + ); +} diff --git a/src/renderer/preload.d.ts b/src/renderer/preload.d.ts index 797d275..7e20f12 100644 --- a/src/renderer/preload.d.ts +++ b/src/renderer/preload.d.ts @@ -3,6 +3,7 @@ import { Channels } from 'main/preload'; declare global { interface Window { electron: { + isMac: boolean; ipcRenderer: { sendMessage(channel: Channels, args: unknown[]): void; on( diff --git a/src/renderer/styles/reboot.css b/src/renderer/styles/reboot.css index 3d86a2c..b95ce3c 100644 --- a/src/renderer/styles/reboot.css +++ b/src/renderer/styles/reboot.css @@ -59,7 +59,7 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; - font-size: 1rem; + font-size: 14px; font-weight: normal; line-height: 1.5; color: #212529; diff --git a/src/renderer/utils/urlUtils.ts b/src/renderer/utils/urlUtils.ts index 61664b6..94f54d4 100644 --- a/src/renderer/utils/urlUtils.ts +++ b/src/renderer/utils/urlUtils.ts @@ -1,28 +1,14 @@ import { APP_URL } from '../lib/config'; -import { PageTypes } from '../types/pageTypes'; -export const getCookbookUrl = ( - page: PageTypes, - id: number, - groupId?: number -) => { - return `${APP_URL}${ - page === 'team' - ? `/assistant/group-sharing/${groupId}/cookbook/${id}/view` - : `/assistant/cookbook/${id}/view` - }`; +export const getCookbookUrl = (id: number) => { + return `${APP_URL}/assistant/cookbook/${id}/view`; }; -export const getSnippetUrl = ( - page: PageTypes, - id: number, - groupId?: number +export const getSnippetUrl: (id: number, page?: 'view' | 'edit') => string = ( + id, + page = 'view' ) => { - return `${APP_URL}${ - page === 'team' - ? `/assistant/group-sharing/${groupId}/snippet/${id}/view` - : `/assistant/snippet/${id}/view` - }`; + return `${APP_URL}/assistant/snippet/${id}/${page}`; }; export const getUserUrl = (slug: string) => {