|
23 | 23 | import { page } from '$app/state';
|
24 | 24 | import Card from '../card.svelte';
|
25 | 25 | import SkeletonRepoList from './skeletonRepoList.svelte';
|
26 |
| - import { onMount, untrack } from 'svelte'; |
| 26 | + import { onMount, untrack, onDestroy } from 'svelte'; |
| 27 | + import { debounce } from '$lib/helpers/debounce'; |
27 | 28 |
|
28 | 29 | let {
|
29 | 30 | action = $bindable('select'),
|
|
52 | 53 | loadInstallations();
|
53 | 54 | });
|
54 | 55 |
|
| 56 | + const debouncedLoadRepositories = debounce( |
| 57 | + async (installationId: string, searchTerm: string) => { |
| 58 | + isLoadingRepositories = true; |
| 59 | + try { |
| 60 | + await loadRepositories(installationId, searchTerm); |
| 61 | + } finally { |
| 62 | + isLoadingRepositories = false; |
| 63 | + } |
| 64 | + }, |
| 65 | + 300 |
| 66 | + ); |
| 67 | +
|
| 68 | + $effect(() => { |
| 69 | + if (selectedInstallation && search !== undefined) { |
| 70 | + debouncedLoadRepositories(selectedInstallation, search); |
| 71 | + } |
| 72 | + }); |
| 73 | +
|
| 74 | + onDestroy(() => { |
| 75 | + debouncedLoadRepositories.cancel(); |
| 76 | + }); |
| 77 | +
|
55 | 78 | async function loadInstallations() {
|
56 | 79 | if (installationList) {
|
57 | 80 | if (installationList.installations.length) {
|
|
71 | 94 | .vcs.listInstallations();
|
72 | 95 | if (installations.length) {
|
73 | 96 | if (!selectedInstallation) {
|
74 |
| - untrack(() => (selectedInstallation = installationList.installations[0].$id)); |
| 97 | + untrack(() => (selectedInstallation = installations[0].$id)); |
75 | 98 | }
|
76 | 99 | installation.set(installations.find((entry) => entry.$id === selectedInstallation));
|
77 | 100 | }
|
|
80 | 103 | }
|
81 | 104 |
|
82 | 105 | async function loadRepositories(installationId: string, search: string) {
|
83 |
| - if ( |
84 |
| - !$repositories || |
85 |
| - $repositories.installationId !== installationId || |
86 |
| - $repositories.search !== search |
87 |
| - ) { |
88 |
| - await fetchRepos(installationId, search); |
89 |
| - } |
90 |
| -
|
91 |
| - if ($repositories.repositories.length && action === 'select') { |
92 |
| - selectedRepository = $repositories.repositories[0].id; |
93 |
| - $repository = $repositories.repositories[0]; |
94 |
| - } |
95 |
| - return $repositories.repositories; |
96 |
| - } |
97 |
| -
|
98 |
| - async function fetchRepos(installationId: string, search: string) { |
99 | 106 | if (product === 'functions') {
|
100 | 107 | $repositories.repositories = (
|
101 | 108 | (await sdk
|
|
117 | 124 | )) as unknown as Models.ProviderRepositoryFrameworkList
|
118 | 125 | ).frameworkProviderRepositories;
|
119 | 126 | }
|
120 |
| -
|
121 | 127 | $repositories.search = search;
|
122 | 128 | $repositories.installationId = installationId;
|
| 129 | + return $repositories.repositories; |
123 | 130 | }
|
124 | 131 |
|
125 | 132 | selectedRepository;
|
|
168 | 175 | installationsMap.find((entry) => entry.$id === selectedInstallation)
|
169 | 176 | );
|
170 | 177 |
|
171 |
| - isLoadingRepositories = true; |
172 |
| - loadRepositories(selectedInstallation, search).then(() => { |
173 |
| - isLoadingRepositories = false; |
174 |
| - }); |
| 178 | + debouncedLoadRepositories.cancel(); |
175 | 179 | }}
|
176 | 180 | bind:value={selectedInstallation} />
|
177 | 181 | <InputSearch
|
|
185 | 189 | <!-- manual installation change -->
|
186 | 190 | {#if isLoadingRepositories}
|
187 | 191 | <SkeletonRepoList />
|
188 |
| - {:else} |
189 |
| - {#await loadRepositories(selectedInstallation, search)} |
190 |
| - <SkeletonRepoList /> |
191 |
| - {:then response} |
192 |
| - {#if response?.length} |
193 |
| - <Paginator items={response} hideFooter={response?.length <= 6} limit={6}> |
194 |
| - {#snippet children( |
195 |
| - paginatedItems: Models.ProviderRepositoryRuntime[] & |
196 |
| - Models.ProviderRepositoryFramework[] |
197 |
| - )} |
198 |
| - <Table.Root columns={1} let:root> |
199 |
| - {#each paginatedItems as repo} |
200 |
| - <Table.Row.Base {root}> |
201 |
| - <Table.Cell {root}> |
| 192 | + {:else if $repositories?.repositories?.length} |
| 193 | + <Paginator |
| 194 | + items={$repositories.repositories} |
| 195 | + hideFooter={$repositories.repositories?.length <= 6} |
| 196 | + limit={6}> |
| 197 | + {#snippet children( |
| 198 | + paginatedItems: Models.ProviderRepositoryRuntime[] & |
| 199 | + Models.ProviderRepositoryFramework[] |
| 200 | + )} |
| 201 | + <Table.Root columns={1} let:root> |
| 202 | + {#each paginatedItems as repo} |
| 203 | + <Table.Row.Base {root}> |
| 204 | + <Table.Cell {root}> |
| 205 | + <Layout.Stack direction="row" alignItems="center" gap="s"> |
| 206 | + {#if action === 'select'} |
| 207 | + <input |
| 208 | + class="is-small u-margin-inline-end-8" |
| 209 | + type="radio" |
| 210 | + name="repositories" |
| 211 | + bind:group={selectedRepository} |
| 212 | + onchange={() => repository.set(repo)} |
| 213 | + value={repo.id} /> |
| 214 | + {/if} |
| 215 | + {#if product === 'sites'} |
| 216 | + {#if repo?.framework && repo.framework !== 'other'} |
| 217 | + <Avatar size="xs" alt={repo.name}> |
| 218 | + <SvgIcon |
| 219 | + name={getFrameworkIcon(repo.framework)} |
| 220 | + iconSize="small" /> |
| 221 | + </Avatar> |
| 222 | + {:else} |
| 223 | + <Avatar size="xs" alt={repo.name} empty /> |
| 224 | + {/if} |
| 225 | + {:else} |
| 226 | + {@const iconName = repo?.runtime |
| 227 | + ? repo.runtime.split('-')[0] |
| 228 | + : undefined} |
| 229 | + <Avatar size="xs" alt={repo.name} empty={!iconName}> |
| 230 | + <SvgIcon name={iconName} iconSize="small" /> |
| 231 | + </Avatar> |
| 232 | + {/if} |
| 233 | + <Layout.Stack |
| 234 | + gap="s" |
| 235 | + direction="row" |
| 236 | + alignItems="center" |
| 237 | + justifyContent="space-between"> |
202 | 238 | <Layout.Stack
|
203 | 239 | direction="row"
|
204 |
| - alignItems="center" |
205 |
| - gap="s"> |
206 |
| - {#if action === 'select'} |
207 |
| - <input |
208 |
| - class="is-small u-margin-inline-end-8" |
209 |
| - type="radio" |
210 |
| - name="repositories" |
211 |
| - bind:group={selectedRepository} |
212 |
| - onchange={() => repository.set(repo)} |
213 |
| - value={repo.id} /> |
214 |
| - {/if} |
215 |
| - {#if product === 'sites'} |
216 |
| - {#if repo?.framework && repo.framework !== 'other'} |
217 |
| - <Avatar size="xs" alt={repo.name}> |
218 |
| - <SvgIcon |
219 |
| - name={getFrameworkIcon( |
220 |
| - repo.framework |
221 |
| - )} |
222 |
| - iconSize="small" /> |
223 |
| - </Avatar> |
224 |
| - {:else} |
225 |
| - <Avatar |
226 |
| - size="xs" |
227 |
| - alt={repo.name} |
228 |
| - empty /> |
229 |
| - {/if} |
230 |
| - {:else} |
231 |
| - {@const iconName = repo?.runtime |
232 |
| - ? repo.runtime.split('-')[0] |
233 |
| - : undefined} |
234 |
| - <Avatar |
235 |
| - size="xs" |
236 |
| - alt={repo.name} |
237 |
| - empty={!iconName}> |
238 |
| - <SvgIcon |
239 |
| - name={iconName} |
240 |
| - iconSize="small" /> |
241 |
| - </Avatar> |
| 240 | + gap="s" |
| 241 | + alignItems="center"> |
| 242 | + <Typography.Text |
| 243 | + truncate |
| 244 | + color="--fgcolor-neutral-secondary"> |
| 245 | + {repo.name} |
| 246 | + </Typography.Text> |
| 247 | + {#if repo.private} |
| 248 | + <Icon |
| 249 | + size="s" |
| 250 | + icon={IconLockClosed} |
| 251 | + color="--fgcolor-neutral-tertiary" /> |
242 | 252 | {/if}
|
243 |
| - <Layout.Stack |
244 |
| - gap="s" |
245 |
| - direction="row" |
246 |
| - alignItems="center" |
247 |
| - justifyContent="space-between"> |
248 |
| - <Layout.Stack |
249 |
| - direction="row" |
250 |
| - gap="s" |
251 |
| - alignItems="center"> |
252 |
| - <Typography.Text |
| 253 | + {#if !$isSmallViewport} |
| 254 | + <time datetime={repo.pushedAt}> |
| 255 | + <Typography.Caption |
| 256 | + variant="400" |
253 | 257 | truncate
|
254 |
| - color="--fgcolor-neutral-secondary"> |
255 |
| - {repo.name} |
256 |
| - </Typography.Text> |
257 |
| - {#if repo.private} |
258 |
| - <Icon |
259 |
| - size="s" |
260 |
| - icon={IconLockClosed} |
261 |
| - color="--fgcolor-neutral-tertiary" /> |
262 |
| - {/if} |
263 |
| - {#if !$isSmallViewport} |
264 |
| - <time datetime={repo.pushedAt}> |
265 |
| - <Typography.Caption |
266 |
| - variant="400" |
267 |
| - truncate |
268 |
| - color="--fgcolor-neutral-tertiary"> |
269 |
| - {timeFromNow(repo.pushedAt)} |
270 |
| - </Typography.Caption> |
271 |
| - </time> |
272 |
| - {/if} |
273 |
| - </Layout.Stack> |
274 |
| - {#if action === 'button'} |
275 |
| - <PinkButton.Button |
276 |
| - size="xs" |
277 |
| - variant="secondary" |
278 |
| - on:click={() => connect(repo)}> |
279 |
| - Connect |
280 |
| - </PinkButton.Button> |
281 |
| - {/if} |
282 |
| - </Layout.Stack> |
| 258 | + color="--fgcolor-neutral-tertiary"> |
| 259 | + {timeFromNow(repo.pushedAt)} |
| 260 | + </Typography.Caption> |
| 261 | + </time> |
| 262 | + {/if} |
283 | 263 | </Layout.Stack>
|
284 |
| - </Table.Cell> |
285 |
| - </Table.Row.Base> |
286 |
| - {/each} |
287 |
| - </Table.Root> |
288 |
| - {/snippet} |
289 |
| - </Paginator> |
290 |
| - {:else if search} |
291 |
| - <EmptySearch hidePages hidePagination bind:search target="repositories"> |
292 |
| - <svelte:fragment slot="actions"> |
293 |
| - {#if search} |
294 |
| - <Button secondary on:click={() => (search = '')}> |
295 |
| - Clear search |
296 |
| - </Button> |
297 |
| - {/if} |
298 |
| - </svelte:fragment> |
299 |
| - </EmptySearch> |
300 |
| - {:else} |
301 |
| - <Card> |
302 |
| - <Layout.Stack alignItems="center" justifyContent="center"> |
303 |
| - <Typography.Text |
304 |
| - variation="m-500" |
305 |
| - color="--fgcolor-neutral-tertiary"> |
306 |
| - No repositories available |
307 |
| - </Typography.Text> |
308 |
| - </Layout.Stack> |
309 |
| - </Card> |
310 |
| - {/if} |
311 |
| - {/await} |
| 264 | + {#if action === 'button'} |
| 265 | + <PinkButton.Button |
| 266 | + size="xs" |
| 267 | + variant="secondary" |
| 268 | + on:click={() => connect(repo)}> |
| 269 | + Connect |
| 270 | + </PinkButton.Button> |
| 271 | + {/if} |
| 272 | + </Layout.Stack> |
| 273 | + </Layout.Stack> |
| 274 | + </Table.Cell> |
| 275 | + </Table.Row.Base> |
| 276 | + {/each} |
| 277 | + </Table.Root> |
| 278 | + {/snippet} |
| 279 | + </Paginator> |
| 280 | + {:else if search} |
| 281 | + <EmptySearch hidePages hidePagination bind:search target="repositories"> |
| 282 | + <svelte:fragment slot="actions"> |
| 283 | + {#if search} |
| 284 | + <Button secondary on:click={() => (search = '')}>Clear search</Button> |
| 285 | + {/if} |
| 286 | + </svelte:fragment> |
| 287 | + </EmptySearch> |
| 288 | + {:else} |
| 289 | + <Card> |
| 290 | + <Layout.Stack alignItems="center" justifyContent="center"> |
| 291 | + <Typography.Text variation="m-500" color="--fgcolor-neutral-tertiary"> |
| 292 | + No repositories available |
| 293 | + </Typography.Text> |
| 294 | + </Layout.Stack> |
| 295 | + </Card> |
312 | 296 | {/if}
|
313 | 297 | {/if}
|
314 | 298 | </Layout.Stack>
|
|
0 commit comments