Skip to content
This repository was archived by the owner on May 4, 2023. It is now read-only.

Commit 3cc4a56

Browse files
feat: implemented snippet search page
1 parent c5bf32a commit 3cc4a56

19 files changed

+705
-12
lines changed

src/renderer/components/Favorite/Favorite.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import { HeartFilledIcon, HeartIcon } from '@codiga/codiga-components';
33
import { useUser } from 'renderer/components/UserContext';
44

55
export type FavoriteProps = {
6-
isSubscribed: boolean;
6+
isSubscribed?: boolean;
77
onSubscribe: () => void;
88
onUnsubscribe: () => void;
99
};
1010

1111
export default function Favorite({
12-
isSubscribed,
12+
isSubscribed = false,
1313
onSubscribe,
1414
onUnsubscribe,
1515
}: FavoriteProps) {

src/renderer/components/Favorite/FavoriteSnippet.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
UNSUBSCRIBE_TO_RECIPE,
66
} from 'renderer/graphql/mutations';
77
import {
8+
GET_RECIPES_SEMANTICALLY,
89
GET_SHARED_RECIPES,
910
GET_USER_RECIPES,
1011
GET_USER_SUBSCRIBED_RECIPES,
@@ -33,6 +34,7 @@ const recipeRefetches = [
3334
query: GET_SHARED_RECIPES,
3435
variables: GET_SHARED_RECIPES_VARIABLES,
3536
},
37+
GET_RECIPES_SEMANTICALLY,
3638
];
3739

3840
export default function FavoriteSnippet({

src/renderer/components/Layout/Layout.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export default function Layout({ children }: LayoutProps) {
5353
{!isOnline ? (
5454
<NoInternetConnection />
5555
) : (
56-
<Flex flex={1}>
56+
<Flex flex={1} overflow="hidden">
5757
<Box
5858
w="214px"
5959
minW="214px"
@@ -69,11 +69,11 @@ export default function Layout({ children }: LayoutProps) {
6969
flex={1}
7070
justifyContent="center"
7171
alignItems="flex-start"
72-
overflow="auto"
72+
overflow="hidden"
7373
bg="neutral.0"
7474
_dark={{ bg: 'neutral.100' }}
7575
>
76-
<Box w="full">
76+
<Box w="full" height="calc(100% - 74px)">
7777
{/* IF THE USER'S LOGGED IN, SHOW CONTENT, OTHERWISE SHOW AN LOGIN NOTICE */}
7878
{id || pathname === '/' ? (
7979
children
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import {
2+
Box,
3+
BoxProps,
4+
useRadio,
5+
useRadioGroup,
6+
UseRadioGroupProps,
7+
UseRadioProps,
8+
} from '@chakra-ui/react';
9+
10+
const RadioButton = ({
11+
children,
12+
inputProps,
13+
...props
14+
}: BoxProps & { inputProps: UseRadioProps }) => {
15+
const { getInputProps, getCheckboxProps } = useRadio(inputProps);
16+
17+
const input = getInputProps();
18+
const checkbox = getCheckboxProps();
19+
20+
return (
21+
<Box
22+
as="label"
23+
sx={{
24+
_first: {
25+
'& div': {
26+
borderLeftRadius: 'sm',
27+
},
28+
},
29+
_last: {
30+
'& div': {
31+
borderRightRadius: 'sm',
32+
},
33+
},
34+
}}
35+
pos="relative"
36+
{...props}
37+
>
38+
<input {...input} />
39+
<Box
40+
{...checkbox}
41+
cursor="pointer"
42+
px="space_8"
43+
py="space_4"
44+
fontSize="12px"
45+
lineHeight="20px"
46+
textTransform="capitalize"
47+
color="neutral.100"
48+
bg="neutral.0"
49+
tabIndex={0}
50+
_checked={{
51+
bg: 'neutral.50',
52+
}}
53+
_focus={{
54+
boxShadow: 'outline',
55+
}}
56+
_focusVisible={{
57+
boxShadow: 'outline',
58+
outline: 0,
59+
}}
60+
_dark={{
61+
color: 'neutral.0',
62+
bg: 'neutral.100',
63+
_checked: {
64+
bg: 'base.onyx',
65+
},
66+
}}
67+
>
68+
{children}
69+
</Box>
70+
</Box>
71+
);
72+
};
73+
74+
const CodeViewToggler = ({
75+
inputProps,
76+
...props
77+
}: BoxProps & { inputProps: UseRadioGroupProps }) => {
78+
const options = ['raw', 'preview'];
79+
80+
const { getRootProps, getRadioProps } = useRadioGroup({
81+
name: 'code-view',
82+
...inputProps,
83+
});
84+
85+
const group = getRootProps();
86+
87+
return (
88+
<Box
89+
{...props}
90+
{...group}
91+
borderRadius="base"
92+
borderWidth="1px"
93+
borderStyle="solid"
94+
borderColor="neutral.50"
95+
_dark={{ borderColor: 'base.onyx' }}
96+
>
97+
{options.map((value) => {
98+
const radio = getRadioProps({ value });
99+
100+
return (
101+
<RadioButton key={value} inputProps={radio}>
102+
{value}
103+
</RadioButton>
104+
);
105+
})}
106+
</Box>
107+
);
108+
};
109+
110+
export default CodeViewToggler;
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { Box, Flex } from '@chakra-ui/react';
2+
import { useState } from 'react';
3+
import {
4+
AssistantRecipeWithStats,
5+
RecipeSummary,
6+
} from 'renderer/types/assistantTypes';
7+
import SearchResultsCode from './SearchResultsCode';
8+
import SearchResultsList from './SearchResultsList';
9+
import SearchResultsListItem from './SearchResultsListItem';
10+
11+
type SearchResultsProps = {
12+
results: AssistantRecipeWithStats[];
13+
};
14+
15+
export default function SearchResults({ results }: SearchResultsProps) {
16+
const [snippetInFocus, setSnippetInFocus] = useState(results[0] || {});
17+
18+
const changeSnippetInFocus = (recipe: RecipeSummary) => {
19+
setSnippetInFocus(recipe);
20+
};
21+
22+
return (
23+
<Flex h="full" overflow="hidden">
24+
<SearchResultsList>
25+
{results.map((result) => (
26+
<SearchResultsListItem
27+
key={result.id}
28+
recipe={result}
29+
changeSnippetInFocus={changeSnippetInFocus}
30+
/>
31+
))}
32+
</SearchResultsList>
33+
<Flex position="relative" flex={1} h="full" w="full" overflowX="scroll">
34+
<Box
35+
position="absolute"
36+
top="0"
37+
left="0"
38+
minHeight="full"
39+
minWidth="full"
40+
h="full"
41+
>
42+
{results[0] ? <SearchResultsCode recipe={snippetInFocus} /> : null}
43+
</Box>
44+
</Flex>
45+
</Flex>
46+
);
47+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import {
2+
Flex,
3+
LinkBox,
4+
IconButton,
5+
useClipboard,
6+
useColorModeValue,
7+
useToken,
8+
Tooltip,
9+
Text,
10+
Link,
11+
} from '@chakra-ui/react';
12+
import {
13+
BubbleIcon,
14+
Code,
15+
CodeContent,
16+
CopyIcon,
17+
useToast,
18+
} from '@codiga/codiga-components';
19+
import { useEffect } from 'react';
20+
import useCodeView, { CodeViewsType } from 'renderer/hooks/useCodeView';
21+
import { APP_URL } from 'renderer/lib/config';
22+
import { AssistantRecipeWithStats } from 'renderer/types/assistantTypes';
23+
import { decodeIndent } from 'renderer/utils/codeUtils';
24+
import CodeViewToggler from './CodeViewToggler';
25+
26+
type SearchResultsCodeProps = {
27+
recipe: AssistantRecipeWithStats;
28+
};
29+
30+
export default function SearchResultsCode({ recipe }: SearchResultsCodeProps) {
31+
const toast = useToast();
32+
const [codeView, setCodeView] = useCodeView('preview');
33+
34+
const neutral100 = useToken('colors', 'neutral.100');
35+
const bg = useColorModeValue('white', neutral100);
36+
37+
const code =
38+
codeView === 'preview'
39+
? decodeIndent(recipe?.presentableFormat)
40+
: decodeIndent(recipe?.code);
41+
const imports = recipe?.imports?.join('\n');
42+
const codeForCopy = imports ? `${imports}\n${code}` : code;
43+
44+
const { hasCopied, onCopy } = useClipboard(codeForCopy);
45+
46+
useEffect(() => {
47+
if (hasCopied) {
48+
toast({ status: 'success', description: 'Snippet copied' });
49+
}
50+
}, [hasCopied, toast]);
51+
52+
const commentsCount = Number(recipe.commentsCount);
53+
const lines = code.split('\n').length;
54+
const lineMaxDigits = lines.toString().length;
55+
const minWidth = lineMaxDigits < 3 ? '2.7em' : `${lineMaxDigits}.25em`;
56+
57+
return (
58+
<LinkBox
59+
as="article"
60+
w="full"
61+
minH="full"
62+
overflow="hidden"
63+
borderWidth="1px"
64+
borderStyle="solid"
65+
borderColor="neutral.50"
66+
_dark={{
67+
borderColor: 'base.onyx',
68+
}}
69+
>
70+
<Code
71+
border={0}
72+
borderRadius={0}
73+
pos="relative"
74+
sx={{
75+
'code[class*="language-"] > span:first-child > .linenumber:first-child':
76+
{
77+
paddingTop: '0.5em !important',
78+
},
79+
'code[class*="language-"] .linenumber': {
80+
border: '0 !important',
81+
background: 'transparent !important',
82+
fontStyle: 'normal !important',
83+
},
84+
}}
85+
>
86+
<Flex
87+
pos="absolute"
88+
alignItems="center"
89+
gridGap="space_8"
90+
top="space_8"
91+
right="space_8"
92+
zIndex="docked"
93+
>
94+
<CodeViewToggler
95+
inputProps={{
96+
value: codeView,
97+
onChange: (value) => setCodeView(value as CodeViewsType),
98+
}}
99+
/>
100+
101+
<Tooltip label="Copy Snippet">
102+
<IconButton
103+
variant="ghost"
104+
h="32px"
105+
minW="32px"
106+
p="space_8"
107+
icon={<CopyIcon />}
108+
onClick={onCopy}
109+
aria-label="Copy Snippet"
110+
/>
111+
</Tooltip>
112+
113+
<Tooltip label="Comment on Snippet">
114+
<IconButton
115+
as={Link}
116+
isExternal
117+
href={`${APP_URL}/assistant/snippet/${recipe.id}/view`}
118+
variant="ghost"
119+
h="32px"
120+
minW="32px"
121+
p="space_8"
122+
icon={
123+
<Flex gridGap="space_4" alignItems="center">
124+
<BubbleIcon />
125+
<Text as="span" size="xs" lineHeight="16px">
126+
{commentsCount}
127+
</Text>
128+
</Flex>
129+
}
130+
aria-label="Comment on Snippet"
131+
/>
132+
</Tooltip>
133+
</Flex>
134+
135+
<CodeContent
136+
customStyle={{
137+
background: bg,
138+
overflow: 'hidden',
139+
}}
140+
codeTagProps={{
141+
style: {
142+
display: 'block',
143+
height: '100%',
144+
fontSize: '14px',
145+
},
146+
}}
147+
lineNumberStyle={{ minWidth }}
148+
language={recipe.language?.toLocaleLowerCase()}
149+
>
150+
{code}
151+
</CodeContent>
152+
</Code>
153+
</LinkBox>
154+
);
155+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Skeleton, VStack } from '@chakra-ui/react';
2+
3+
export default function SearchResultsCodeLoading() {
4+
return (
5+
<VStack
6+
p="space_16"
7+
pt="7px"
8+
w="full"
9+
spacing="space_8"
10+
alignItems="flex-start"
11+
>
12+
<Skeleton h="16px" w="60%" />
13+
<Skeleton h="16px" w="40%" />
14+
<Skeleton h="16px" w="0%" />
15+
<Skeleton h="16px" w="30%" />
16+
<Skeleton h="16px" w="40%" />
17+
<Skeleton h="16px" w="60%" />
18+
<Skeleton h="16px" w="70%" />
19+
<Skeleton h="16px" w="80%" />
20+
<Skeleton h="16px" w="75%" />
21+
<Skeleton h="16px" w="65%" />
22+
<Skeleton h="16px" w="50%" />
23+
<Skeleton h="16px" w="60%" />
24+
<Skeleton h="16px" w="50%" />
25+
<Skeleton h="16px" w="30%" />
26+
<Skeleton h="16px" w="20%" />
27+
</VStack>
28+
);
29+
}

0 commit comments

Comments
 (0)