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.
-
+
-
+