feat(dataview): add an S3 browser component with column and list views, file preview, editing, and object management
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
import { html } from '@design.estate/dees-element';
|
||||
import type { IS3DataProvider, IS3Object } from './interfaces.js';
|
||||
import './dees-s3-browser.js';
|
||||
|
||||
// Mock in-memory S3 data provider for demo purposes
|
||||
class MockS3DataProvider implements IS3DataProvider {
|
||||
private objects: Map<string, { content: string; contentType: string; size: number; lastModified: string }> = new Map();
|
||||
|
||||
constructor() {
|
||||
const now = new Date().toISOString();
|
||||
// Seed with sample data
|
||||
this.objects.set('documents/readme.md', {
|
||||
content: btoa('# Welcome\n\nThis is a demo S3 browser.\n'),
|
||||
contentType: 'text/markdown',
|
||||
size: 42,
|
||||
lastModified: now,
|
||||
});
|
||||
this.objects.set('documents/config.json', {
|
||||
content: btoa('{\n "name": "demo",\n "version": "1.0.0"\n}'),
|
||||
contentType: 'application/json',
|
||||
size: 48,
|
||||
lastModified: now,
|
||||
});
|
||||
this.objects.set('documents/notes/todo.txt', {
|
||||
content: btoa('Buy milk\nFix bug #42\nDeploy to production'),
|
||||
contentType: 'text/plain',
|
||||
size: 45,
|
||||
lastModified: now,
|
||||
});
|
||||
this.objects.set('images/logo.png', {
|
||||
content: btoa('fake-png-data'),
|
||||
contentType: 'image/png',
|
||||
size: 24500,
|
||||
lastModified: now,
|
||||
});
|
||||
this.objects.set('images/banner.jpg', {
|
||||
content: btoa('fake-jpg-data'),
|
||||
contentType: 'image/jpeg',
|
||||
size: 156000,
|
||||
lastModified: now,
|
||||
});
|
||||
this.objects.set('scripts/deploy.sh', {
|
||||
content: btoa('#!/bin/bash\necho "Deploying..."\n'),
|
||||
contentType: 'text/plain',
|
||||
size: 34,
|
||||
lastModified: now,
|
||||
});
|
||||
this.objects.set('index.html', {
|
||||
content: btoa('<!DOCTYPE html>\n<html>\n<body>\n <h1>Hello World</h1>\n</body>\n</html>'),
|
||||
contentType: 'text/html',
|
||||
size: 72,
|
||||
lastModified: now,
|
||||
});
|
||||
this.objects.set('styles.css', {
|
||||
content: btoa('body { margin: 0; font-family: sans-serif; }'),
|
||||
contentType: 'text/css',
|
||||
size: 44,
|
||||
lastModified: now,
|
||||
});
|
||||
}
|
||||
|
||||
async listObjects(bucket: string, prefix?: string, delimiter?: string): Promise<{ objects: IS3Object[]; prefixes: string[] }> {
|
||||
const pfx = prefix || '';
|
||||
const objects: IS3Object[] = [];
|
||||
const prefixes = new Set<string>();
|
||||
|
||||
for (const [key, data] of this.objects) {
|
||||
if (!key.startsWith(pfx)) continue;
|
||||
const rest = key.slice(pfx.length);
|
||||
|
||||
if (delimiter) {
|
||||
const slashIndex = rest.indexOf(delimiter);
|
||||
if (slashIndex >= 0) {
|
||||
prefixes.add(pfx + rest.slice(0, slashIndex + 1));
|
||||
} else {
|
||||
objects.push({ key, size: data.size, lastModified: data.lastModified });
|
||||
}
|
||||
} else {
|
||||
objects.push({ key, size: data.size, lastModified: data.lastModified });
|
||||
}
|
||||
}
|
||||
|
||||
return { objects, prefixes: Array.from(prefixes).sort() };
|
||||
}
|
||||
|
||||
async getObject(bucket: string, key: string): Promise<{ content: string; contentType: string; size: number; lastModified: string }> {
|
||||
const obj = this.objects.get(key);
|
||||
if (!obj) throw new Error('Not found');
|
||||
return { ...obj };
|
||||
}
|
||||
|
||||
async putObject(bucket: string, key: string, base64Content: string, contentType: string): Promise<boolean> {
|
||||
this.objects.set(key, {
|
||||
content: base64Content,
|
||||
contentType,
|
||||
size: atob(base64Content).length,
|
||||
lastModified: new Date().toISOString(),
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
async deleteObject(bucket: string, key: string): Promise<boolean> {
|
||||
return this.objects.delete(key);
|
||||
}
|
||||
|
||||
async deletePrefix(bucket: string, prefix: string): Promise<boolean> {
|
||||
for (const key of this.objects.keys()) {
|
||||
if (key.startsWith(prefix)) {
|
||||
this.objects.delete(key);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async getObjectUrl(bucket: string, key: string): Promise<string> {
|
||||
const obj = this.objects.get(key);
|
||||
if (!obj) return '';
|
||||
const blob = new Blob([Uint8Array.from(atob(obj.content), c => c.charCodeAt(0))], { type: obj.contentType });
|
||||
return URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
async moveObject(bucket: string, sourceKey: string, destKey: string): Promise<{ success: boolean; error?: string }> {
|
||||
const obj = this.objects.get(sourceKey);
|
||||
if (!obj) return { success: false, error: 'Source not found' };
|
||||
this.objects.set(destKey, { ...obj, lastModified: new Date().toISOString() });
|
||||
this.objects.delete(sourceKey);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
async movePrefix(bucket: string, sourcePrefix: string, destPrefix: string): Promise<{ success: boolean; movedCount?: number; error?: string }> {
|
||||
let count = 0;
|
||||
const toMove = Array.from(this.objects.entries()).filter(([k]) => k.startsWith(sourcePrefix));
|
||||
for (const [key, data] of toMove) {
|
||||
const newKey = destPrefix + key.slice(sourcePrefix.length);
|
||||
this.objects.set(newKey, { ...data, lastModified: new Date().toISOString() });
|
||||
this.objects.delete(key);
|
||||
count++;
|
||||
}
|
||||
return { success: true, movedCount: count };
|
||||
}
|
||||
}
|
||||
|
||||
export const demoFunc = () => html`
|
||||
<style>
|
||||
.demo-container {
|
||||
height: 600px;
|
||||
padding: 16px;
|
||||
}
|
||||
</style>
|
||||
<div class="demo-container">
|
||||
<dees-s3-browser
|
||||
.dataProvider=${new MockS3DataProvider()}
|
||||
.bucketName=${'demo-bucket'}
|
||||
></dees-s3-browser>
|
||||
</div>
|
||||
`;
|
||||
Reference in New Issue
Block a user