|
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 | 240 | alignItems="center"
|
|
279 | 315 | on:click={() => connect(repo)}>
|
280 | 316 | Connect
|
281 | 317 | </PinkButton.Button>
|
| 318 | + gap="s" |
| 319 | + alignItems="center"> |
| 320 | + <Typography.Text |
| 321 | + truncate |
| 322 | + color="--fgcolor-neutral-secondary"> |
| 323 | + {repo.name} |
| 324 | + </Typography.Text> |
| 325 | + {#if repo.private} |
| 326 | + <Icon |
| 327 | + size="s" |
| 328 | + icon={IconLockClosed} |
| 329 | + color="--fgcolor-neutral-tertiary" /> |
| 330 | + {/if} |
| 331 | + {#if !$isSmallViewport} |
| 332 | + <time datetime={repo.pushedAt}> |
| 333 | + <Typography.Caption |
| 334 | + variant="400" |
| 335 | + truncate |
| 336 | + color="--fgcolor-neutral-tertiary"> |
| 337 | + {timeFromNow(repo.pushedAt)} |
| 338 | + </Typography.Caption> |
| 339 | + </time> |
282 | 340 | {/if}
|
283 | 341 | </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} |
| 342 | + {#if action === 'button'} |
| 343 | + <PinkButton.Button |
| 344 | + size="xs" |
| 345 | + variant="secondary" |
| 346 | + on:click={() => connect(repo)}> |
| 347 | + Connect |
| 348 | + </PinkButton.Button> |
| 349 | + {/if} |
| 350 | + </Layout.Stack> |
| 351 | + </Layout.Stack> |
| 352 | + </Table.Cell> |
| 353 | + </Table.Row.Base> |
| 354 | + {/each} |
| 355 | + </Table.Root> |
| 356 | + {/snippet} |
| 357 | + </Paginator> |
| 358 | + {:else if search} |
| 359 | + <EmptySearch hidePages hidePagination bind:search target="repositories"> |
| 360 | + <svelte:fragment slot="actions"> |
| 361 | + {#if search} |
| 362 | + <Button secondary on:click={() => (search = '')}>Clear search</Button> |
| 363 | + {/if} |
| 364 | + </svelte:fragment> |
| 365 | + </EmptySearch> |
| 366 | + {:else} |
| 367 | + <Card> |
| 368 | + <Layout.Stack alignItems="center" justifyContent="center"> |
| 369 | + <Typography.Text variation="m-500" color="--fgcolor-neutral-tertiary"> |
| 370 | + No repositories available |
| 371 | + </Typography.Text> |
| 372 | + </Layout.Stack> |
| 373 | + </Card> |
312 | 374 | {/if}
|
313 | 375 | {/if}
|
314 | 376 | </Layout.Stack>
|
|
0 commit comments