From c3fb40262d267f9077aefc03186234179a25a1c1 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com> Date: Mon, 6 Oct 2025 23:34:18 +0530 Subject: [PATCH 01/10] fix: Buggy root directory Modal --- src/lib/components/git/selectRootModal.svelte | 68 +++++++++++++++++-- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/src/lib/components/git/selectRootModal.svelte b/src/lib/components/git/selectRootModal.svelte index bc817623e6..65ebd6940e 100644 --- a/src/lib/components/git/selectRootModal.svelte +++ b/src/lib/components/git/selectRootModal.svelte @@ -37,7 +37,10 @@ ]; let currentPath: string = './'; let currentDir: Directory; - export let expanded = writable(['lib-0', 'tree-0']); + export let expanded = writable([]); + let initialized = false; + let initialPath: string = './'; + let pathNotFound = false; onMount(async () => { try { @@ -61,13 +64,13 @@ })); currentDir = directories[0]; isLoading = false; + $expanded = Array.from(new Set([...$expanded, './'])); } catch { return; } }); - async function fetchContents(e: CustomEvent) { - const path = e.detail.fullPath as string; + async function loadPath(path: string) { currentPath = path; const pathSegments = path.split('/').filter((segment) => segment !== '.' && segment !== ''); @@ -99,6 +102,7 @@ const contentDirectories = content.contents.filter((e) => e.isDirectory); if (contentDirectories.length === 0) { + $expanded = Array.from(new Set([...$expanded, path])); return; } @@ -133,7 +137,7 @@ }); } directories = [...directories]; - $expanded = [...$expanded, path]; + $expanded = Array.from(new Set([...$expanded, path])); } catch (error) { console.error(error); } finally { @@ -142,6 +146,60 @@ } } + async function fetchContents(e: CustomEvent) { + const path = e.detail.fullPath as string; + await loadPath(path); + } + + function normalizePath(path: string): string { + if (!path || path === '') return './'; + if (path === './') return './'; + if (path.startsWith('./')) return path.replace(/\/$/, ''); + if (path.startsWith('/')) return '.' + path.replace(/\/$/, ''); + return './' + path.replace(/\/$/, ''); + } + + async function expandToPath(path: string) { + pathNotFound = false; + const normalized = normalizePath(path); + const segments = normalized.split('/').filter((s) => s !== '.' && s !== ''); + let cumulative = './'; + $expanded = Array.from(new Set([...$expanded, './'])); + for (const segment of segments) { + cumulative = cumulative === './' ? `./${segment}` : `${cumulative}/${segment}`; + const parentSegments = cumulative.split('/').filter((s) => s !== '.' && s !== ''); + let cursor = directories[0]; + for (const s of parentSegments.slice(0, -1)) { + const next = cursor.children?.find((d) => d.title === s); + if (!next) { + pathNotFound = true; + return; + } + cursor = next; + } + await loadPath(cursor.fullPath ?? './'); + const exists = cursor.children?.some((d) => d.title === segment); + if (!exists) { + pathNotFound = true; + return; + } + $expanded = Array.from(new Set([...$expanded, cumulative])); + } + currentPath = normalized; + } + + $: if (show && !initialized && !isLoading) { + initialized = true; + initialPath = normalizePath(rootDir ?? './'); + currentPath = initialPath; + expandToPath(initialPath); + } + + $: if (!show && initialized) { + initialized = false; + pathNotFound = false; + } + function handleSubmit() { rootDir = currentPath; show = false; @@ -156,6 +214,6 @@ - + From 45e9a40583c88031dbefa165d210242870c7f40f Mon Sep 17 00:00:00 2001 From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com> Date: Tue, 7 Oct 2025 00:52:05 +0530 Subject: [PATCH 02/10] some fixes --- package.json | 4 +- pnpm-lock.yaml | 20 +- src/lib/components/git/selectRootModal.svelte | 311 ++++++++++-------- 3 files changed, 188 insertions(+), 147 deletions(-) diff --git a/package.json b/package.json index 4968186968..f23d028033 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,9 @@ "@ai-sdk/svelte": "^1.1.24", "@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@636ed39", "@appwrite.io/pink-icons": "0.25.0", - "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@8f82877", + "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@aa2ed6e", "@appwrite.io/pink-legacy": "^1.0.3", - "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8f82877", + "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@aa2ed6e", "@faker-js/faker": "^9.9.0", "@popperjs/core": "^2.11.8", "@sentry/sveltekit": "^8.38.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 827befa274..08b9db4e20 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,14 +18,14 @@ importers: specifier: 0.25.0 version: 0.25.0 '@appwrite.io/pink-icons-svelte': - specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@8f82877 - version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@8f82877(svelte@5.25.3) + specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@aa2ed6e + version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@aa2ed6e(svelte@5.25.3) '@appwrite.io/pink-legacy': specifier: ^1.0.3 version: 1.0.3 '@appwrite.io/pink-svelte': - specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8f82877 - version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8f82877(svelte@5.25.3) + specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@aa2ed6e + version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@aa2ed6e(svelte@5.25.3) '@faker-js/faker': specifier: ^9.9.0 version: 9.9.0 @@ -269,8 +269,8 @@ packages: peerDependencies: svelte: ^4.0.0 - '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@8f82877': - resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@8f82877} + '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@aa2ed6e': + resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@aa2ed6e} version: 2.0.0-RC.1 peerDependencies: svelte: ^4.0.0 @@ -284,8 +284,8 @@ packages: '@appwrite.io/pink-legacy@1.0.3': resolution: {integrity: sha512-GGde5fmPhs+s6/3aFeMPc/kKADG/gTFkYQSy6oBN8pK0y0XNCLrZZgBv+EBbdhwdtqVEWXa0X85Mv9w7jcIlwQ==} - '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8f82877': - resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8f82877} + '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@aa2ed6e': + resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@aa2ed6e} version: 2.0.0-RC.2 peerDependencies: svelte: ^4.0.0 @@ -3709,7 +3709,7 @@ snapshots: dependencies: svelte: 5.25.3 - '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@8f82877(svelte@5.25.3)': + '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@aa2ed6e(svelte@5.25.3)': dependencies: svelte: 5.25.3 @@ -3722,7 +3722,7 @@ snapshots: '@appwrite.io/pink-icons': 1.0.0 the-new-css-reset: 1.11.3 - '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8f82877(svelte@5.25.3)': + '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@aa2ed6e(svelte@5.25.3)': dependencies: '@appwrite.io/pink-icons-svelte': 2.0.0-RC.1(svelte@5.25.3) '@floating-ui/dom': 1.6.13 diff --git a/src/lib/components/git/selectRootModal.svelte b/src/lib/components/git/selectRootModal.svelte index 65ebd6940e..f50122caf5 100644 --- a/src/lib/components/git/selectRootModal.svelte +++ b/src/lib/components/git/selectRootModal.svelte @@ -7,25 +7,31 @@ import { installation, repository } from '$lib/stores/vcs'; import { VCSDetectionType, type Models } from '@appwrite.io/console'; import { DirectoryPicker } from '@appwrite.io/pink-svelte'; - import { onMount } from 'svelte'; import { writable } from 'svelte/store'; type Directory = { title: string; fullPath: string; - fileCount: number; - thumbnailUrl: string; + fileCount?: number; + thumbnailUrl?: string; children?: Directory[]; loading?: boolean; }; - export let show = false; - export let rootDir: string; - export let product: 'sites' | 'functions' = 'functions'; - export let branch: string; + let { + show = $bindable(false), + rootDir = $bindable(''), + product = 'functions' as 'sites' | 'functions', + branch + }: { + show?: boolean; + rootDir?: string; + product?: 'sites' | 'functions'; + branch: string; + } = $props(); - let isLoading = true; - let directories: Directory[] = [ + let isLoading = $state(true); + let directories = $state([ { title: 'Root', fullPath: './', @@ -34,170 +40,199 @@ children: [], loading: false } - ]; - let currentPath: string = './'; - let currentDir: Directory; - export let expanded = writable([]); - let initialized = false; - let initialPath: string = './'; - let pathNotFound = false; - - onMount(async () => { + ]); + let currentPath = $state('./'); + let expandedStore = writable([]); + let initialized = $state(false); + let initialPath = $state('./'); + let isFetching = false; + + let hasChanges = $derived(currentPath !== initialPath); + + async function detectRuntimeOrFramework(path: string): Promise { try { - const content = await sdk + const detection = await sdk .forProject(page.params.region, page.params.project) - .vcs.getRepositoryContents({ + .vcs.createRepositoryDetection({ installationId: $installation.$id, providerRepositoryId: $repository.id, - providerRootDirectory: currentPath, - providerReference: branch + type: + product === 'sites' ? VCSDetectionType.Framework : VCSDetectionType.Runtime, + providerRootDirectory: path }); - directories[0].fileCount = content.contents?.length ?? 0; - directories[0].children = content.contents - .filter((e) => e.isDirectory) - .map((dir) => ({ - title: dir.name, - fullPath: currentPath + dir.name, - fileCount: undefined, - thumbnailUrl: dir.name, - loading: false - })); - currentDir = directories[0]; - isLoading = false; - $expanded = Array.from(new Set([...$expanded, './'])); - } catch { - return; - } - }); - async function loadPath(path: string) { - currentPath = path; - - const pathSegments = path.split('/').filter((segment) => segment !== '.' && segment !== ''); - let traversedDir = directories[0]; // Start at root - - for (const segment of pathSegments) { - const nextDir = traversedDir.children?.find((dir) => dir.title === segment); - if (!nextDir) break; - traversedDir = nextDir; + const iconName = + product === 'sites' + ? detection.framework + : (detection as unknown as Models.DetectionRuntime).runtime; + return $iconPath(iconName, 'color'); + } catch (err) { + return null; } + } - currentDir = traversedDir; - - if (!currentDir.fileCount) { - currentDir.loading = true; - directories = [...directories]; + $effect(() => { + if (!isLoading) return; + (async () => { try { const content = await sdk .forProject(page.params.region, page.params.project) .vcs.getRepositoryContents({ installationId: $installation.$id, providerRepositoryId: $repository.id, - providerRootDirectory: path, + providerRootDirectory: './', providerReference: branch }); - const fileCount = content.contents?.length ?? 0; - const contentDirectories = content.contents.filter((e) => e.isDirectory); + directories[0] = { + ...directories[0], + fileCount: content.contents?.length ?? 0, + children: content.contents + .filter((e) => e.isDirectory) + .map((dir) => ({ + title: dir.name, + fullPath: `./${dir.name}`, + fileCount: undefined, + // set logo for root directories + thumbnailUrl: dir.name, + loading: false + })) + }; - if (contentDirectories.length === 0) { - $expanded = Array.from(new Set([...$expanded, path])); - return; + const detectedIcon = await detectRuntimeOrFramework('./'); + if (detectedIcon) { + directories[0].thumbnailUrl = detectedIcon; } - currentDir.fileCount = fileCount; - currentDir.children = contentDirectories.map((dir) => ({ - title: dir.name, - fullPath: path + '/' + dir.name, - fileCount: undefined, - thumbnailUrl: undefined - })); - const runtime = await sdk - .forProject(page.params.region, page.params.project) - .vcs.createRepositoryDetection({ - installationId: $installation.$id, - providerRepositoryId: $repository.id, - type: - product === 'sites' - ? VCSDetectionType.Framework - : VCSDetectionType.Runtime, - providerRootDirectory: path - }); - if (product === 'sites') { - currentDir.children.forEach((dir) => { - dir.thumbnailUrl = $iconPath(runtime.framework, 'color'); - }); - } else if (product === 'functions') { - currentDir.children.forEach((dir) => { - dir.thumbnailUrl = $iconPath( - (runtime as unknown as Models.DetectionRuntime).runtime, - 'color' - ); - }); - } - directories = [...directories]; - $expanded = Array.from(new Set([...$expanded, path])); + isLoading = false; + expandedStore.update((exp) => [...exp, './']); } catch (error) { - console.error(error); - } finally { - currentDir.loading = false; + console.error('Failed to load root directory:', error); + isLoading = false; } + })(); + }); + + function getDirByPath(path: string): Directory | null { + const segments = path.split('/').filter((s) => s !== '.' && s !== ''); + let node: Directory | null = directories[0] ?? null; + for (const seg of segments) { + const next = node?.children?.find((d) => d.title === seg) ?? null; + if (!next) return null; + node = next; } + return node; } - async function fetchContents(e: CustomEvent) { - const path = e.detail.fullPath as string; - await loadPath(path); + async function loadPath(path: string) { + // skip loading if this directory was donee + const targetDir = getDirByPath(path); + if (!targetDir || targetDir.fileCount !== undefined) return; + + if (isFetching) return; + isFetching = true; + targetDir.loading = true; + + try { + const content = await sdk + .forProject(page.params.region, page.params.project) + .vcs.getRepositoryContents({ + installationId: $installation.$id, + providerRepositoryId: $repository.id, + providerRootDirectory: path, + providerReference: branch + }); + + const fileCount = content.contents?.length ?? 0; + const contentDirectories = content.contents.filter((e) => e.isDirectory); + + if (contentDirectories.length === 0) { + expandedStore.update((exp) => [...new Set([...exp, path])]); + return; + } + + targetDir.fileCount = fileCount; + + // set logo only for the current folder, not for the children + const detectedIcon = await detectRuntimeOrFramework(path); + if (detectedIcon) { + targetDir.thumbnailUrl = detectedIcon; + } + + targetDir.children = contentDirectories.map((dir) => { + return { + title: dir.name, + fullPath: `${path}/${dir.name}`, + fileCount: undefined, + thumbnailUrl: dir.name + }; + }); + + expandedStore.update((exp) => [...new Set([...exp, path])]); + } catch (error) { + console.error('Failed to load directory:', error); + } finally { + targetDir.loading = false; + isFetching = false; + } } function normalizePath(path: string): string { - if (!path || path === '') return './'; - if (path === './') return './'; - if (path.startsWith('./')) return path.replace(/\/$/, ''); - if (path.startsWith('/')) return '.' + path.replace(/\/$/, ''); - return './' + path.replace(/\/$/, ''); + if (!path || path === './') return './'; + const trimmed = path.replace(/\/$/, ''); + return trimmed.startsWith('./') ? trimmed : `./${trimmed}`; } async function expandToPath(path: string) { - pathNotFound = false; const normalized = normalizePath(path); const segments = normalized.split('/').filter((s) => s !== '.' && s !== ''); - let cumulative = './'; - $expanded = Array.from(new Set([...$expanded, './'])); + + expandedStore.update((exp) => [...new Set([...exp, './'])]); + + let currentDir = directories[0]; + let currentPath = './'; + for (const segment of segments) { - cumulative = cumulative === './' ? `./${segment}` : `${cumulative}/${segment}`; - const parentSegments = cumulative.split('/').filter((s) => s !== '.' && s !== ''); - let cursor = directories[0]; - for (const s of parentSegments.slice(0, -1)) { - const next = cursor.children?.find((d) => d.title === s); - if (!next) { - pathNotFound = true; - return; - } - cursor = next; - } - await loadPath(cursor.fullPath ?? './'); - const exists = cursor.children?.some((d) => d.title === segment); - if (!exists) { - pathNotFound = true; - return; - } - $expanded = Array.from(new Set([...$expanded, cumulative])); + currentPath = currentPath === './' ? `./${segment}` : `${currentPath}/${segment}`; + + // Load the parent directory if not already loaded + await loadPath(currentDir.fullPath); + + // Find the next directory + const nextDir = currentDir.children?.find((d) => d.title === segment); + if (!nextDir) return; // Path doesn't exist + + currentDir = nextDir; + expandedStore.update((exp) => [...new Set([...exp, currentPath])]); } + currentPath = normalized; } - $: if (show && !initialized && !isLoading) { - initialized = true; - initialPath = normalizePath(rootDir ?? './'); - currentPath = initialPath; - expandToPath(initialPath); - } + $effect(() => { + if (show && !initialized && !isLoading) { + initialized = true; + const normalized = normalizePath(rootDir || './'); + initialPath = normalized; + currentPath = normalized; + expandToPath(normalized); + } + }); + + // reset state when modal closes + $effect(() => { + if (!show && initialized) { + initialized = false; + } + }); - $: if (!show && initialized) { - initialized = false; - pathNotFound = false; + async function handleSelect(e: CustomEvent) { + const path = e.detail.fullPath as string; + if (isFetching) return; + + currentPath = path; + await loadPath(path); } function handleSubmit() { @@ -210,10 +245,16 @@ Select the directory where your site code is located using the menu below. - + - + From 16f9f46db6eb35046204384fb4f89b0b7699c683 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Mon, 16 Feb 2026 13:40:47 +0530 Subject: [PATCH 03/10] fix: lag --- src/lib/components/git/selectRootModal.svelte | 134 +++++++++++------- 1 file changed, 81 insertions(+), 53 deletions(-) diff --git a/src/lib/components/git/selectRootModal.svelte b/src/lib/components/git/selectRootModal.svelte index 27af86d7a4..51d468be67 100644 --- a/src/lib/components/git/selectRootModal.svelte +++ b/src/lib/components/git/selectRootModal.svelte @@ -34,21 +34,38 @@ let directories = $state([ { title: 'Root', - fullPath: './', + fullPath: '/', fileCount: 0, - thumbnailUrl: 'root', + thumbnailUrl: $iconPath('empty', 'grayscale'), children: [], loading: false } ]); - let currentPath = $state('./'); + let currentPath = $state('/'); let expandedStore = writable([]); let initialized = $state(false); - let initialPath = $state('./'); - let isFetching = false; + let treeVersion = $state(0); + let initialPath = $state('/'); + const inFlightPaths = new Set(); let hasChanges = $derived(currentPath !== initialPath); + function normalizePath(path: string): string { + if (!path || path === './' || path === '/') return '/'; + const trimmed = path.replace(/^\.\//, '').replace(/^\/+/, '').replace(/\/$/, ''); + return `/${trimmed}`; + } + + function toProviderPath(path: string): string { + const normalized = normalizePath(path); + if (normalized === '/') return './'; + return `./${normalized.slice(1)}`; + } + + function bumpTreeVersion() { + treeVersion += 1; + } + async function detectRuntimeOrFramework(path: string): Promise { try { const detection = await sdk @@ -58,14 +75,14 @@ providerRepositoryId: $repository.id, type: product === 'sites' ? VCSDetectionType.Framework : VCSDetectionType.Runtime, - providerRootDirectory: path + providerRootDirectory: toProviderPath(path) }); const iconName = product === 'sites' ? detection.framework : (detection as unknown as Models.DetectionRuntime).runtime; - return $iconPath(iconName, 'color'); + return iconName ? $iconPath(iconName, 'color') : null; } catch (err) { return null; } @@ -92,21 +109,22 @@ .filter((e) => e.isDirectory) .map((dir) => ({ title: dir.name, - fullPath: `./${dir.name}`, + fullPath: `/${dir.name}`, fileCount: undefined, // set logo for root directories - thumbnailUrl: dir.name, + thumbnailUrl: $iconPath('empty', 'grayscale'), loading: false })) }; - const detectedIcon = await detectRuntimeOrFramework('./'); + const detectedIcon = await detectRuntimeOrFramework('/'); if (detectedIcon) { directories[0].thumbnailUrl = detectedIcon; } isLoading = false; - expandedStore.update((exp) => [...exp, './']); + expandedStore.update((exp) => [...new Set([...exp, '/'])]); + bumpTreeVersion(); } catch (error) { console.error('Failed to load root directory:', error); isLoading = false; @@ -115,7 +133,7 @@ }); function getDirByPath(path: string): Directory | null { - const segments = path.split('/').filter((s) => s !== '.' && s !== ''); + const segments = path.split('/').filter((s) => s !== ''); let node: Directory | null = directories[0] ?? null; for (const seg of segments) { const next = node?.children?.find((d) => d.title === seg) ?? null; @@ -130,8 +148,8 @@ const targetDir = getDirByPath(path); if (!targetDir || targetDir.fileCount !== undefined) return; - if (isFetching) return; - isFetching = true; + if (inFlightPaths.has(path)) return; + inFlightPaths.add(path); targetDir.loading = true; try { @@ -140,7 +158,7 @@ .vcs.getRepositoryContents({ installationId: $installation.$id, providerRepositoryId: $repository.id, - providerRootDirectory: path, + providerRootDirectory: toProviderPath(path), providerReference: branch }); @@ -160,51 +178,61 @@ targetDir.thumbnailUrl = detectedIcon; } - targetDir.children = contentDirectories.map((dir) => { - return { - title: dir.name, - fullPath: `${path}/${dir.name}`, - fileCount: undefined, - thumbnailUrl: dir.name - }; - }); + const nextChildren = contentDirectories.map((dir) => ({ + title: dir.name, + fullPath: path === '/' ? `/${dir.name}` : `${path}/${dir.name}`, + fileCount: undefined, + thumbnailUrl: $iconPath('empty', 'grayscale') + })); + targetDir.children = nextChildren; + bumpTreeVersion(); expandedStore.update((exp) => [...new Set([...exp, path])]); } catch (error) { console.error('Failed to load directory:', error); } finally { targetDir.loading = false; - isFetching = false; + inFlightPaths.delete(path); } } - function normalizePath(path: string): string { - if (!path || path === './') return './'; - const trimmed = path.replace(/\/$/, ''); - return trimmed.startsWith('./') ? trimmed : `./${trimmed}`; - } - async function expandToPath(path: string) { const normalized = normalizePath(path); - const segments = normalized.split('/').filter((s) => s !== '.' && s !== ''); + const segments = normalized.split('/').filter((s) => s !== ''); - expandedStore.update((exp) => [...new Set([...exp, './'])]); + const pathsToExpand = ['/']; let currentDir = directories[0]; - let currentPath = './'; + let currentPath = '/'; for (const segment of segments) { - currentPath = currentPath === './' ? `./${segment}` : `${currentPath}/${segment}`; + currentPath = currentPath === '/' ? `/${segment}` : `${currentPath}/${segment}`; + pathsToExpand.push(currentPath); - // Load the parent directory if not already loaded - await loadPath(currentDir.fullPath); + if (!currentDir.children) { + currentDir.children = []; + } - // Find the next directory - const nextDir = currentDir.children?.find((d) => d.title === segment); - if (!nextDir) return; // Path doesn't exist + let nextDir = currentDir.children.find((d) => d.title === segment); + if (!nextDir) { + nextDir = { + title: segment, + fullPath: currentPath, + fileCount: undefined, + thumbnailUrl: $iconPath('empty', 'grayscale'), + children: [] + }; + currentDir.children = [...currentDir.children, nextDir]; + } currentDir = nextDir; - expandedStore.update((exp) => [...new Set([...exp, currentPath])]); + } + + expandedStore.update((exp) => [...new Set([...exp, ...pathsToExpand])]); + bumpTreeVersion(); + + for (const pathToLoad of pathsToExpand) { + loadPath(pathToLoad); } currentPath = normalized; @@ -213,7 +241,7 @@ $effect(() => { if (show && !initialized && !isLoading) { initialized = true; - const normalized = normalizePath(rootDir || './'); + const normalized = normalizePath(rootDir || '/'); initialPath = normalized; currentPath = normalized; expandToPath(normalized); @@ -227,12 +255,10 @@ } }); - async function handleSelect(e: CustomEvent) { - const path = e.detail.fullPath as string; - if (isFetching) return; - + async function handleSelect(detail: { fullPath: string }) { + const path = detail.fullPath as string; currentPath = path; - await loadPath(path); + loadPath(path); } function handleSubmit() { @@ -245,13 +271,15 @@ Select the directory where your site code is located using the menu below. - + {#key treeVersion} + + {/key} From 0181088b5aa724c7e8a0dfa1f583f62d97825490 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Mon, 16 Feb 2026 14:47:50 +0530 Subject: [PATCH 04/10] svelte icon fix --- src/lib/components/git/selectRootModal.svelte | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/lib/components/git/selectRootModal.svelte b/src/lib/components/git/selectRootModal.svelte index 51d468be67..57725da095 100644 --- a/src/lib/components/git/selectRootModal.svelte +++ b/src/lib/components/git/selectRootModal.svelte @@ -50,6 +50,14 @@ let hasChanges = $derived(currentPath !== initialPath); + const iconAliases = new Map([ + ['svelte-kit', 'svelte'], + ['sveltekit', 'svelte'], + ['svelte_kit', 'svelte'], + ['sveltejs', 'svelte'], + ['other', 'empty'] + ]); + function normalizePath(path: string): string { if (!path || path === './' || path === '/') return '/'; const trimmed = path.replace(/^\.\//, '').replace(/^\/+/, '').replace(/\/$/, ''); @@ -66,6 +74,13 @@ treeVersion += 1; } + function resolveIconUrl(rawIconName: string | null | undefined): string | null { + if (!rawIconName) return null; + const normalized = rawIconName.toLowerCase(); + const iconName = iconAliases.get(normalized) ?? normalized; + return $iconPath(iconName, 'color'); + } + async function detectRuntimeOrFramework(path: string): Promise { try { const detection = await sdk @@ -82,7 +97,7 @@ product === 'sites' ? detection.framework : (detection as unknown as Models.DetectionRuntime).runtime; - return iconName ? $iconPath(iconName, 'color') : null; + return resolveIconUrl(iconName); } catch (err) { return null; } From 7f31579759f8720958af8ed92122844766ab5685 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Thu, 19 Feb 2026 18:44:27 +0530 Subject: [PATCH 05/10] some more chnages --- src/lib/components/git/DirectoryItem.svelte | 245 ++++++++++++++++++ src/lib/components/git/DirectoryPicker.svelte | 116 +++++++++ src/lib/components/git/selectRootModal.svelte | 178 ++++++++----- 3 files changed, 480 insertions(+), 59 deletions(-) create mode 100644 src/lib/components/git/DirectoryItem.svelte create mode 100644 src/lib/components/git/DirectoryPicker.svelte diff --git a/src/lib/components/git/DirectoryItem.svelte b/src/lib/components/git/DirectoryItem.svelte new file mode 100644 index 0000000000..68512940a4 --- /dev/null +++ b/src/lib/components/git/DirectoryItem.svelte @@ -0,0 +1,245 @@ + + +{#each directories as { title, fileCount, fullPath, thumbnailUrl, thumbnailIcon, thumbnailHtml, children, hasChildren: explicitHasChildren, showThumbnail = true, loading = false }, i} + {@const hasChildren = explicitHasChildren ?? !!children?.length} + {@const __MELTUI_BUILDER_1__ = $group({ id: fullPath })} + {@const __MELTUI_BUILDER_0__ = $item({ + id: fullPath, + hasChildren + })} + +
+ + + {#if children} +
+ +
+ {/if} +
+{/each} + + diff --git a/src/lib/components/git/DirectoryPicker.svelte b/src/lib/components/git/DirectoryPicker.svelte new file mode 100644 index 0000000000..bbc5bced92 --- /dev/null +++ b/src/lib/components/git/DirectoryPicker.svelte @@ -0,0 +1,116 @@ + + + + +
+ {#if isLoading} +
+ Loading directory data... +
+ {:else} + + {/if} +
+ + diff --git a/src/lib/components/git/selectRootModal.svelte b/src/lib/components/git/selectRootModal.svelte index 57725da095..bd2ee17fdd 100644 --- a/src/lib/components/git/selectRootModal.svelte +++ b/src/lib/components/git/selectRootModal.svelte @@ -6,7 +6,7 @@ import { sdk } from '$lib/stores/sdk'; import { installation, repository } from '$lib/stores/vcs'; import { VCSDetectionType, type Models } from '@appwrite.io/console'; - import { DirectoryPicker } from '@appwrite.io/pink-svelte'; + import DirectoryPicker from '$lib/components/git/DirectoryPicker.svelte'; import { writable } from 'svelte/store'; type Directory = { @@ -15,6 +15,7 @@ fileCount?: number; thumbnailUrl?: string; children?: Directory[]; + hasChildren?: boolean; loading?: boolean; }; @@ -33,20 +34,24 @@ let isLoading = $state(true); let directories = $state([ { - title: 'Root', + title: 'Repository (root)', fullPath: '/', - fileCount: 0, + fileCount: undefined, thumbnailUrl: $iconPath('empty', 'grayscale'), children: [], + hasChildren: true, loading: false } ]); let currentPath = $state('/'); let expandedStore = writable([]); let initialized = $state(false); - let treeVersion = $state(0); let initialPath = $state('/'); const inFlightPaths = new Set(); + const contentsCache = new Map< + string, + { fileCount: number; directories: Array<{ name: string }> } + >(); let hasChanges = $derived(currentPath !== initialPath); @@ -70,10 +75,6 @@ return `./${normalized.slice(1)}`; } - function bumpTreeVersion() { - treeVersion += 1; - } - function resolveIconUrl(rawIconName: string | null | undefined): string | null { if (!rawIconName) return null; const normalized = rawIconName.toLowerCase(); @@ -103,34 +104,103 @@ } } + async function fetchContents(path: string) { + const cached = contentsCache.get(path); + if (cached) return cached; + + const content = await sdk + .forProject(page.params.region, page.params.project) + .vcs.getRepositoryContents({ + installationId: $installation.$id, + providerRepositoryId: $repository.id, + providerRootDirectory: toProviderPath(path), + providerReference: branch + }); + + const fileCount = content.contents?.length ?? 0; + const directories = content.contents + .filter((e) => e.isDirectory) + .map((dir) => ({ name: dir.name })); + + const result = { fileCount, directories }; + contentsCache.set(path, result); + return result; + } + + function ensureChildren(path: string, directories: Array<{ name: string }>) { + const targetDir = getDirByPath(path); + if (!targetDir) return; + + if (directories.length === 0) { + targetDir.hasChildren = false; + targetDir.children = []; + return; + } + + const existingByTitle = new Map( + (targetDir.children ?? []).map((child) => [child.title, child]) + ); + targetDir.children = directories.map((dir) => { + const fullPath = path === '/' ? `/${dir.name}` : `${path}/${dir.name}`; + const existing = existingByTitle.get(dir.name); + if (existing) { + existing.fullPath = fullPath; + existing.hasChildren = true; + existing.loading = existing.loading ?? false; + existing.thumbnailUrl = existing.thumbnailUrl ?? $iconPath('empty', 'grayscale'); + existing.children = existing.children ?? []; + return existing; + } + return { + title: dir.name, + fullPath, + fileCount: undefined, + thumbnailUrl: $iconPath('empty', 'grayscale'), + children: [], + hasChildren: true, + loading: false + }; + }); + } + + async function prefetchPath(path: string) { + const normalized = normalizePath(path); + const segments = normalized.split('/').filter((s) => s !== ''); + const pathsToLoad = ['/']; + let currentPath = '/'; + + for (const segment of segments) { + currentPath = currentPath === '/' ? `/${segment}` : `${currentPath}/${segment}`; + pathsToLoad.push(currentPath); + } + + for (const pathToLoad of pathsToLoad) { + const { fileCount, directories } = await fetchContents(pathToLoad); + const targetDir = getDirByPath(pathToLoad); + if (targetDir) { + targetDir.fileCount = fileCount; + } + ensureChildren(pathToLoad, directories); + } + } + $effect(() => { if (!isLoading) return; (async () => { try { - const content = await sdk - .forProject(page.params.region, page.params.project) - .vcs.getRepositoryContents({ - installationId: $installation.$id, - providerRepositoryId: $repository.id, - providerRootDirectory: './', - providerReference: branch - }); + const content = await fetchContents('/'); + + const repoTitle = $repository?.name + ? `${$repository.name} (root)` + : 'Repository (root)'; directories[0] = { ...directories[0], - fileCount: content.contents?.length ?? 0, - children: content.contents - .filter((e) => e.isDirectory) - .map((dir) => ({ - title: dir.name, - fullPath: `/${dir.name}`, - fileCount: undefined, - // set logo for root directories - thumbnailUrl: $iconPath('empty', 'grayscale'), - loading: false - })) + title: repoTitle, + fileCount: content.fileCount }; + ensureChildren('/', content.directories); const detectedIcon = await detectRuntimeOrFramework('/'); if (detectedIcon) { @@ -139,7 +209,7 @@ isLoading = false; expandedStore.update((exp) => [...new Set([...exp, '/'])]); - bumpTreeVersion(); + prefetchPath(rootDir || '/'); } catch (error) { console.error('Failed to load root directory:', error); isLoading = false; @@ -163,24 +233,20 @@ const targetDir = getDirByPath(path); if (!targetDir || targetDir.fileCount !== undefined) return; + if (!targetDir.children) { + targetDir.children = []; + } + if (inFlightPaths.has(path)) return; inFlightPaths.add(path); targetDir.loading = true; try { - const content = await sdk - .forProject(page.params.region, page.params.project) - .vcs.getRepositoryContents({ - installationId: $installation.$id, - providerRepositoryId: $repository.id, - providerRootDirectory: toProviderPath(path), - providerReference: branch - }); - - const fileCount = content.contents?.length ?? 0; - const contentDirectories = content.contents.filter((e) => e.isDirectory); + const { fileCount, directories: contentDirectories } = await fetchContents(path); if (contentDirectories.length === 0) { + targetDir.hasChildren = false; + targetDir.children = []; expandedStore.update((exp) => [...new Set([...exp, path])]); return; } @@ -193,14 +259,7 @@ targetDir.thumbnailUrl = detectedIcon; } - const nextChildren = contentDirectories.map((dir) => ({ - title: dir.name, - fullPath: path === '/' ? `/${dir.name}` : `${path}/${dir.name}`, - fileCount: undefined, - thumbnailUrl: $iconPath('empty', 'grayscale') - })); - targetDir.children = nextChildren; - bumpTreeVersion(); + ensureChildren(path, contentDirectories); expandedStore.update((exp) => [...new Set([...exp, path])]); } catch (error) { @@ -235,7 +294,8 @@ fullPath: currentPath, fileCount: undefined, thumbnailUrl: $iconPath('empty', 'grayscale'), - children: [] + children: [], + hasChildren: true }; currentDir.children = [...currentDir.children, nextDir]; } @@ -244,10 +304,11 @@ } expandedStore.update((exp) => [...new Set([...exp, ...pathsToExpand])]); - bumpTreeVersion(); + // ensure each segment loads in order so deeper children appear for (const pathToLoad of pathsToExpand) { - loadPath(pathToLoad); + // eslint-disable-next-line no-await-in-loop + await loadPath(pathToLoad); } currentPath = normalized; @@ -286,15 +347,14 @@ Select the directory where your site code is located using the menu below. - {#key treeVersion} - - {/key} + From aecdfd5b4629f8e4d4f6936f14eeb772b4721e46 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Thu, 19 Feb 2026 19:11:09 +0530 Subject: [PATCH 06/10] svelte 5 --- src/lib/components/git/DirectoryItem.svelte | 110 +++++++++--------- src/lib/components/git/DirectoryPicker.svelte | 48 +++++--- src/lib/components/git/selectRootModal.svelte | 58 +++++++-- src/lib/components/git/types.ts | 14 +++ 4 files changed, 153 insertions(+), 77 deletions(-) create mode 100644 src/lib/components/git/types.ts diff --git a/src/lib/components/git/DirectoryItem.svelte b/src/lib/components/git/DirectoryItem.svelte index 68512940a4..baf7066013 100644 --- a/src/lib/components/git/DirectoryItem.svelte +++ b/src/lib/components/git/DirectoryItem.svelte @@ -3,33 +3,41 @@ import type { createTreeView } from '@melt-ui/svelte'; import { IconChevronRight } from '@appwrite.io/pink-icons-svelte'; import { Icon, Layout, Selector, Spinner, Typography } from '@appwrite.io/pink-svelte'; - - export let directories: Array<{ - title: string; - fileCount?: number; - fullPath: string; - thumbnailUrl?: string; - thumbnailIcon?: typeof Icon; - thumbnailHtml?: string; - children?: typeof directories; - hasChildren?: boolean; - showThumbnail?: boolean; - loading?: boolean; - }>; - export let level = 0; - export let containerWidth: number | undefined; - export let selectedPath: string | undefined; - export let onSelect: - | ((detail: { title: string; fullPath: string; hasChildren: boolean }) => void) - | undefined; + import DirectoryItemSelf from './DirectoryItem.svelte'; + + let { + directories, + level = 0, + containerWidth, + selectedPath, + onSelect + }: { + directories: Array<{ + title: string; + fileCount?: number; + fullPath: string; + thumbnailUrl?: string; + thumbnailIcon?: typeof Icon; + thumbnailHtml?: string; + children?: typeof directories; + hasChildren?: boolean; + showThumbnail?: boolean; + loading?: boolean; + }>; + level?: number; + containerWidth?: number; + selectedPath?: string; + onSelect?: (detail: { title: string; fullPath: string; hasChildren: boolean }) => void; + } = $props(); const Radio = Selector.Radio; - let radioInputs: Array = []; - let value: string | undefined; - let thumbnailStates: Array<{ loading: boolean; error: boolean }> = []; + let radioInputs = $state>([]); + let value = $state(undefined); + let thumbnailStates = $state>([]); - $: if (directories) { + $effect(() => { + if (!directories) return; if (thumbnailStates.length < directories.length) { thumbnailStates = [ ...thumbnailStates, @@ -41,7 +49,7 @@ } else if (thumbnailStates.length > directories.length) { thumbnailStates = thumbnailStates.slice(0, directories.length); } - } + }); function handleThumbnailLoad(index: number) { if (!thumbnailStates[index]) return; @@ -62,40 +70,41 @@ const paddingLeftStyle = `padding-left: ${32 * level + 8}px`; - $: if (selectedPath && directories?.length) { - const idx = directories.findIndex((d) => d.fullPath === selectedPath); - if (idx !== -1 && radioInputs[idx]) { - radioInputs[idx].checked = true; + $effect(() => { + if (selectedPath && directories?.length) { + const idx = directories.findIndex((d) => d.fullPath === selectedPath); + if (idx !== -1 && radioInputs[idx]) { + radioInputs[idx].checked = true; + } } - } + }); {#each directories as { title, fileCount, fullPath, thumbnailUrl, thumbnailIcon, thumbnailHtml, children, hasChildren: explicitHasChildren, showThumbnail = true, loading = false }, i} {@const hasChildren = explicitHasChildren ?? !!children?.length} {@const __MELTUI_BUILDER_1__ = $group({ id: fullPath })} {@const __MELTUI_BUILDER_0__ = $item({ - id: fullPath, - hasChildren - })} + id: fullPath, + hasChildren + })}
From 5d4a44a38d1f927d009bb77c9fe7f8b8fe01e080 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Fri, 20 Feb 2026 15:13:59 +0530 Subject: [PATCH 10/10] fix domains table lint --- src/lib/components/domains/recordTable.svelte | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib/components/domains/recordTable.svelte b/src/lib/components/domains/recordTable.svelte index 118b8149a5..fdba7438e2 100644 --- a/src/lib/components/domains/recordTable.svelte +++ b/src/lib/components/domains/recordTable.svelte @@ -140,14 +140,16 @@ {#if variant === 'cname' && !subdomain} {#if isCloud} - Since is an apex domain, CNAME - record is only supported by certain providers. If yours doesn't, please verify using + Since is an apex domain, + CNAME record is only supported by certain providers. If yours doesn't, please verify + using nameservers instead. {:else if aTabVisible || aaaaTabVisible} - Since is an apex domain, CNAME - record is only supported by certain providers. If yours doesn't, please verify using + Since is an apex domain, + CNAME record is only supported by certain providers. If yours doesn't, please verify + using {#if aTabVisible} A record {#if aaaaTabVisible}