From 77caa58852bc3bb6259fc70a9bd9e9d9495a7cbe Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 16 Feb 2026 19:21:30 +0100 Subject: [PATCH 01/13] feat: add file system using indexedDB --- src/lib/components/Stores/tabs.ts | 5 + src/lib/components/Utils/fileSystem.ts | 138 +++++++++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 src/lib/components/Stores/tabs.ts create mode 100644 src/lib/components/Utils/fileSystem.ts diff --git a/src/lib/components/Stores/tabs.ts b/src/lib/components/Stores/tabs.ts new file mode 100644 index 0000000..b2a2561 --- /dev/null +++ b/src/lib/components/Stores/tabs.ts @@ -0,0 +1,5 @@ +import { type Tab, tabTypes } from '$lib/components/Tabs/types'; +import { type Writable, writable } from 'svelte/store'; + +export const tabsStore: Writable = writable([{ type: tabTypes[0], title: 'Game' }]); +export const tabSelectedStore: Writable = writable(0); diff --git a/src/lib/components/Utils/fileSystem.ts b/src/lib/components/Utils/fileSystem.ts new file mode 100644 index 0000000..bfea074 --- /dev/null +++ b/src/lib/components/Utils/fileSystem.ts @@ -0,0 +1,138 @@ +import fileSaver from 'file-saver'; +import { openDB } from 'idb'; +import JSZip from 'jszip'; + +const dbName = 'NanoForge'; +let dbPromise: Promise | null = null; + +export interface File { + id: string; + lastModified: number; +} + +async function getDB() { + if (!dbPromise) { + dbPromise = openDB(dbName, 1, { + upgrade(db) { + if (!db.objectStoreNames.contains('files')) { + db.createObjectStore('files', { keyPath: 'id' }); + } + }, + }); + } + return dbPromise; +} + +export async function initDB() { + await getDB(); +} + +export async function listFiles(): Promise> { + const db = await getDB(); + const tx = db.transaction('files', 'readonly'); + const store = tx.objectStore('files'); + + const allFiles = await store.getAll(); + + return allFiles.map((file: File) => ({ + id: file.id, + lastModified: file.lastModified, + })); +} + +export async function listFolderContents( + path: string = '', +): Promise> { + const allFiles = await listFiles(); + const cleanPath = path.endsWith('/') ? path.slice(0, -1) : path; + + const items = new Map(); + + const files = allFiles.filter((file) => { + const filePath = file.id; + if (cleanPath === '') { + return !filePath.includes('/'); // Racine + } + return filePath.startsWith(cleanPath + '/') && !filePath.includes('/', cleanPath.length + 1); + }); + + for (const file of files) { + const fileName = file.id.split('/').pop(); + const name = cleanPath === '' ? file.id : (fileName ?? file.id); + items.set(name, { name, type: 'file' as const, lastModified: file.lastModified }); + } + + const folders = new Set(); + for (const file of allFiles) { + const filePath = file.id; + if (cleanPath === '') { + const firstFolder = filePath.split('/')[0]; + if (firstFolder) folders.add(firstFolder); + } else { + const relativePath = filePath.substring(cleanPath.length + 1); + const firstFolder = relativePath.split('/')[0]; + if (firstFolder) folders.add(firstFolder); + } + } + + for (const folder of folders) { + if (!items.has(folder)) { + items.set(folder, { name: folder, type: 'folder' as const }); + } + } + + return Array.from(items.values()).sort((a, b) => { + if (a.type === 'folder' && b.type === 'file') return -1; + if (a.type === 'file' && b.type === 'folder') return 1; + return a.name.localeCompare(b.name); + }); +} + +export async function saveFile(fileName: string, content: string) { + const db = await getDB(); + const tx = db.transaction('files', 'readwrite'); + const store = tx.objectStore('files'); + await store.put({ id: fileName, content, lastModified: Date.now() }); + await tx.done; +} + +export async function loadFile(fileName: string) { + const db = await getDB(); + const tx = db.transaction('files', 'readonly'); + const store = tx.objectStore('files'); + const file = await store.get(fileName); + return file?.content || ''; +} + +function ensureFolder(zip: JSZip, path: string): JSZip { + return zip.folder(path) ?? zip; +} + +export async function exportToZip(filename: string = 'NanoForge.zip') { + const db = await getDB(); + const tx = db.transaction('files', 'readonly'); + const store = tx.objectStore('files'); + + const allFiles = await store.getAll(); + const zip = new JSZip(); + + for (const file of allFiles) { + const parts = file.id.split('/'); + let currentFolder = zip; + + for (let i = 0; i < parts.length - 1; i++) { + const folderName = parts[i]; + const nextFolder = currentFolder.folder(folderName); + if (nextFolder) { + currentFolder = nextFolder; + } else { + currentFolder = ensureFolder(currentFolder, parts[i]); + } + } + + currentFolder.file(parts[parts.length - 1], file.content); + } + + const zipBlob = await zip.generateAsync({ type: 'blob' }); + fileSaver.saveAs(zipBlob, filename); +} From 6555c9e1f97ddc8ac8d50f7613525e28251884d0 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 16 Mar 2026 01:37:49 +0100 Subject: [PATCH 02/13] fix: list local folder in ContentBrowser --- src/lib/components/Storage/db.ts | 21 ++++ src/lib/components/Storage/fileSystem.ts | 91 +++++++++++++++ src/lib/components/Storage/tabs.ts | 9 ++ src/lib/components/Stores/workingFile.ts | 3 + src/lib/components/Utils/fileSystem.ts | 138 ----------------------- 5 files changed, 124 insertions(+), 138 deletions(-) create mode 100644 src/lib/components/Storage/db.ts create mode 100644 src/lib/components/Storage/fileSystem.ts create mode 100644 src/lib/components/Storage/tabs.ts create mode 100644 src/lib/components/Stores/workingFile.ts delete mode 100644 src/lib/components/Utils/fileSystem.ts diff --git a/src/lib/components/Storage/db.ts b/src/lib/components/Storage/db.ts new file mode 100644 index 0000000..a76d4de --- /dev/null +++ b/src/lib/components/Storage/db.ts @@ -0,0 +1,21 @@ +import { openDB } from 'idb'; + +const dbName = 'NanoForge'; +let dbPromise: Promise | null = null; + +export async function getDB() { + if (!dbPromise) { + dbPromise = openDB(dbName, 1, { + upgrade(db) { + if (!db.objectStoreNames.contains('files')) { + db.createObjectStore('files', { keyPath: 'id' }); + } + }, + }); + } + return dbPromise; +} + +export async function initDB() { + await getDB(); +} diff --git a/src/lib/components/Storage/fileSystem.ts b/src/lib/components/Storage/fileSystem.ts new file mode 100644 index 0000000..a850e10 --- /dev/null +++ b/src/lib/components/Storage/fileSystem.ts @@ -0,0 +1,91 @@ +import { getDB } from '$lib/components/Storage/db'; + +export interface File { + id: string; + lastModified: number; +} + +export interface FolderContent { + name: string; + type: 'file' | 'folder'; + lastModified?: number; +} + +export async function listFiles(): Promise> { + const db = await getDB(); + const tx = db.transaction('files', 'readonly'); + const store = tx.objectStore('files'); + + const allFiles = await store.getAll(); + + return allFiles.map((file: File) => ({ + id: file.id, + lastModified: file.lastModified, + })); +} + +export async function listFolders(): Promise { + const folders = new Set(); + const files = await listFiles(); + + for (const file of files) { + const parts = file.id.split('/'); + + parts.pop(); + + let current = ''; + for (const part of parts) { + current = current ? `${current}/${part}` : part; + folders.add(current); + } + } + + return Array.from(folders).sort(); +} + +export async function listFolderContents(path: string = ''): Promise> { + const allFiles = await listFiles(); + let cleanPath = path.startsWith('/') ? path.substring(1) : path; + cleanPath = cleanPath.endsWith('/') || cleanPath === '' ? cleanPath : cleanPath.concat('/'); + + const items = new Map(); + + const files = allFiles.filter((file) => { + const filePath = file.id; + return filePath.startsWith(cleanPath); + }); + + for (const file of files) { + const relativePath = file.id.substring(cleanPath.length); + const isFolderIndex = relativePath.indexOf('/'); + const name = isFolderIndex >= 0 ? relativePath.substring(0, isFolderIndex) : relativePath; + + items.set(name, { + name, + type: isFolderIndex >= 0 ? 'folder' : 'file', + lastModified: file.lastModified, + }); + } + + return Array.from(items.values()).sort((a, b) => { + if (a.type === 'folder' && b.type === 'file') return -1; + if (a.type === 'file' && b.type === 'folder') return 1; + return a.name.localeCompare(b.name); + }); +} + +export async function saveFile(fileName: string, content: string) { + const db = await getDB(); + const tx = db.transaction('files', 'readwrite'); + const store = tx.objectStore('files'); + await store.put({ id: fileName, content, lastModified: Date.now() }); + await tx.done; +} + +export async function loadFile(fileName: string): Promise { + const db = await getDB(); + const tx = db.transaction('files', 'readonly'); + const store = tx.objectStore('files'); + const file = await store.get(fileName); + return file?.content || ''; +} diff --git a/src/lib/components/Storage/tabs.ts b/src/lib/components/Storage/tabs.ts new file mode 100644 index 0000000..4dff86b --- /dev/null +++ b/src/lib/components/Storage/tabs.ts @@ -0,0 +1,9 @@ +import { getDB } from '$lib/components/Storage/db'; + +export async function saveTab(fileName: string, content: string) { + const db = await getDB(); + const tx = db.transaction('tabs', 'readwrite'); + const store = tx.objectStore('tabs'); + await store.put({ id: fileName, content, lastModified: Date.now() }); + await tx.done; +} diff --git a/src/lib/components/Stores/workingFile.ts b/src/lib/components/Stores/workingFile.ts new file mode 100644 index 0000000..773c2a7 --- /dev/null +++ b/src/lib/components/Stores/workingFile.ts @@ -0,0 +1,3 @@ +import { type Writable, writable } from 'svelte/store'; + +export const workingFileStore: Writable = writable(''); diff --git a/src/lib/components/Utils/fileSystem.ts b/src/lib/components/Utils/fileSystem.ts deleted file mode 100644 index bfea074..0000000 --- a/src/lib/components/Utils/fileSystem.ts +++ /dev/null @@ -1,138 +0,0 @@ -import fileSaver from 'file-saver'; -import { openDB } from 'idb'; -import JSZip from 'jszip'; - -const dbName = 'NanoForge'; -let dbPromise: Promise | null = null; - -export interface File { - id: string; - lastModified: number; -} - -async function getDB() { - if (!dbPromise) { - dbPromise = openDB(dbName, 1, { - upgrade(db) { - if (!db.objectStoreNames.contains('files')) { - db.createObjectStore('files', { keyPath: 'id' }); - } - }, - }); - } - return dbPromise; -} - -export async function initDB() { - await getDB(); -} - -export async function listFiles(): Promise> { - const db = await getDB(); - const tx = db.transaction('files', 'readonly'); - const store = tx.objectStore('files'); - - const allFiles = await store.getAll(); - - return allFiles.map((file: File) => ({ - id: file.id, - lastModified: file.lastModified, - })); -} - -export async function listFolderContents( - path: string = '', -): Promise> { - const allFiles = await listFiles(); - const cleanPath = path.endsWith('/') ? path.slice(0, -1) : path; - - const items = new Map(); - - const files = allFiles.filter((file) => { - const filePath = file.id; - if (cleanPath === '') { - return !filePath.includes('/'); // Racine - } - return filePath.startsWith(cleanPath + '/') && !filePath.includes('/', cleanPath.length + 1); - }); - - for (const file of files) { - const fileName = file.id.split('/').pop(); - const name = cleanPath === '' ? file.id : (fileName ?? file.id); - items.set(name, { name, type: 'file' as const, lastModified: file.lastModified }); - } - - const folders = new Set(); - for (const file of allFiles) { - const filePath = file.id; - if (cleanPath === '') { - const firstFolder = filePath.split('/')[0]; - if (firstFolder) folders.add(firstFolder); - } else { - const relativePath = filePath.substring(cleanPath.length + 1); - const firstFolder = relativePath.split('/')[0]; - if (firstFolder) folders.add(firstFolder); - } - } - - for (const folder of folders) { - if (!items.has(folder)) { - items.set(folder, { name: folder, type: 'folder' as const }); - } - } - - return Array.from(items.values()).sort((a, b) => { - if (a.type === 'folder' && b.type === 'file') return -1; - if (a.type === 'file' && b.type === 'folder') return 1; - return a.name.localeCompare(b.name); - }); -} - -export async function saveFile(fileName: string, content: string) { - const db = await getDB(); - const tx = db.transaction('files', 'readwrite'); - const store = tx.objectStore('files'); - await store.put({ id: fileName, content, lastModified: Date.now() }); - await tx.done; -} - -export async function loadFile(fileName: string) { - const db = await getDB(); - const tx = db.transaction('files', 'readonly'); - const store = tx.objectStore('files'); - const file = await store.get(fileName); - return file?.content || ''; -} - -function ensureFolder(zip: JSZip, path: string): JSZip { - return zip.folder(path) ?? zip; -} - -export async function exportToZip(filename: string = 'NanoForge.zip') { - const db = await getDB(); - const tx = db.transaction('files', 'readonly'); - const store = tx.objectStore('files'); - - const allFiles = await store.getAll(); - const zip = new JSZip(); - - for (const file of allFiles) { - const parts = file.id.split('/'); - let currentFolder = zip; - - for (let i = 0; i < parts.length - 1; i++) { - const folderName = parts[i]; - const nextFolder = currentFolder.folder(folderName); - if (nextFolder) { - currentFolder = nextFolder; - } else { - currentFolder = ensureFolder(currentFolder, parts[i]); - } - } - - currentFolder.file(parts[parts.length - 1], file.content); - } - - const zipBlob = await zip.generateAsync({ type: 'blob' }); - fileSaver.saveAs(zipBlob, filename); -} From c22b8b48b33b5ceaab2d1b4265fbb905f9c26290 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 17 Mar 2026 02:31:43 +0100 Subject: [PATCH 03/13] feat: create project push and select project overlay --- src/lib/components/{ => Utils}/Storage/db.ts | 10 ++++ .../{ => Utils}/Storage/fileSystem.ts | 2 +- .../components/Utils/Storage/projectSync.ts | 55 +++++++++++++++++++ .../components/{ => Utils}/Storage/tabs.ts | 2 +- 4 files changed, 67 insertions(+), 2 deletions(-) rename src/lib/components/{ => Utils}/Storage/db.ts (68%) rename src/lib/components/{ => Utils}/Storage/fileSystem.ts (97%) create mode 100644 src/lib/components/Utils/Storage/projectSync.ts rename src/lib/components/{ => Utils}/Storage/tabs.ts (82%) diff --git a/src/lib/components/Storage/db.ts b/src/lib/components/Utils/Storage/db.ts similarity index 68% rename from src/lib/components/Storage/db.ts rename to src/lib/components/Utils/Storage/db.ts index a76d4de..8c78816 100644 --- a/src/lib/components/Storage/db.ts +++ b/src/lib/components/Utils/Storage/db.ts @@ -19,3 +19,13 @@ export async function getDB() { export async function initDB() { await getDB(); } + +export async function clearDB() { + const db = await getDB(); + const tx = db.transaction('files', 'readwrite'); + const store = tx.objectStore('files'); + + await store.clear(); + + await tx.done; +} diff --git a/src/lib/components/Storage/fileSystem.ts b/src/lib/components/Utils/Storage/fileSystem.ts similarity index 97% rename from src/lib/components/Storage/fileSystem.ts rename to src/lib/components/Utils/Storage/fileSystem.ts index a850e10..96aadf1 100644 --- a/src/lib/components/Storage/fileSystem.ts +++ b/src/lib/components/Utils/Storage/fileSystem.ts @@ -1,4 +1,4 @@ -import { getDB } from '$lib/components/Storage/db'; +import { getDB } from '$lib/components/Utils/Storage/db'; export interface File { id: string; diff --git a/src/lib/components/Utils/Storage/projectSync.ts b/src/lib/components/Utils/Storage/projectSync.ts new file mode 100644 index 0000000..4c3593f --- /dev/null +++ b/src/lib/components/Utils/Storage/projectSync.ts @@ -0,0 +1,55 @@ +import { clearDB } from '$lib/components/Utils/Storage/db'; +import { listFiles, loadFile, saveFile } from '$lib/components/Utils/Storage/fileSystem'; + +export async function loadRemoteProject() { + const response = await fetch('/fs?/=readDirRec', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ dirPath: '/' }), + }); + + const result = await response.json(); + + if (!result.success) throw new Error(result.errorMsg); + + await clearDB(); + + for (const item of result.dirContent) { + if (item.type === 'file') { + const fileRes = await fetch('/fs?/=readFile', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ filePath: item.name }), + }); + const fileResult = await fileRes.json(); + + if (fileResult.success) { + await saveFile(item.name, fileResult.fileContent); + } + } + } +} + +export async function pushLocalProject() { + console.log('🔄 Push projet vers serveur distant...'); + + const localFiles = await listFiles(); + + const pushPromises = localFiles.map(async (file) => { + const content = await loadFile(file.id); + + const formData = new FormData(); + formData.append('filePath', file.id); + formData.append('fileContent', content); + + await fetch('/fs?/writeFile', { + method: 'POST', + body: JSON.stringify({ + filePath: formData.get('filePath'), + fileContent: formData.get('fileContent'), + }), + }); + }); + + await Promise.all(pushPromises); +} diff --git a/src/lib/components/Storage/tabs.ts b/src/lib/components/Utils/Storage/tabs.ts similarity index 82% rename from src/lib/components/Storage/tabs.ts rename to src/lib/components/Utils/Storage/tabs.ts index 4dff86b..dce3159 100644 --- a/src/lib/components/Storage/tabs.ts +++ b/src/lib/components/Utils/Storage/tabs.ts @@ -1,4 +1,4 @@ -import { getDB } from '$lib/components/Storage/db'; +import { getDB } from '$lib/components/Utils/Storage/db'; export async function saveTab(fileName: string, content: string) { const db = await getDB(); From 106baff0c4b4f14d2ffbccc46fa1c78db4471cfe Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 17 Mar 2026 23:41:45 +0100 Subject: [PATCH 04/13] feat: create new project dialog --- .../components/Utils/Storage/projectSync.ts | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/lib/components/Utils/Storage/projectSync.ts b/src/lib/components/Utils/Storage/projectSync.ts index 4c3593f..a7d33ba 100644 --- a/src/lib/components/Utils/Storage/projectSync.ts +++ b/src/lib/components/Utils/Storage/projectSync.ts @@ -1,7 +1,22 @@ -import { clearDB } from '$lib/components/Utils/Storage/db'; -import { listFiles, loadFile, saveFile } from '$lib/components/Utils/Storage/fileSystem'; +import { listFiles, loadFile } from '$lib/components/Utils/Storage/fileSystem'; -export async function loadRemoteProject() { +export async function createProject(formData: FormData) { + await fetch('/cli?/createProject', { + method: 'POST', + body: JSON.stringify({ + projectPath: formData.get('projectPath'), + projectName: formData.get('projectName'), + packageManager: formData.get('packageManager'), + language: formData.get('language'), + strictTypeChecking: formData.get('strictTypeChecking') ?? false, + multiplayerServer: formData.get('multiplayerServer') ?? false, + skipDependencyInstallation: formData.get('skipDependencyInstallation') ?? false, + dockerContainerization: formData.get('dockerContainerization') ?? false, + }), + }); +} + +/*export async function loadRemoteProject() { const response = await fetch('/fs?/=readDirRec', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -28,11 +43,9 @@ export async function loadRemoteProject() { } } } -} +}*/ export async function pushLocalProject() { - console.log('🔄 Push projet vers serveur distant...'); - const localFiles = await listFiles(); const pushPromises = localFiles.map(async (file) => { From b32615499994c6e546b9eb5b8015ec395f2f7941 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 16 Feb 2026 19:21:30 +0100 Subject: [PATCH 05/13] feat: add file system using indexedDB --- src/lib/components/Utils/fileSystem.ts | 138 +++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 src/lib/components/Utils/fileSystem.ts diff --git a/src/lib/components/Utils/fileSystem.ts b/src/lib/components/Utils/fileSystem.ts new file mode 100644 index 0000000..bfea074 --- /dev/null +++ b/src/lib/components/Utils/fileSystem.ts @@ -0,0 +1,138 @@ +import fileSaver from 'file-saver'; +import { openDB } from 'idb'; +import JSZip from 'jszip'; + +const dbName = 'NanoForge'; +let dbPromise: Promise | null = null; + +export interface File { + id: string; + lastModified: number; +} + +async function getDB() { + if (!dbPromise) { + dbPromise = openDB(dbName, 1, { + upgrade(db) { + if (!db.objectStoreNames.contains('files')) { + db.createObjectStore('files', { keyPath: 'id' }); + } + }, + }); + } + return dbPromise; +} + +export async function initDB() { + await getDB(); +} + +export async function listFiles(): Promise> { + const db = await getDB(); + const tx = db.transaction('files', 'readonly'); + const store = tx.objectStore('files'); + + const allFiles = await store.getAll(); + + return allFiles.map((file: File) => ({ + id: file.id, + lastModified: file.lastModified, + })); +} + +export async function listFolderContents( + path: string = '', +): Promise> { + const allFiles = await listFiles(); + const cleanPath = path.endsWith('/') ? path.slice(0, -1) : path; + + const items = new Map(); + + const files = allFiles.filter((file) => { + const filePath = file.id; + if (cleanPath === '') { + return !filePath.includes('/'); // Racine + } + return filePath.startsWith(cleanPath + '/') && !filePath.includes('/', cleanPath.length + 1); + }); + + for (const file of files) { + const fileName = file.id.split('/').pop(); + const name = cleanPath === '' ? file.id : (fileName ?? file.id); + items.set(name, { name, type: 'file' as const, lastModified: file.lastModified }); + } + + const folders = new Set(); + for (const file of allFiles) { + const filePath = file.id; + if (cleanPath === '') { + const firstFolder = filePath.split('/')[0]; + if (firstFolder) folders.add(firstFolder); + } else { + const relativePath = filePath.substring(cleanPath.length + 1); + const firstFolder = relativePath.split('/')[0]; + if (firstFolder) folders.add(firstFolder); + } + } + + for (const folder of folders) { + if (!items.has(folder)) { + items.set(folder, { name: folder, type: 'folder' as const }); + } + } + + return Array.from(items.values()).sort((a, b) => { + if (a.type === 'folder' && b.type === 'file') return -1; + if (a.type === 'file' && b.type === 'folder') return 1; + return a.name.localeCompare(b.name); + }); +} + +export async function saveFile(fileName: string, content: string) { + const db = await getDB(); + const tx = db.transaction('files', 'readwrite'); + const store = tx.objectStore('files'); + await store.put({ id: fileName, content, lastModified: Date.now() }); + await tx.done; +} + +export async function loadFile(fileName: string) { + const db = await getDB(); + const tx = db.transaction('files', 'readonly'); + const store = tx.objectStore('files'); + const file = await store.get(fileName); + return file?.content || ''; +} + +function ensureFolder(zip: JSZip, path: string): JSZip { + return zip.folder(path) ?? zip; +} + +export async function exportToZip(filename: string = 'NanoForge.zip') { + const db = await getDB(); + const tx = db.transaction('files', 'readonly'); + const store = tx.objectStore('files'); + + const allFiles = await store.getAll(); + const zip = new JSZip(); + + for (const file of allFiles) { + const parts = file.id.split('/'); + let currentFolder = zip; + + for (let i = 0; i < parts.length - 1; i++) { + const folderName = parts[i]; + const nextFolder = currentFolder.folder(folderName); + if (nextFolder) { + currentFolder = nextFolder; + } else { + currentFolder = ensureFolder(currentFolder, parts[i]); + } + } + + currentFolder.file(parts[parts.length - 1], file.content); + } + + const zipBlob = await zip.generateAsync({ type: 'blob' }); + fileSaver.saveAs(zipBlob, filename); +} From 219d49fa127af2cf109f2e61b96cc6415aee7b1d Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 16 Mar 2026 01:37:49 +0100 Subject: [PATCH 06/13] fix: list local folder in ContentBrowser --- src/lib/components/Storage/db.ts | 21 ++++ src/lib/components/Storage/fileSystem.ts | 91 +++++++++++++++ src/lib/components/Storage/tabs.ts | 9 ++ src/lib/components/Utils/fileSystem.ts | 138 ----------------------- 4 files changed, 121 insertions(+), 138 deletions(-) create mode 100644 src/lib/components/Storage/db.ts create mode 100644 src/lib/components/Storage/fileSystem.ts create mode 100644 src/lib/components/Storage/tabs.ts delete mode 100644 src/lib/components/Utils/fileSystem.ts diff --git a/src/lib/components/Storage/db.ts b/src/lib/components/Storage/db.ts new file mode 100644 index 0000000..a76d4de --- /dev/null +++ b/src/lib/components/Storage/db.ts @@ -0,0 +1,21 @@ +import { openDB } from 'idb'; + +const dbName = 'NanoForge'; +let dbPromise: Promise | null = null; + +export async function getDB() { + if (!dbPromise) { + dbPromise = openDB(dbName, 1, { + upgrade(db) { + if (!db.objectStoreNames.contains('files')) { + db.createObjectStore('files', { keyPath: 'id' }); + } + }, + }); + } + return dbPromise; +} + +export async function initDB() { + await getDB(); +} diff --git a/src/lib/components/Storage/fileSystem.ts b/src/lib/components/Storage/fileSystem.ts new file mode 100644 index 0000000..a850e10 --- /dev/null +++ b/src/lib/components/Storage/fileSystem.ts @@ -0,0 +1,91 @@ +import { getDB } from '$lib/components/Storage/db'; + +export interface File { + id: string; + lastModified: number; +} + +export interface FolderContent { + name: string; + type: 'file' | 'folder'; + lastModified?: number; +} + +export async function listFiles(): Promise> { + const db = await getDB(); + const tx = db.transaction('files', 'readonly'); + const store = tx.objectStore('files'); + + const allFiles = await store.getAll(); + + return allFiles.map((file: File) => ({ + id: file.id, + lastModified: file.lastModified, + })); +} + +export async function listFolders(): Promise { + const folders = new Set(); + const files = await listFiles(); + + for (const file of files) { + const parts = file.id.split('/'); + + parts.pop(); + + let current = ''; + for (const part of parts) { + current = current ? `${current}/${part}` : part; + folders.add(current); + } + } + + return Array.from(folders).sort(); +} + +export async function listFolderContents(path: string = ''): Promise> { + const allFiles = await listFiles(); + let cleanPath = path.startsWith('/') ? path.substring(1) : path; + cleanPath = cleanPath.endsWith('/') || cleanPath === '' ? cleanPath : cleanPath.concat('/'); + + const items = new Map(); + + const files = allFiles.filter((file) => { + const filePath = file.id; + return filePath.startsWith(cleanPath); + }); + + for (const file of files) { + const relativePath = file.id.substring(cleanPath.length); + const isFolderIndex = relativePath.indexOf('/'); + const name = isFolderIndex >= 0 ? relativePath.substring(0, isFolderIndex) : relativePath; + + items.set(name, { + name, + type: isFolderIndex >= 0 ? 'folder' : 'file', + lastModified: file.lastModified, + }); + } + + return Array.from(items.values()).sort((a, b) => { + if (a.type === 'folder' && b.type === 'file') return -1; + if (a.type === 'file' && b.type === 'folder') return 1; + return a.name.localeCompare(b.name); + }); +} + +export async function saveFile(fileName: string, content: string) { + const db = await getDB(); + const tx = db.transaction('files', 'readwrite'); + const store = tx.objectStore('files'); + await store.put({ id: fileName, content, lastModified: Date.now() }); + await tx.done; +} + +export async function loadFile(fileName: string): Promise { + const db = await getDB(); + const tx = db.transaction('files', 'readonly'); + const store = tx.objectStore('files'); + const file = await store.get(fileName); + return file?.content || ''; +} diff --git a/src/lib/components/Storage/tabs.ts b/src/lib/components/Storage/tabs.ts new file mode 100644 index 0000000..4dff86b --- /dev/null +++ b/src/lib/components/Storage/tabs.ts @@ -0,0 +1,9 @@ +import { getDB } from '$lib/components/Storage/db'; + +export async function saveTab(fileName: string, content: string) { + const db = await getDB(); + const tx = db.transaction('tabs', 'readwrite'); + const store = tx.objectStore('tabs'); + await store.put({ id: fileName, content, lastModified: Date.now() }); + await tx.done; +} diff --git a/src/lib/components/Utils/fileSystem.ts b/src/lib/components/Utils/fileSystem.ts deleted file mode 100644 index bfea074..0000000 --- a/src/lib/components/Utils/fileSystem.ts +++ /dev/null @@ -1,138 +0,0 @@ -import fileSaver from 'file-saver'; -import { openDB } from 'idb'; -import JSZip from 'jszip'; - -const dbName = 'NanoForge'; -let dbPromise: Promise | null = null; - -export interface File { - id: string; - lastModified: number; -} - -async function getDB() { - if (!dbPromise) { - dbPromise = openDB(dbName, 1, { - upgrade(db) { - if (!db.objectStoreNames.contains('files')) { - db.createObjectStore('files', { keyPath: 'id' }); - } - }, - }); - } - return dbPromise; -} - -export async function initDB() { - await getDB(); -} - -export async function listFiles(): Promise> { - const db = await getDB(); - const tx = db.transaction('files', 'readonly'); - const store = tx.objectStore('files'); - - const allFiles = await store.getAll(); - - return allFiles.map((file: File) => ({ - id: file.id, - lastModified: file.lastModified, - })); -} - -export async function listFolderContents( - path: string = '', -): Promise> { - const allFiles = await listFiles(); - const cleanPath = path.endsWith('/') ? path.slice(0, -1) : path; - - const items = new Map(); - - const files = allFiles.filter((file) => { - const filePath = file.id; - if (cleanPath === '') { - return !filePath.includes('/'); // Racine - } - return filePath.startsWith(cleanPath + '/') && !filePath.includes('/', cleanPath.length + 1); - }); - - for (const file of files) { - const fileName = file.id.split('/').pop(); - const name = cleanPath === '' ? file.id : (fileName ?? file.id); - items.set(name, { name, type: 'file' as const, lastModified: file.lastModified }); - } - - const folders = new Set(); - for (const file of allFiles) { - const filePath = file.id; - if (cleanPath === '') { - const firstFolder = filePath.split('/')[0]; - if (firstFolder) folders.add(firstFolder); - } else { - const relativePath = filePath.substring(cleanPath.length + 1); - const firstFolder = relativePath.split('/')[0]; - if (firstFolder) folders.add(firstFolder); - } - } - - for (const folder of folders) { - if (!items.has(folder)) { - items.set(folder, { name: folder, type: 'folder' as const }); - } - } - - return Array.from(items.values()).sort((a, b) => { - if (a.type === 'folder' && b.type === 'file') return -1; - if (a.type === 'file' && b.type === 'folder') return 1; - return a.name.localeCompare(b.name); - }); -} - -export async function saveFile(fileName: string, content: string) { - const db = await getDB(); - const tx = db.transaction('files', 'readwrite'); - const store = tx.objectStore('files'); - await store.put({ id: fileName, content, lastModified: Date.now() }); - await tx.done; -} - -export async function loadFile(fileName: string) { - const db = await getDB(); - const tx = db.transaction('files', 'readonly'); - const store = tx.objectStore('files'); - const file = await store.get(fileName); - return file?.content || ''; -} - -function ensureFolder(zip: JSZip, path: string): JSZip { - return zip.folder(path) ?? zip; -} - -export async function exportToZip(filename: string = 'NanoForge.zip') { - const db = await getDB(); - const tx = db.transaction('files', 'readonly'); - const store = tx.objectStore('files'); - - const allFiles = await store.getAll(); - const zip = new JSZip(); - - for (const file of allFiles) { - const parts = file.id.split('/'); - let currentFolder = zip; - - for (let i = 0; i < parts.length - 1; i++) { - const folderName = parts[i]; - const nextFolder = currentFolder.folder(folderName); - if (nextFolder) { - currentFolder = nextFolder; - } else { - currentFolder = ensureFolder(currentFolder, parts[i]); - } - } - - currentFolder.file(parts[parts.length - 1], file.content); - } - - const zipBlob = await zip.generateAsync({ type: 'blob' }); - fileSaver.saveAs(zipBlob, filename); -} From e5d8810a9066cd4dba540d077c9e0a0756e0760c Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 17 Mar 2026 02:31:43 +0100 Subject: [PATCH 07/13] feat: create project push and select project overlay --- src/lib/components/Storage/db.ts | 21 ------ src/lib/components/Storage/fileSystem.ts | 91 ------------------------ src/lib/components/Storage/tabs.ts | 9 --- 3 files changed, 121 deletions(-) delete mode 100644 src/lib/components/Storage/db.ts delete mode 100644 src/lib/components/Storage/fileSystem.ts delete mode 100644 src/lib/components/Storage/tabs.ts diff --git a/src/lib/components/Storage/db.ts b/src/lib/components/Storage/db.ts deleted file mode 100644 index a76d4de..0000000 --- a/src/lib/components/Storage/db.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { openDB } from 'idb'; - -const dbName = 'NanoForge'; -let dbPromise: Promise | null = null; - -export async function getDB() { - if (!dbPromise) { - dbPromise = openDB(dbName, 1, { - upgrade(db) { - if (!db.objectStoreNames.contains('files')) { - db.createObjectStore('files', { keyPath: 'id' }); - } - }, - }); - } - return dbPromise; -} - -export async function initDB() { - await getDB(); -} diff --git a/src/lib/components/Storage/fileSystem.ts b/src/lib/components/Storage/fileSystem.ts deleted file mode 100644 index a850e10..0000000 --- a/src/lib/components/Storage/fileSystem.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { getDB } from '$lib/components/Storage/db'; - -export interface File { - id: string; - lastModified: number; -} - -export interface FolderContent { - name: string; - type: 'file' | 'folder'; - lastModified?: number; -} - -export async function listFiles(): Promise> { - const db = await getDB(); - const tx = db.transaction('files', 'readonly'); - const store = tx.objectStore('files'); - - const allFiles = await store.getAll(); - - return allFiles.map((file: File) => ({ - id: file.id, - lastModified: file.lastModified, - })); -} - -export async function listFolders(): Promise { - const folders = new Set(); - const files = await listFiles(); - - for (const file of files) { - const parts = file.id.split('/'); - - parts.pop(); - - let current = ''; - for (const part of parts) { - current = current ? `${current}/${part}` : part; - folders.add(current); - } - } - - return Array.from(folders).sort(); -} - -export async function listFolderContents(path: string = ''): Promise> { - const allFiles = await listFiles(); - let cleanPath = path.startsWith('/') ? path.substring(1) : path; - cleanPath = cleanPath.endsWith('/') || cleanPath === '' ? cleanPath : cleanPath.concat('/'); - - const items = new Map(); - - const files = allFiles.filter((file) => { - const filePath = file.id; - return filePath.startsWith(cleanPath); - }); - - for (const file of files) { - const relativePath = file.id.substring(cleanPath.length); - const isFolderIndex = relativePath.indexOf('/'); - const name = isFolderIndex >= 0 ? relativePath.substring(0, isFolderIndex) : relativePath; - - items.set(name, { - name, - type: isFolderIndex >= 0 ? 'folder' : 'file', - lastModified: file.lastModified, - }); - } - - return Array.from(items.values()).sort((a, b) => { - if (a.type === 'folder' && b.type === 'file') return -1; - if (a.type === 'file' && b.type === 'folder') return 1; - return a.name.localeCompare(b.name); - }); -} - -export async function saveFile(fileName: string, content: string) { - const db = await getDB(); - const tx = db.transaction('files', 'readwrite'); - const store = tx.objectStore('files'); - await store.put({ id: fileName, content, lastModified: Date.now() }); - await tx.done; -} - -export async function loadFile(fileName: string): Promise { - const db = await getDB(); - const tx = db.transaction('files', 'readonly'); - const store = tx.objectStore('files'); - const file = await store.get(fileName); - return file?.content || ''; -} diff --git a/src/lib/components/Storage/tabs.ts b/src/lib/components/Storage/tabs.ts deleted file mode 100644 index 4dff86b..0000000 --- a/src/lib/components/Storage/tabs.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { getDB } from '$lib/components/Storage/db'; - -export async function saveTab(fileName: string, content: string) { - const db = await getDB(); - const tx = db.transaction('tabs', 'readwrite'); - const store = tx.objectStore('tabs'); - await store.put({ id: fileName, content, lastModified: Date.now() }); - await tx.done; -} From 9db5a0b0f3d2814ac5821e65ea029c3d599c483b Mon Sep 17 00:00:00 2001 From: bill Date: Thu, 19 Mar 2026 04:26:56 +0100 Subject: [PATCH 08/13] feat: create API interface with local API. Use create and load project files --- src/lib/components/Stores/project.ts | 3 + .../components/Utils/Storage/projectSync.ts | 68 ------------------- 2 files changed, 3 insertions(+), 68 deletions(-) create mode 100644 src/lib/components/Stores/project.ts delete mode 100644 src/lib/components/Utils/Storage/projectSync.ts diff --git a/src/lib/components/Stores/project.ts b/src/lib/components/Stores/project.ts new file mode 100644 index 0000000..74a9c72 --- /dev/null +++ b/src/lib/components/Stores/project.ts @@ -0,0 +1,3 @@ +import { type Writable, writable } from 'svelte/store'; + +export const projectPathStore: Writable = writable(''); diff --git a/src/lib/components/Utils/Storage/projectSync.ts b/src/lib/components/Utils/Storage/projectSync.ts deleted file mode 100644 index a7d33ba..0000000 --- a/src/lib/components/Utils/Storage/projectSync.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { listFiles, loadFile } from '$lib/components/Utils/Storage/fileSystem'; - -export async function createProject(formData: FormData) { - await fetch('/cli?/createProject', { - method: 'POST', - body: JSON.stringify({ - projectPath: formData.get('projectPath'), - projectName: formData.get('projectName'), - packageManager: formData.get('packageManager'), - language: formData.get('language'), - strictTypeChecking: formData.get('strictTypeChecking') ?? false, - multiplayerServer: formData.get('multiplayerServer') ?? false, - skipDependencyInstallation: formData.get('skipDependencyInstallation') ?? false, - dockerContainerization: formData.get('dockerContainerization') ?? false, - }), - }); -} - -/*export async function loadRemoteProject() { - const response = await fetch('/fs?/=readDirRec', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ dirPath: '/' }), - }); - - const result = await response.json(); - - if (!result.success) throw new Error(result.errorMsg); - - await clearDB(); - - for (const item of result.dirContent) { - if (item.type === 'file') { - const fileRes = await fetch('/fs?/=readFile', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ filePath: item.name }), - }); - const fileResult = await fileRes.json(); - - if (fileResult.success) { - await saveFile(item.name, fileResult.fileContent); - } - } - } -}*/ - -export async function pushLocalProject() { - const localFiles = await listFiles(); - - const pushPromises = localFiles.map(async (file) => { - const content = await loadFile(file.id); - - const formData = new FormData(); - formData.append('filePath', file.id); - formData.append('fileContent', content); - - await fetch('/fs?/writeFile', { - method: 'POST', - body: JSON.stringify({ - filePath: formData.get('filePath'), - fileContent: formData.get('fileContent'), - }), - }); - }); - - await Promise.all(pushPromises); -} From 8ba42ee5fa5380e730091be04caacab51a9808de Mon Sep 17 00:00:00 2001 From: bill Date: Thu, 19 Mar 2026 20:34:02 +0100 Subject: [PATCH 09/13] feat: create project list cache on project-loader --- src/lib/components/Utils/Storage/db.ts | 31 ------- .../components/Utils/Storage/fileSystem.ts | 91 ------------------- src/lib/components/Utils/Storage/tabs.ts | 9 -- 3 files changed, 131 deletions(-) delete mode 100644 src/lib/components/Utils/Storage/db.ts delete mode 100644 src/lib/components/Utils/Storage/fileSystem.ts delete mode 100644 src/lib/components/Utils/Storage/tabs.ts diff --git a/src/lib/components/Utils/Storage/db.ts b/src/lib/components/Utils/Storage/db.ts deleted file mode 100644 index 8c78816..0000000 --- a/src/lib/components/Utils/Storage/db.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { openDB } from 'idb'; - -const dbName = 'NanoForge'; -let dbPromise: Promise | null = null; - -export async function getDB() { - if (!dbPromise) { - dbPromise = openDB(dbName, 1, { - upgrade(db) { - if (!db.objectStoreNames.contains('files')) { - db.createObjectStore('files', { keyPath: 'id' }); - } - }, - }); - } - return dbPromise; -} - -export async function initDB() { - await getDB(); -} - -export async function clearDB() { - const db = await getDB(); - const tx = db.transaction('files', 'readwrite'); - const store = tx.objectStore('files'); - - await store.clear(); - - await tx.done; -} diff --git a/src/lib/components/Utils/Storage/fileSystem.ts b/src/lib/components/Utils/Storage/fileSystem.ts deleted file mode 100644 index 96aadf1..0000000 --- a/src/lib/components/Utils/Storage/fileSystem.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { getDB } from '$lib/components/Utils/Storage/db'; - -export interface File { - id: string; - lastModified: number; -} - -export interface FolderContent { - name: string; - type: 'file' | 'folder'; - lastModified?: number; -} - -export async function listFiles(): Promise> { - const db = await getDB(); - const tx = db.transaction('files', 'readonly'); - const store = tx.objectStore('files'); - - const allFiles = await store.getAll(); - - return allFiles.map((file: File) => ({ - id: file.id, - lastModified: file.lastModified, - })); -} - -export async function listFolders(): Promise { - const folders = new Set(); - const files = await listFiles(); - - for (const file of files) { - const parts = file.id.split('/'); - - parts.pop(); - - let current = ''; - for (const part of parts) { - current = current ? `${current}/${part}` : part; - folders.add(current); - } - } - - return Array.from(folders).sort(); -} - -export async function listFolderContents(path: string = ''): Promise> { - const allFiles = await listFiles(); - let cleanPath = path.startsWith('/') ? path.substring(1) : path; - cleanPath = cleanPath.endsWith('/') || cleanPath === '' ? cleanPath : cleanPath.concat('/'); - - const items = new Map(); - - const files = allFiles.filter((file) => { - const filePath = file.id; - return filePath.startsWith(cleanPath); - }); - - for (const file of files) { - const relativePath = file.id.substring(cleanPath.length); - const isFolderIndex = relativePath.indexOf('/'); - const name = isFolderIndex >= 0 ? relativePath.substring(0, isFolderIndex) : relativePath; - - items.set(name, { - name, - type: isFolderIndex >= 0 ? 'folder' : 'file', - lastModified: file.lastModified, - }); - } - - return Array.from(items.values()).sort((a, b) => { - if (a.type === 'folder' && b.type === 'file') return -1; - if (a.type === 'file' && b.type === 'folder') return 1; - return a.name.localeCompare(b.name); - }); -} - -export async function saveFile(fileName: string, content: string) { - const db = await getDB(); - const tx = db.transaction('files', 'readwrite'); - const store = tx.objectStore('files'); - await store.put({ id: fileName, content, lastModified: Date.now() }); - await tx.done; -} - -export async function loadFile(fileName: string): Promise { - const db = await getDB(); - const tx = db.transaction('files', 'readonly'); - const store = tx.objectStore('files'); - const file = await store.get(fileName); - return file?.content || ''; -} diff --git a/src/lib/components/Utils/Storage/tabs.ts b/src/lib/components/Utils/Storage/tabs.ts deleted file mode 100644 index dce3159..0000000 --- a/src/lib/components/Utils/Storage/tabs.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { getDB } from '$lib/components/Utils/Storage/db'; - -export async function saveTab(fileName: string, content: string) { - const db = await getDB(); - const tx = db.transaction('tabs', 'readwrite'); - const store = tx.objectStore('tabs'); - await store.put({ id: fileName, content, lastModified: Date.now() }); - await tx.done; -} From dab5851c25192827a33366e0eea6fce538bc31e7 Mon Sep 17 00:00:00 2001 From: bill Date: Fri, 20 Mar 2026 01:54:51 +0100 Subject: [PATCH 10/13] feat: add game controller up to screen view with play and stop --- src/lib/assets/play.png | Bin 0 -> 1386 bytes src/lib/assets/stop.png | Bin 0 -> 1028 bytes src/lib/components/Stores/projectIsUpdated.ts | 3 + src/lib/components/Utils/api/local-api.ts | 24 ++++++ src/lib/components/Utils/api/project-api.ts | 3 + .../Widget/ScreenView/ScreenViewWidget.svelte | 76 ++++++++++++++++-- 6 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 src/lib/assets/play.png create mode 100644 src/lib/assets/stop.png create mode 100644 src/lib/components/Stores/projectIsUpdated.ts diff --git a/src/lib/assets/play.png b/src/lib/assets/play.png new file mode 100644 index 0000000000000000000000000000000000000000..7a28285ea775e3e5bdf2df105fb08afc6a6d7884 GIT binary patch literal 1386 zcmV-w1(o`VP)EX>4Tx04R}tk-tmBKpe$iQ$;Bihh`9Q$WWauf{HlSDionYs1;guFuC*>G-*jv zTpR`0f`dO6s}3&Cx;nTDg5VDj{{V4PbdeIjmlRsWcyQc@clRE5?*O4$VY<~52XwmC8V-o<#9|G7WMfR?uy5Ri!DtY+H88^kl4 zc8&8svB*k_Mtn{@ZqfybAGxl2{KmQHvcNM%Gn1Yt7Kx=|7pq;&N~TUcO&ry9gYt!} z#~SA?&U&TJ+V|uy4CjrNWvL{ZE8!=i9QY@rtKjGsacKtHBRC1NU z$gzMbbSSPL{11M2YZay@y`*pg=zVdVk5Qm+7icvc=lj@kS|>p88Mrb!{%RAL{Up87 z(V|Dd;5Kk^-OpV2qvfXFQnTJw5q@8k3V$WT|Q8{ps& z7%Nity3f1&yLH`xLGAktHws-&l17}G@K~#9!?VYh}6Hy$;e|Kp~ zt2a~()uB|kfK!_yf=DkFXEV6E9iq6nxTy$&OH@#l(o#Cru@3PcxI{!K(q2&zGAaQr zMC@>_4KaC6C!t16E_b=#d++-h3%$F(Pk!(E8Tv{D0000CnY>qAyR`k}9#$e+jK06a zb;RK>r3{l=`4t1pIcy`l$bV#XGHXqfclJKSgusmBqMrYyFM*)X z2|2TISA5$48tMi|Q)$|pU&ORTIUy|y|6$*2HHchCWH;_$Vv^djx>4Q#M&vr8@ZtF+ zmYso+fYSHvopj|oriF_TBBDn77m@3Tsdu*_hJ%oRTH^j>K`u0sU_Apw=$>XlUa z8m5Jd5F(~?`+5kGsP?rH@^|gOn1m2kweRhWRc;6fVZ=eZ$*%Ub7Q(UiA%t1& zLkP3lhY)794?L$b!wV(HjYY@VS@^1C&Ahi!6 zG1fkWFk?K#3m-yaG_5tEDK=NQ4B3LT?WFz7W!-vRnBTqn^1ol8zHh(wjM$x zs%)j-Avp;vTgeFtDtn9cgpCjtDqEQ?OO?u&enM2MY-xq4TG>)=S(lH`oL056rJ9hS zvR`A(2NyCP;;%^qf`4#d#aj17?^{Qh!&Ed3JC^T|oHY#@u}?@3Qxa4*mUUP{{vjo( sY%FJzb?p_`FM5xjVkH0o0018R2Egijv@8_?G5`Po07*qoM6N<$g0nbsvH$=8 literal 0 HcmV?d00001 diff --git a/src/lib/assets/stop.png b/src/lib/assets/stop.png new file mode 100644 index 0000000000000000000000000000000000000000..bb5ccb36282803c18caf53134aceaab710374f82 GIT binary patch literal 1028 zcmeAS@N?(olHy`uVBq!ia0vp^DImb;zxQDWnWOQ+u9~9lX##~O0_N&8by-}~5?Sc9LNuz>VCNTclT6Q`7#Y^?K{A}_rv3APuTvzqy*v=Q54j8){PoAeP*10rEe0A&@uVsEVzA{%!nm@I! zIWF~F_gvM|bGN=f(dT*IJ2iFPWf@1I?uaHHPOVoOijA5L~c#`DCC7 zXMsm#F#`kF9S~;Z{yeK1D9B#o>Fdh=idk5c$)fJw>LdmRCPq&e$B>F!Z|~ZAF+0k% zeJuaO(bU@1+gldpdLx#_bNNf%F9I)*sJTjM=PNF1|G;p|Q^Cb?^&KVGgwB@zt^y(= z<&sfBmD=>-Il+@6*(kanm=wIj}XV z+*t6)x%0l}=9f;N{{JsqU(c((y(m5X-Sws$_vU5VJhS{9w^r@?TekOB;v$cJRo%Tb zMPj~We`;n{S?Sh~$?8WgU5VItU9~ZeyN8|JyEhNgfHaDaXr>HuJRe zNS{jH{m8L`Q|Id@iLbu09|c+z6gV6m1ezKYSQs5SnBcSk3yUHLkPo4q1i%V8pfp4S zZjC3x#l#}CL)MBZo_M8oxGgHOQy?X~JJC5e-<@NU3cHT9hG$t<-Ie!F)9=P!PC0bI zE-Chfl4q8gtUIf1^UgnJwz{X4<;|3L`E%y2 zV|CT}JUeD@)Y;@!e>f{w-+XH&EBjGQ-&bGV&Q4Es@fXgY1yMWp{qCz*1;zopr0D`2h;s5{u literal 0 HcmV?d00001 diff --git a/src/lib/components/Stores/projectIsUpdated.ts b/src/lib/components/Stores/projectIsUpdated.ts new file mode 100644 index 0000000..70b5d34 --- /dev/null +++ b/src/lib/components/Stores/projectIsUpdated.ts @@ -0,0 +1,3 @@ +import { type Writable, writable } from 'svelte/store'; + +export const projectIsUpdatedStore: Writable = writable(false); diff --git a/src/lib/components/Utils/api/local-api.ts b/src/lib/components/Utils/api/local-api.ts index ca330d7..a26c1a5 100644 --- a/src/lib/components/Utils/api/local-api.ts +++ b/src/lib/components/Utils/api/local-api.ts @@ -38,6 +38,30 @@ export class LocalAPI extends ProjectApi { } } + async playProject(): Promise { + const resp = await fetch('/cli?/startDevProject', { + method: 'POST', + body: JSON.stringify({}), + }); + const result = deserialize(await resp.text()); + if (result.type !== 'success') { + if (result.type === 'failure' && result.data) throw new Error(result.data.errorMsg as string); + throw new Error('Failed to start project'); + } + } + + async stopProject(): Promise { + const resp = await fetch('/cli?/stopProject', { + method: 'POST', + body: JSON.stringify({}), + }); + const result = deserialize(await resp.text()); + if (result.type !== 'success') { + if (result.type === 'failure' && result.data) throw new Error(result.data.errorMsg as string); + throw new Error('Failed to stop project'); + } + } + async uploadFiles(): Promise { const localFiles = await listFiles(); diff --git a/src/lib/components/Utils/api/project-api.ts b/src/lib/components/Utils/api/project-api.ts index 087a4e0..0b0c92c 100644 --- a/src/lib/components/Utils/api/project-api.ts +++ b/src/lib/components/Utils/api/project-api.ts @@ -7,6 +7,9 @@ export abstract class ProjectApi { abstract createProject(formData: FormData): Promise; abstract loadProject(formData: FormData): Promise; + abstract playProject(): Promise; + abstract stopProject(): Promise; + abstract uploadFiles(): Promise; abstract downloadFiles(): Promise; } diff --git a/src/lib/components/Widget/ScreenView/ScreenViewWidget.svelte b/src/lib/components/Widget/ScreenView/ScreenViewWidget.svelte index 08f6535..a7b7d7b 100644 --- a/src/lib/components/Widget/ScreenView/ScreenViewWidget.svelte +++ b/src/lib/components/Widget/ScreenView/ScreenViewWidget.svelte @@ -1,16 +1,78 @@
-
- + +
+ +
+
+
-
From 12a07521230259f4fef0d2c99110f5f200ad28ff Mon Sep 17 00:00:00 2001 From: bill Date: Fri, 20 Mar 2026 04:23:34 +0100 Subject: [PATCH 11/13] feat: create project loading progress bar --- .../ProjectLoader/CreateProject.svelte | 12 ++--- .../ProjectLoader/LoadProject.svelte | 14 ++---- .../ProjectLoader/ProgressBar.svelte | 49 +++++++++++++++++++ src/lib/components/Utils/api/local-api.ts | 36 ++++++++++---- src/lib/components/Utils/api/project-api.ts | 2 +- src/routes/load-project/+page.svelte | 36 ++++++++------ 6 files changed, 104 insertions(+), 45 deletions(-) create mode 100644 src/lib/components/ProjectLoader/ProgressBar.svelte diff --git a/src/lib/components/ProjectLoader/CreateProject.svelte b/src/lib/components/ProjectLoader/CreateProject.svelte index d2c3428..232d8a4 100644 --- a/src/lib/components/ProjectLoader/CreateProject.svelte +++ b/src/lib/components/ProjectLoader/CreateProject.svelte @@ -1,13 +1,12 @@ + + diff --git a/src/lib/components/Utils/api/local-api.ts b/src/lib/components/Utils/api/local-api.ts index a26c1a5..105395d 100644 --- a/src/lib/components/Utils/api/local-api.ts +++ b/src/lib/components/Utils/api/local-api.ts @@ -82,7 +82,7 @@ export class LocalAPI extends ProjectApi { }); } - async downloadFiles(): Promise { + async downloadFiles(): Promise[]> { const readDirResp = await fetch('/fs?/readDirRec', { method: 'POST', body: JSON.stringify({ dirPath: '/' }), @@ -97,25 +97,41 @@ export class LocalAPI extends ProjectApi { } await clearDB(); - await this._downloadDirectoryRec(readDirResult.data.dirContent as DirectoryRec); + return this._downloadDirectoryRec(readDirResult.data.dirContent as DirectoryRec); } - private async _downloadDirectoryRec(dir: DirectoryRec, currentPath: string = ''): Promise { + private async _downloadDirectoryRec( + dir: DirectoryRec, + currentPath: string = '', + ): Promise[]> { + const promises: Promise[] = []; + for (const file of dir.files) { - const fileRes = await fetch('/fs?/readFile', { + const filePromise = fetch('/fs?/readFile', { method: 'POST', body: JSON.stringify({ filePath: '/' + currentPath + file }), + }).then(async (fileRes) => { + const fileResult = deserialize(await fileRes.text()); + if (fileResult.type === 'success' && fileResult.data) { + await saveFile(currentPath + file, fileResult.data.fileContent as string); + } }); - const fileResult = deserialize(await fileRes.text()); - - if (fileResult.type === 'success' && fileResult.data) { - await saveFile(currentPath + file, fileResult.data.fileContent as string); - } + promises.push(filePromise); } + for (const [dirName, children] of Object.entries(dir.directories)) { if (dirName !== 'node_modules') { - await this._downloadDirectoryRec(children, currentPath + dirName + '/'); + const dirPromise: Promise = this._downloadDirectoryRec( + children, + currentPath + dirName + '/', + ).then((subDirPromises) => { + promises.push(...subDirPromises); + return; + }); + + promises.push(dirPromise); } } + return promises; } } diff --git a/src/lib/components/Utils/api/project-api.ts b/src/lib/components/Utils/api/project-api.ts index 0b0c92c..62edf85 100644 --- a/src/lib/components/Utils/api/project-api.ts +++ b/src/lib/components/Utils/api/project-api.ts @@ -11,5 +11,5 @@ export abstract class ProjectApi { abstract stopProject(): Promise; abstract uploadFiles(): Promise; - abstract downloadFiles(): Promise; + abstract downloadFiles(): Promise[]>; } diff --git a/src/routes/load-project/+page.svelte b/src/routes/load-project/+page.svelte index 1373d7b..3b61f47 100644 --- a/src/routes/load-project/+page.svelte +++ b/src/routes/load-project/+page.svelte @@ -1,7 +1,6 @@