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 1/2] 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 2/2] 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. - + - +