♻️ Makes use of tailwind for styling

This commit is contained in:
Rune Harlyk
2026-01-22 20:04:28 +01:00
committed by Rune Harlyk
parent 4ac54279a8
commit f10406b29c
@@ -10,6 +10,10 @@
let uploadProgress: TransferProgress | null = null let uploadProgress: TransferProgress | null = null
let downloadProgress: TransferProgress | null = null let downloadProgress: TransferProgress | null = null
const joinPath = (name: string) => (currentPath === '/' ? '/' + name : currentPath + '/' + name)
const getError = (e: unknown, fallback: string) =>
e instanceof Error ? e.message : (e as { error?: string })?.error || fallback
async function loadDirectory() { async function loadDirectory() {
loading = true loading = true
error = '' error = ''
@@ -22,7 +26,7 @@
error = result.error || 'Failed to load directory' error = result.error || 'Failed to load directory'
} }
} catch (e) { } catch (e) {
error = e instanceof Error ? e.message : 'Unknown error' error = getError(e, 'Unknown error')
} finally { } finally {
loading = false loading = false
} }
@@ -41,26 +45,16 @@
uploadProgress = null uploadProgress = null
error = '' error = ''
const destinationPath = currentPath.endsWith('/')
? currentPath + file.name
: currentPath + '/' + file.name
try { try {
const result = await fileSystemClient.uploadFileFromBrowser( const result = await fileSystemClient.uploadFileFromBrowser(
destinationPath, joinPath(file.name),
file, file,
(progress) => { p => (uploadProgress = p)
uploadProgress = progress
}
) )
if (result.success) await loadDirectory()
if (result.success) { else error = result.error || 'Upload failed'
await loadDirectory()
} else {
error = result.error || 'Upload failed'
}
} catch (e) { } catch (e) {
error = e instanceof Error ? e.message : 'Upload error' error = getError(e, 'Upload error')
} finally { } finally {
uploadProgress = null uploadProgress = null
input.value = '' input.value = ''
@@ -71,20 +65,15 @@
downloadProgress = null downloadProgress = null
error = '' error = ''
const filePath = currentPath.endsWith('/')
? currentPath + filename
: currentPath + '/' + filename
try { try {
const result = await fileSystemClient.downloadFileAndSave(filePath, filename, (progress) => { const result = await fileSystemClient.downloadFileAndSave(
downloadProgress = progress joinPath(filename),
}) filename,
p => (downloadProgress = p)
if (!result.success) { )
error = result.error || 'Download failed' if (!result.success) error = result.error || 'Download failed'
}
} catch (e) { } catch (e) {
error = e instanceof Error ? e.message : 'Download error' error = getError(e, 'Download error')
} finally { } finally {
downloadProgress = null downloadProgress = null
} }
@@ -92,38 +81,28 @@
async function handleDelete(name: string, isDirectory: boolean) { async function handleDelete(name: string, isDirectory: boolean) {
if (!confirm(`Delete ${isDirectory ? 'directory' : 'file'} "${name}"?`)) return if (!confirm(`Delete ${isDirectory ? 'directory' : 'file'} "${name}"?`)) return
error = '' error = ''
const path = currentPath.endsWith('/') ? currentPath + name : currentPath + '/' + name
try { try {
const result = await fileSystemClient.deleteFile(path) const result = await fileSystemClient.deleteFile(joinPath(name))
if (result.success) { if (result.success) await loadDirectory()
await loadDirectory() else error = result.error || 'Delete failed'
} else {
error = result.error || 'Delete failed'
}
} catch (e) { } catch (e) {
error = e instanceof Error ? e.message : 'Delete error' error = getError(e, 'Delete error')
} }
} }
async function handleCreateDirectory() { async function handleCreateDirectory() {
const name = prompt('Enter directory name:') const name = prompt('Enter directory name:')
if (!name) return if (!name) return
error = '' error = ''
const path = currentPath.endsWith('/') ? currentPath + name : currentPath + '/' + name
try { try {
const result = await fileSystemClient.createDirectory(path) const result = await fileSystemClient.createDirectory(joinPath(name))
if (result.success) { if (result.success) await loadDirectory()
await loadDirectory() else error = result.error || 'Failed to create directory'
} else {
error = result.error || 'Failed to create directory'
}
} catch (e) { } catch (e) {
error = e instanceof Error ? e.message : 'Error creating directory' error = getError(e, 'Error creating directory')
} }
} }
@@ -135,240 +114,114 @@
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
} }
onMount(() => { onMount(loadDirectory)
loadDirectory()
})
</script> </script>
<div class="file-manager"> <div class="max-w-3xl mx-auto my-8 p-4 border border-gray-300 rounded-lg bg-white">
<div class="toolbar"> <div class="mb-4">
<h2>File Manager</h2> <h2 class="m-0 mb-2">File Manager</h2>
<div class="path">Current: {currentPath}</div> <div class="font-mono bg-gray-100 p-2 rounded mb-2">Current: {currentPath}</div>
<div class="actions"> <div class="flex gap-2">
<button on:click={handleCreateDirectory}>New Folder</button> <button
<label class="upload-btn"> class="px-4 py-2 bg-blue-600 text-white rounded cursor-pointer hover:bg-blue-700"
on:click={handleCreateDirectory}>New Folder</button
>
<label
class="px-4 py-2 bg-blue-600 text-white rounded cursor-pointer hover:bg-blue-700"
>
Upload File Upload File
<input type="file" on:change={handleFileUpload} style="display: none;" /> <input type="file" on:change={handleFileUpload} class="hidden" />
</label> </label>
<button on:click={loadDirectory}>Refresh</button> <button
class="px-4 py-2 bg-blue-600 text-white rounded cursor-pointer hover:bg-blue-700"
on:click={loadDirectory}>Refresh</button
>
</div> </div>
</div> </div>
{#if error} {#if error}
<div class="error">{error}</div> <div class="bg-red-100 text-red-800 p-3 rounded mb-4">{error}</div>
{/if} {/if}
{#if uploadProgress} {#if uploadProgress}
<div class="progress"> <div class="mb-4">
<div class="progress-label"> <div class="mb-2 text-sm">
Uploading: {uploadProgress.percentage.toFixed(1)}% ({formatBytes( Uploading: {uploadProgress.percentage.toFixed(1)}% ({formatBytes(
uploadProgress.bytesTransferred uploadProgress.bytesTransferred
)} / {formatBytes(uploadProgress.totalBytes)}) )} / {formatBytes(uploadProgress.totalBytes)})
</div> </div>
<div class="progress-bar"> <div class="h-5 bg-gray-200 rounded overflow-hidden">
<div class="progress-fill" style="width: {uploadProgress.percentage}%"></div> <div
class="h-full bg-green-600 transition-all duration-300"
style="width: {uploadProgress.percentage}%"
></div>
</div> </div>
</div> </div>
{/if} {/if}
{#if downloadProgress} {#if downloadProgress}
<div class="progress"> <div class="mb-4">
<div class="progress-label"> <div class="mb-2 text-sm">
Downloading: {downloadProgress.percentage.toFixed(1)}% ({formatBytes( Downloading: {downloadProgress.percentage.toFixed(1)}% ({formatBytes(
downloadProgress.bytesTransferred downloadProgress.bytesTransferred
)} / {formatBytes(downloadProgress.totalBytes)}) )} / {formatBytes(downloadProgress.totalBytes)})
</div> </div>
<div class="progress-bar"> <div class="h-5 bg-gray-200 rounded overflow-hidden">
<div class="progress-fill" style="width: {downloadProgress.percentage}%"></div> <div
class="h-full bg-green-600 transition-all duration-300"
style="width: {downloadProgress.percentage}%"
></div>
</div> </div>
</div> </div>
{/if} {/if}
<div class="file-list"> <div class="border border-gray-300 rounded min-h-[200px]">
{#if loading} {#if loading}
<div class="loading">Loading...</div> <div class="text-center p-8 text-gray-500">Loading...</div>
{:else} {:else}
{#if currentPath !== '/'} {#if currentPath !== '/'}
<div class="file-item directory" on:click={() => navigateTo('/')}> <div
<span class="icon">📁</span> class="flex items-center p-3 border-b border-gray-100 gap-2 bg-gray-50 cursor-pointer"
<span class="name">..</span> on:click={() => navigateTo('/')}
>
<span class="text-2xl">📁</span>
<span class="flex-1 hover:underline">..</span>
</div> </div>
{/if} {/if}
{#each directories as dir} {#each directories as dir}
<div class="file-item directory"> <div class="flex items-center p-3 border-b border-gray-100 gap-2 bg-gray-50">
<span class="icon">📁</span> <span class="text-2xl">📁</span>
<span class="name" on:click={() => navigateTo(currentPath + '/' + dir.name)} <span
>{dir.name}</span class="flex-1 cursor-pointer hover:underline"
on:click={() => navigateTo(currentPath + '/' + dir.name)}>{dir.name}</span
>
<button
class="px-3 py-1 bg-red-600 text-white rounded text-sm hover:bg-red-700"
on:click={() => handleDelete(dir.name, true)}>Delete</button
> >
<button class="delete-btn" on:click={() => handleDelete(dir.name, true)}>Delete</button>
</div> </div>
{/each} {/each}
{#each files as file} {#each files as file}
<div class="file-item"> <div class="flex items-center p-3 border-b border-gray-100 gap-2 last:border-b-0">
<span class="icon">📄</span> <span class="text-2xl">📄</span>
<span class="name">{file.name}</span> <span class="flex-1">{file.name}</span>
<span class="size">{formatBytes(file.size)}</span> <span class="text-gray-500 text-sm">{formatBytes(file.size)}</span>
<button class="download-btn" on:click={() => handleDownload(file.name)}>Download</button> <button
<button class="delete-btn" on:click={() => handleDelete(file.name, false)}>Delete</button> class="px-3 py-1 bg-green-600 text-white rounded text-sm hover:bg-green-700"
on:click={() => handleDownload(file.name)}>Download</button
>
<button
class="px-3 py-1 bg-red-600 text-white rounded text-sm hover:bg-red-700"
on:click={() => handleDelete(file.name, false)}>Delete</button
>
</div> </div>
{/each} {/each}
{#if files.length === 0 && directories.length === 0} {#if files.length === 0 && directories.length === 0}
<div class="empty">Directory is empty</div> <div class="text-center p-8 text-gray-500">Directory is empty</div>
{/if} {/if}
{/if} {/if}
</div> </div>
</div> </div>
<style>
.file-manager {
max-width: 800px;
margin: 2rem auto;
padding: 1rem;
border: 1px solid #ddd;
border-radius: 8px;
background: white;
}
.toolbar {
margin-bottom: 1rem;
}
.toolbar h2 {
margin: 0 0 0.5rem 0;
}
.path {
font-family: monospace;
background: #f5f5f5;
padding: 0.5rem;
border-radius: 4px;
margin-bottom: 0.5rem;
}
.actions {
display: flex;
gap: 0.5rem;
}
.actions button,
.upload-btn {
padding: 0.5rem 1rem;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.actions button:hover,
.upload-btn:hover {
background: #0056b3;
}
.error {
background: #f8d7da;
color: #721c24;
padding: 0.75rem;
border-radius: 4px;
margin-bottom: 1rem;
}
.progress {
margin-bottom: 1rem;
}
.progress-label {
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
.progress-bar {
height: 20px;
background: #e9ecef;
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: #28a745;
transition: width 0.3s ease;
}
.file-list {
border: 1px solid #ddd;
border-radius: 4px;
min-height: 200px;
}
.file-item {
display: flex;
align-items: center;
padding: 0.75rem;
border-bottom: 1px solid #eee;
gap: 0.5rem;
}
.file-item:last-child {
border-bottom: none;
}
.file-item.directory {
background: #f8f9fa;
}
.icon {
font-size: 1.5rem;
}
.name {
flex: 1;
cursor: pointer;
}
.name:hover {
text-decoration: underline;
}
.size {
color: #6c757d;
font-size: 0.9rem;
}
.download-btn,
.delete-btn {
padding: 0.25rem 0.75rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
}
.download-btn {
background: #28a745;
color: white;
}
.download-btn:hover {
background: #218838;
}
.delete-btn {
background: #dc3545;
color: white;
}
.delete-btn:hover {
background: #c82333;
}
.loading,
.empty {
text-align: center;
padding: 2rem;
color: #6c757d;
}
</style>