This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

basic structure for searching urls for connections

+163 -25
+116 -13
src/webapp/features/connections/components/addConnectionDrawer/AddConnectionForm.tsx
··· 2 2 3 3 import { 4 4 Button, 5 + Combobox, 5 6 Group, 7 + Image, 6 8 Input, 9 + Loader, 10 + ScrollArea, 7 11 Select, 8 12 Stack, 9 13 Text, 10 14 Textarea, 11 - TextInput, 15 + useCombobox, 12 16 VisuallyHidden, 13 17 } from '@mantine/core'; 14 18 import { useForm } from '@mantine/form'; 19 + import { useDebouncedValue } from '@mantine/hooks'; 15 20 import { notifications } from '@mantine/notifications'; 21 + import { useState } from 'react'; 22 + import { useQuery } from '@tanstack/react-query'; 16 23 import useCreateConnection from '../../lib/mutations/useCreateConnection'; 24 + import { searchUrls } from '../../lib/dal'; 17 25 import { IoMdLink } from 'react-icons/io'; 18 26 19 27 interface Props { ··· 35 43 export default function AddConnectionForm(props: Props) { 36 44 const createConnection = useCreateConnection(); 37 45 46 + const combobox = useCombobox({ 47 + onDropdownClose: () => combobox.resetSelectedOption(), 48 + }); 49 + 50 + const [inputValue, setInputValue] = useState(''); 51 + const [debounced] = useDebouncedValue(inputValue, 200); 52 + 53 + const { 54 + data: searchResults, 55 + isFetching, 56 + error, 57 + } = useQuery({ 58 + queryKey: ['url search', debounced], 59 + queryFn: () => 60 + searchUrls({ 61 + searchQuery: debounced, 62 + limit: 10, 63 + }), 64 + enabled: debounced.trim().length > 0, 65 + }); 66 + 67 + const urls = searchResults?.urls ?? []; 68 + const empty = 69 + !error && !isFetching && debounced.trim().length > 0 && urls.length === 0; 70 + 38 71 const form = useForm({ 39 72 initialValues: { 40 73 targetUrl: '', ··· 81 114 }, 82 115 ); 83 116 }; 117 + 118 + const options = urls.map((urlView) => ( 119 + <Combobox.Option key={urlView.url} value={urlView.url} p={5}> 120 + <Group gap={'xs'} wrap="nowrap" align="flex-start"> 121 + {urlView.metadata.imageUrl && ( 122 + <Image 123 + src={urlView.metadata.imageUrl} 124 + alt={urlView.metadata.title || 'URL thumbnail'} 125 + w={60} 126 + h={60} 127 + radius="sm" 128 + fit="cover" 129 + /> 130 + )} 131 + <Stack gap={0} style={{ flex: 1, minWidth: 0 }}> 132 + <Text fw={500} c={'bright'} lineClamp={1} size="sm"> 133 + {urlView.metadata.title || urlView.url} 134 + </Text> 135 + {urlView.metadata.description && ( 136 + <Text c={'dimmed'} lineClamp={2} size="xs"> 137 + {urlView.metadata.description} 138 + </Text> 139 + )} 140 + <Text c={'gray'} lineClamp={1} size="xs"> 141 + {urlView.url} 142 + </Text> 143 + </Stack> 144 + </Group> 145 + </Combobox.Option> 146 + )); 84 147 85 148 return ( 86 149 <form onSubmit={handleCreateConnection}> 87 150 <Stack gap={'xl'}> 88 - <TextInput 89 - id="targetUrl" 90 - label="Target URL" 91 - type="url" 92 - placeholder="https://www.example.com" 93 - variant="filled" 94 - required 95 - size="md" 96 - leftSection={<IoMdLink size={22} />} 97 - key={form.key('targetUrl')} 98 - {...form.getInputProps('targetUrl')} 99 - /> 151 + <Stack gap={4}> 152 + <Input.Label size="md" htmlFor="targetUrl" required> 153 + Target URL 154 + </Input.Label> 155 + <Combobox 156 + shadow="sm" 157 + radius={'md'} 158 + store={combobox} 159 + withinPortal={false} 160 + onOptionSubmit={(url) => { 161 + form.setFieldValue('targetUrl', url); 162 + setInputValue(url); 163 + combobox.closeDropdown(); 164 + }} 165 + > 166 + <Combobox.Target> 167 + <Input 168 + id="targetUrl" 169 + component="input" 170 + type="url" 171 + placeholder="https://www.example.com or start typing to search for urls" 172 + value={inputValue} 173 + onChange={(e) => { 174 + const val = e.currentTarget.value; 175 + setInputValue(val); 176 + form.setFieldValue('targetUrl', val); 177 + combobox.openDropdown(); 178 + }} 179 + onFocus={() => combobox.openDropdown()} 180 + onBlur={() => combobox.closeDropdown()} 181 + leftSection={<IoMdLink size={22} />} 182 + rightSection={isFetching && <Loader size={18} />} 183 + variant="filled" 184 + size="md" 185 + required 186 + /> 187 + </Combobox.Target> 188 + 189 + <Combobox.Dropdown hidden={debounced.trim().length === 0}> 190 + <Combobox.Options> 191 + <ScrollArea.Autosize type="scroll" mah={300}> 192 + {isFetching && <Combobox.Empty>Searching...</Combobox.Empty>} 193 + {error && ( 194 + <Combobox.Empty>Could not search for URLs</Combobox.Empty> 195 + )} 196 + {empty && <Combobox.Empty>No URLs found</Combobox.Empty>} 197 + {options.length > 0 && options} 198 + </ScrollArea.Autosize> 199 + </Combobox.Options> 200 + </Combobox.Dropdown> 201 + </Combobox> 202 + </Stack> 100 203 101 204 <Select 102 205 id="connectionType"
+1 -5
src/webapp/features/connections/components/connectionFilters/ConnectionFilters.tsx
··· 1 1 'use client'; 2 2 3 3 import { Group, Button, Menu } from '@mantine/core'; 4 - import { 5 - createContext, 6 - useContext, 7 - ReactNode, 8 - } from 'react'; 4 + import { createContext, useContext, ReactNode } from 'react'; 9 5 import { upperFirst } from '@mantine/hooks'; 10 6 import { MdFilterList } from 'react-icons/md'; 11 7 import { ConnectionType } from '@semble/types';
+26 -4
src/webapp/features/connections/lib/connectionKeys.ts
··· 3 3 export const connectionKeys = { 4 4 all: () => ['connections'] as const, 5 5 forwardForUrl: (url: string) => ['connections', 'forward', url] as const, 6 - forwardForUrlInfinite: (url: string, limit?: number, connectionTypes?: ConnectionType[]) => 7 - ['connections', 'forward', url, 'infinite', limit, connectionTypes] as const, 6 + forwardForUrlInfinite: ( 7 + url: string, 8 + limit?: number, 9 + connectionTypes?: ConnectionType[], 10 + ) => 11 + [ 12 + 'connections', 13 + 'forward', 14 + url, 15 + 'infinite', 16 + limit, 17 + connectionTypes, 18 + ] as const, 8 19 backwardForUrl: (url: string) => ['connections', 'backward', url] as const, 9 - backwardForUrlInfinite: (url: string, limit?: number, connectionTypes?: ConnectionType[]) => 10 - ['connections', 'backward', url, 'infinite', limit, connectionTypes] as const, 20 + backwardForUrlInfinite: ( 21 + url: string, 22 + limit?: number, 23 + connectionTypes?: ConnectionType[], 24 + ) => 25 + [ 26 + 'connections', 27 + 'backward', 28 + url, 29 + 'infinite', 30 + limit, 31 + connectionTypes, 32 + ] as const, 11 33 userConnections: (identifier: string) => 12 34 ['connections', 'user', identifier] as const, 13 35 };
+7
src/webapp/features/connections/lib/dal.ts
··· 4 4 CreateConnectionRequest, 5 5 GetForwardConnectionsForUrlParams, 6 6 GetBackwardConnectionsForUrlParams, 7 + SearchUrlsParams, 7 8 } from '@semble/types'; 8 9 import { cache } from 'react'; 9 10 ··· 35 36 return response; 36 37 }, 37 38 ); 39 + 40 + export const searchUrls = cache(async (params: SearchUrlsParams) => { 41 + const client = createSembleClient(); 42 + const response = await client.searchUrls(params); 43 + return response; 44 + });
+5 -1
src/webapp/features/connections/lib/queries/useBackwardConnections.tsx
··· 13 13 const limit = props?.limit ?? 16; 14 14 15 15 const backwardConnections = useSuspenseInfiniteQuery({ 16 - queryKey: connectionKeys.backwardForUrlInfinite(props.url, props.limit, props.connectionTypes), 16 + queryKey: connectionKeys.backwardForUrlInfinite( 17 + props.url, 18 + props.limit, 19 + props.connectionTypes, 20 + ), 17 21 initialPageParam: 1, 18 22 queryFn: ({ pageParam = 1 }) => { 19 23 return getBackwardConnectionsForUrl({
+5 -1
src/webapp/features/connections/lib/queries/useForwardConnections.tsx
··· 13 13 const limit = props?.limit ?? 16; 14 14 15 15 const forwardConnections = useSuspenseInfiniteQuery({ 16 - queryKey: connectionKeys.forwardForUrlInfinite(props.url, props.limit, props.connectionTypes), 16 + queryKey: connectionKeys.forwardForUrlInfinite( 17 + props.url, 18 + props.limit, 19 + props.connectionTypes, 20 + ), 17 21 initialPageParam: 1, 18 22 queryFn: ({ pageParam = 1 }) => { 19 23 return getForwardConnectionsForUrl({
+3 -1
src/webapp/features/semble/containers/sembleConnectionsContainer/SembleConnectionsContainer.tsx
··· 27 27 useDisclosure(false); 28 28 29 29 const [direction, setDirection] = useState<Direction>('outgoing'); 30 - const [connectionType, setConnectionType] = useState<ConnectionType | null>(null); 30 + const [connectionType, setConnectionType] = useState<ConnectionType | null>( 31 + null, 32 + ); 31 33 32 34 const connectionTypes = connectionType ? [connectionType] : undefined; 33 35