This commit is contained in:
metacryst
2026-04-28 20:05:00 -05:00
commit 0d6c7683ff
123 changed files with 20922 additions and 0 deletions

View File

@@ -0,0 +1,408 @@
class DesktopFilesGrid extends Shadow {
constructor(files, groups, viewMode, onFileClick) {
super()
this.files = files
this.groups = groups
this.viewMode = viewMode
this.onFileClick = onFileClick
}
render() {
if (this.files.length === 0) {
VStack(() => {
p("🗂")
.margin(0)
.fontSize(2.5, em)
.opacity(0.18)
p("No files here")
.margin(0)
.marginTop(0.65, em)
.fontSize(0.9, em)
.color("var(--headertext)")
.opacity(0.32)
})
.flex(1)
.justifyContent("center")
.alignItems("center")
.height(100, pct)
return;
}
if (this.viewMode === "list") {
this.renderListView()
} else {
this.renderGridView()
}
}
renderListView() {
VStack(() => {
// Header row
HStack(() => {
p("Name") .styles(this.colHeader).flex(1)
p("Shared with").styles(this.colHeader).width(160, px).flexShrink(0)
p("Modified") .styles(this.colHeader).width(130, px).flexShrink(0)
p("Size") .styles(this.colHeader).width(80, px).flexShrink(0)
p("Group") .styles(this.colHeader).width(120, px).flexShrink(0)
})
.paddingHorizontal(1.25, em)
.paddingVertical(0.45, em)
.borderBottom("1px solid var(--divider)")
.flexShrink(0)
VStack(() => {
this.files.forEach((file, i) => this.renderListRow(file, i))
})
.overflowY("auto")
.flex(1)
})
.width(100, pct)
.height(100, pct)
.overflow("hidden")
}
renderListRow(file, index) {
const self = this
HStack(() => {
// Icon + name
HStack(() => {
p(this.fileIcon(file))
.margin(0)
.fontSize(1.15, em)
.lineHeight("1")
.flexShrink(0)
VStack(() => {
p(file.name)
.margin(0)
.fontSize(0.88, em)
.fontWeight("500")
.color("var(--headertext)")
.overflow("hidden")
.whiteSpace("nowrap")
.textOverflow("ellipsis")
})
.flex(1)
.minWidth(0)
})
.gap(0.6, em)
.flex(1)
.minWidth(0)
.alignItems("center")
// Active editors badge
HStack(() => {
if (file.activeEditors && file.activeEditors.length > 0) {
this.renderActiveEditors(file.activeEditors, "list")
} else if (file.sharedWith && file.sharedWith.length > 0) {
this.renderSharedAvatars(file.sharedWith)
} else {
p("—").margin(0).fontSize(0.8, em).color("var(--headertext)").opacity(0.2)
}
})
.width(160, px)
.flexShrink(0)
.alignItems("center")
.gap(0.3, em)
// Modified
p(this.relativeDate(file.modifiedAt))
.margin(0)
.fontSize(0.78, em)
.color("var(--headertext)")
.opacity(0.45)
.width(130, px)
.flexShrink(0)
.overflow("hidden")
.whiteSpace("nowrap")
// Size
p(file.size || "—")
.margin(0)
.fontSize(0.78, em)
.color("var(--headertext)")
.opacity(0.45)
.width(80, px)
.flexShrink(0)
// Group tag
HStack(() => {
const group = this.groups.find(g => g.id === file.groupId);
if (group) {
VStack(() => {})
.width(0.5, em).height(0.5, em)
.borderRadius(50, pct)
.background(group.color)
.flexShrink(0)
p(group.name)
.margin(0)
.fontSize(0.72, em)
.color("var(--headertext)")
.opacity(0.55)
.overflow("hidden")
.whiteSpace("nowrap")
.textOverflow("ellipsis")
}
})
.gap(0.38, em)
.width(120, px)
.flexShrink(0)
.alignItems("center")
.minWidth(0)
})
.paddingHorizontal(1.25, em)
.paddingVertical(0.62, em)
.borderBottom("1px solid var(--divider)")
.cursor("pointer")
.alignItems("center")
.width(100, pct)
.boxSizing("border-box")
.onClick(function(done){ if(done){ self.onFileClick(file) } })
}
renderGridView() {
VStack(() => {
ZStack(() => {
this.files.forEach(file => this.renderGridCard(file))
})
.display("grid")
.attr({ style: "display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 1em;" })
})
.padding(1.5, em)
.overflowY("auto")
.height(100, pct)
.width(100, pct)
.boxSizing("border-box")
}
renderGridCard(file) {
VStack(() => {
// Thumbnail area
ZStack(() => {
// File type large icon
VStack(() => {
p(this.fileIcon(file))
.margin(0)
.fontSize(2.4, em)
.lineHeight("1")
})
.width(100, pct)
.height(100, pct)
.justifyContent("center")
.alignItems("center")
.background(this.fileTint(file))
// Active editor badge (top-right)
if (file.activeEditors && file.activeEditors.length > 0) {
VStack(() => {
this.renderActiveEditors(file.activeEditors, "grid")
})
.position("absolute")
.top(0.45, em)
.right(0.45, em)
}
// Group color stripe (bottom of thumbnail)
VStack(() => {
const group = this.groups.find(g => g.id === file.groupId);
if (group) {
VStack(() => {})
.width(100, pct)
.height(3, px)
.background(group.color)
}
})
.position("absolute")
.bottom(0).left(0).right(0)
})
.position("relative")
.width(100, pct)
.height(6.5, em)
.borderRadius("0.5em 0.5em 0 0")
.overflow("hidden")
.flexShrink(0)
// Info area
VStack(() => {
p(file.name)
.margin(0)
.fontSize(0.8, em)
.fontWeight("600")
.color("var(--headertext)")
.overflow("hidden")
.whiteSpace("nowrap")
.textOverflow("ellipsis")
.width(100, pct)
p(this.relativeDate(file.modifiedAt))
.margin(0)
.marginTop(0.18, em)
.fontSize(0.68, em)
.color("var(--headertext)")
.opacity(0.38)
})
.padding(0.65, em)
.background("var(--darkaccent)")
.borderRadius("0 0 0.5em 0.5em")
.width(100, pct)
.boxSizing("border-box")
})
.border("1px solid var(--divider)")
.borderRadius(0.5, em)
.overflow("hidden")
.cursor("pointer")
.onClick((done) => {if(!done) return; this.onFileClick(file)})
}
renderActiveEditors(editors, context) {
const isGrid = context === "grid";
HStack(() => {
// Pulsing green dot
ZStack(() => {
VStack(() => {})
.width(isGrid ? 0.55 : 0.48, em)
.height(isGrid ? 0.55 : 0.48, em)
.borderRadius(50, pct)
.background("#22c55e")
.opacity(0.4)
.attr({ style: "animation: pulse-ring 1.5s ease-out infinite;" })
VStack(() => {})
.width(isGrid ? 0.4 : 0.35, em)
.height(isGrid ? 0.4 : 0.35, em)
.borderRadius(50, pct)
.background("#22c55e")
.position("absolute")
})
.position("relative")
.width(isGrid ? 0.55 : 0.48, em)
.height(isGrid ? 0.55 : 0.48, em)
.flexShrink(0)
// Editor avatar stack
HStack(() => {
editors.slice(0, 3).forEach((editor, i) => {
VStack(() => {
p(editor[0].toUpperCase())
.margin(0)
.fontSize(isGrid ? 0.5 : 0.45, em)
.fontWeight("700")
.color("white")
.lineHeight("1")
})
.width(isGrid ? 1.4 : 1.25, em)
.height(isGrid ? 1.4 : 1.25, em)
.borderRadius(50, pct)
.background(this.avatarColor(editor))
.justifyContent("center")
.alignItems("center")
.boxSizing("border-box")
.flexShrink(0)
.marginLeft(i > 0 ? -0.4 : 0, em)
})
})
.alignItems("center")
if (!isGrid) {
p(editors.length === 1
? `${editors[0]} is editing`
: `${editors.length} editing`)
.margin(0)
.fontSize(0.72, em)
.color("#22c55e")
.fontWeight("500")
.whiteSpace("nowrap")
}
})
.gap(isGrid ? 0.25 : 0.45, em)
.alignItems("center")
.paddingHorizontal(isGrid ? 0.4 : 0)
.paddingVertical(isGrid ? 0.22 : 0)
.background(isGrid ? "rgba(0,0,0,0.55)" : "transparent")
.borderRadius(isGrid ? 100 : 0, px)
}
renderSharedAvatars(people) {
HStack(() => {
people.slice(0, 4).forEach((name, i) => {
VStack(() => {
p(name[0].toUpperCase())
.margin(0)
.fontSize(0.45, em)
.fontWeight("700")
.color("white")
.lineHeight("1")
})
.width(1.25, em)
.height(1.25, em)
.borderRadius(50, pct)
.background(this.avatarColor(name))
.justifyContent("center")
.alignItems("center")
.boxSizing("border-box")
.flexShrink(0)
.marginLeft(i > 0 ? -0.38 : 0, em)
})
})
.alignItems("center")
}
colHeader(el) {
return el
.margin(0)
.fontSize(0.72, em)
.fontWeight("600")
.letterSpacing("0.03em")
.color("var(--headertext)")
.opacity(0.4)
}
fileIcon(file) {
const ext = file.name.split(".").pop().toLowerCase();
const map = {
pdf: "📄", doc: "📝", docx: "📝", txt: "📃", md: "📃",
xls: "📊", xlsx: "📊", csv: "📊",
ppt: "📋", pptx: "📋",
jpg: "🖼", jpeg: "🖼", png: "🖼", gif: "🖼", svg: "🖼", webp: "🖼",
mp4: "🎬", mov: "🎬", avi: "🎬",
mp3: "🎵", wav: "🎵",
zip: "🗜", rar: "🗜",
js: "⚙️", ts: "⚙️", py: "⚙️", json: "⚙️",
folder: "📁",
};
if (file.type === "folder") return "📁";
return map[ext] || "📄";
}
fileTint(file) {
const ext = file.name.split(".").pop().toLowerCase();
const tints = {
pdf: "rgba(239,68,68,0.08)", doc: "rgba(59,130,246,0.08)", docx: "rgba(59,130,246,0.08)",
xls: "rgba(16,185,129,0.08)", xlsx: "rgba(16,185,129,0.08)", csv: "rgba(16,185,129,0.08)",
jpg: "rgba(245,158,11,0.08)", jpeg: "rgba(245,158,11,0.08)", png: "rgba(245,158,11,0.08)",
mp4: "rgba(139,92,246,0.08)", mov: "rgba(139,92,246,0.08)",
zip: "rgba(107,114,128,0.08)",
};
if (file.type === "folder") return "rgba(59,130,246,0.06)";
return tints[ext] || "var(--darkaccent)";
}
relativeDate(date) {
const d = new Date(date);
const days = Math.floor((Date.now() - d) / 86400000);
if (days === 0) return "Today";
if (days === 1) return "Yesterday";
if (days < 7) return `${days} days ago`;
return d.toLocaleDateString([], { month: "short", day: "numeric", year: "numeric" });
}
avatarColor(name) {
const colors = ["#3b82f6", "#ef4444", "#10b981", "#f59e0b", "#8b5cf6", "#ec4899", "#06b6d4", "#84cc16"];
let hash = 0;
for (let i = 0; i < name.length; i++) hash = name.charCodeAt(i) + ((hash << 5) - hash);
return colors[Math.abs(hash) % colors.length];
}
}
register(DesktopFilesGrid)

View File

@@ -0,0 +1,175 @@
class DesktopFilesSidebar extends Shadow {
constructor(groups, activeGroupIds, onToggleGroup, locations, activeLocation, onSelectLocation) {
super()
this.groups = groups
this.activeGroupIds = activeGroupIds
this.onToggleGroup = onToggleGroup
this.locations = locations
this.activeLocation = activeLocation
this.onSelectLocation = onSelectLocation
}
render() {
const self = this
VStack(() => {
// ── Locations ─────────────────────────────────────────────
VStack(() => {
this.sectionLabel("BROWSE")
this.locations.forEach(loc => {
const isActive = this.activeLocation === loc.id;
HStack(() => {
p(loc.icon)
.margin(0)
.fontSize(0.88, em)
.lineHeight("1")
.flexShrink(0)
p(loc.label)
.margin(0)
.fontSize(0.88, em)
.fontWeight(isActive ? "600" : "400")
.color("var(--headertext)")
.opacity(isActive ? 1 : 0.75)
})
.gap(0.6, em)
.paddingHorizontal(0.85, em)
.paddingVertical(0.42, em)
.marginHorizontal(0.4, em)
.borderRadius(0.45, em)
.background(isActive ? "var(--app)" : "transparent")
.cursor("pointer")
.alignItems("center")
.width("calc(100% - 0.8em)")
.boxSizing("border-box")
.onClick(function(done){ if(done){ self.onSelectLocation(loc.id) } })
})
})
.paddingTop(0.9, em)
// ── Divider ───────────────────────────────────────────────
VStack(() => {})
.height(1, px)
.background("var(--divider)")
.marginVertical(0.65, em)
.marginHorizontal(1, em)
// ── Permission groups ─────────────────────────────────────
VStack(() => {
this.sectionLabel("PERMISSION GROUPS")
this.groups.forEach(group => {
const isOn = this.activeGroupIds.has(group.id);
HStack(() => {
// Colored dot / checkbox area
HStack(() => {
VStack(() => {
if (isOn) {
p("✓")
.margin(0)
.fontSize(0.6, em)
.fontWeight("800")
.color("white")
.lineHeight("1")
}
})
.width(0.95, em)
.height(0.95, em)
.borderRadius(0.22, em)
.background(isOn ? group.color : "transparent")
.border(`1.5px solid ${group.color}`)
.justifyContent("center")
.alignItems("center")
.boxSizing("border-box")
.flexShrink(0)
})
.cursor("pointer")
p(group.name)
.margin(0)
.fontSize(0.85, em)
.fontWeight("400")
.color("var(--headertext)")
.opacity(isOn ? 1 : 0.45)
.flex(1)
.minWidth(0)
.overflow("hidden")
.whiteSpace("nowrap")
.textOverflow("ellipsis")
p(`${group.fileCount}`)
.margin(0)
.fontSize(0.68, em)
.color("var(--headertext)")
.opacity(0.3)
.flexShrink(0)
})
.gap(0.6, em)
.paddingHorizontal(0.85, em)
.paddingVertical(0.38, em)
.marginHorizontal(0.4, em)
.borderRadius(0.45, em)
.cursor("pointer")
.alignItems("center")
.width("calc(100% - 0.8em)")
.boxSizing("border-box")
.onClick(function(done){ if(done){ self.onToggleGroup(group.id) } })
})
})
VStack(() => {}).flex(1)
// ── Storage usage ─────────────────────────────────────────
VStack(() => {
HStack(() => {
p("Storage")
.margin(0)
.fontSize(0.75, em)
.color("var(--headertext)")
.opacity(0.45)
.flex(1)
p("4.2 GB / 10 GB")
.margin(0)
.fontSize(0.7, em)
.color("var(--headertext)")
.opacity(0.3)
})
.alignItems("center")
.marginBottom(0.45, em)
// Bar
VStack(() => {
VStack(() => {})
.width(42, pct)
.height(100, pct)
.background("var(--quillred)")
.borderRadius(100, px)
})
.width(100, pct)
.height(4, px)
.background("var(--darkaccent)")
.borderRadius(100, px)
.overflow("hidden")
})
.paddingHorizontal(1.1, em)
.paddingBottom(1.1, em)
.flexShrink(0)
})
.height(100, pct)
.width(100, pct)
.boxSizing("border-box")
.overflowY("auto")
}
sectionLabel(text) {
p(text)
.margin(0)
.marginBottom(0.25, em)
.paddingHorizontal(1.1, em)
.fontSize(0.62, em)
.fontWeight("700")
.letterSpacing("0.07em")
.color("var(--headertext)")
.opacity(0.35)
}
}
register(DesktopFilesSidebar)

View File

@@ -0,0 +1,98 @@
class DesktopFilesToolbar extends Shadow {
constructor(title, viewMode, searchText, onViewChange, onSearch, onUpload) {
super()
this.title = title
this.viewMode = viewMode
this.searchText = searchText
this.onViewChange = onViewChange
this.onSearch = onSearch
this.onUpload = onUpload
}
render() {
const self = this
HStack(() => {
// Title
p(this.title)
.margin(0)
.fontSize(1.05, em)
.fontWeight("700")
.color("var(--headertext)")
.flex(1)
.minWidth(0)
// Search
HStack(() => {
p("🔍")
.margin(0)
.fontSize(0.8, em)
.opacity(0.38)
.flexShrink(0)
input("", "200px")
.attr({ type: "text", placeholder: "Search files…", value: this.searchText })
.border("none")
.outline("none")
.background("transparent")
.color("var(--headertext)")
.fontSize(0.85, em)
.onInput((e) => this.onSearch(e.target.value))
})
.gap(0.5, em)
.paddingHorizontal(0.8, em)
.paddingVertical(0.52, em)
.background("var(--darkaccent)")
.border("1px solid var(--divider)")
.borderRadius(0.5, em)
.alignItems("center")
// View toggle
HStack(() => {
this.viewBtn("list", "☰")
this.viewBtn("grid", "⊞")
})
.background("var(--darkaccent)")
.border("1px solid var(--divider)")
.borderRadius(0.5, em)
.overflow("hidden")
// Upload
button("+ Upload")
.paddingHorizontal(1, em)
.paddingVertical(0.52, em)
.background("var(--quillred)")
.color("white")
.border("none")
.borderRadius(0.5, em)
.fontSize(0.85, em)
.fontWeight("600")
.cursor("pointer")
.flexShrink(0)
.onClick(function(done){ if(done){ self.onUpload() } })
})
.gap(0.65, em)
.paddingHorizontal(1.5, em)
.paddingVertical(0.85, em)
.borderBottom("1px solid var(--divider)")
.alignItems("center")
.width(100, pct)
.boxSizing("border-box")
.flexShrink(0)
}
viewBtn(mode, icon) {
const self = this
const isActive = this.viewMode === mode;
button(icon)
.paddingHorizontal(0.65, em)
.paddingVertical(0.45, em)
.background(isActive ? "var(--app)" : "transparent")
.color("var(--headertext)")
.border("none")
.cursor("pointer")
.fontSize(0.95, em)
.opacity(isActive ? 1 : 0.45)
.onClick(function(done){ if(done){ self.onViewChange(mode) } })
}
}
register(DesktopFilesToolbar)

159
files/desktop/files.js Normal file
View File

@@ -0,0 +1,159 @@
import "./DesktopFilesSidebar.js"
import "./DesktopFilesToolbar.js"
import "./DesktopFilesGrid.js"
css(`
files- {
font-family: 'Arial';
scrollbar-width: none;
-ms-overflow-style: none;
}
@keyframes pulse-ring {
0% { transform: scale(1); opacity: 0.5; }
100% { transform: scale(2.2); opacity: 0; }
}
`)
class Files extends Shadow {
constructor() {
super()
this.viewMode = "list"
this.searchText = ""
this.activeLocation = "all"
this.groups = [
{ id: 1, name: "All Members", color: "#3b82f6", fileCount: 24 },
{ id: 2, name: "Admins", color: "#ef4444", fileCount: 8 },
{ id: 3, name: "Editors", color: "#10b981", fileCount: 15 },
{ id: 4, name: "Viewers", color: "#f59e0b", fileCount: 19 },
{ id: 5, name: "External", color: "#8b5cf6", fileCount: 4 },
]
this.activeGroupIds = new Set(this.groups.map(g => g.id))
this.locations = [
{ id: "all", label: "All Files", icon: "🗂" },
{ id: "mine", label: "My Files", icon: "👤" },
{ id: "shared", label: "Shared with Me", icon: "👥" },
{ id: "recent", label: "Recent", icon: "🕐" },
{ id: "starred", label: "Starred", icon: "⭐" },
{ id: "trash", label: "Trash", icon: "🗑" },
]
const ago = (d) => new Date(Date.now() - d * 86400000);
this.files = [
// Folders
{ id: 1, type: "folder", name: "Design Assets", groupId: 3, modifiedAt: ago(0), size: "—", sharedWith: ["Sarah McIntyre", "Jordan Kim", "Marcus Webb"], activeEditors: [], ownerId: 1, starred: true },
{ id: 2, type: "folder", name: "Engineering Docs", groupId: 2, modifiedAt: ago(1), size: "—", sharedWith: ["Marcus Webb"], activeEditors: [], ownerId: 1, starred: false },
// PDFs
{ id: 3, type: "file", name: "Q2 Board Presentation.pdf", groupId: 2, modifiedAt: ago(0), size: "4.2 MB", sharedWith: ["Priya Anand", "Marcus Webb"], activeEditors: ["Sarah McIntyre"], ownerId: 1, starred: true },
{ id: 4, type: "file", name: "Brand Guidelines.pdf", groupId: 1, modifiedAt: ago(3), size: "12.8 MB", sharedWith: ["Sarah McIntyre", "Jordan Kim"], activeEditors: [], ownerId: 2, starred: false },
{ id: 5, type: "file", name: "Legal NDA Template.pdf", groupId: 2, modifiedAt: ago(14), size: "0.9 MB", sharedWith: ["Priya Anand"], activeEditors: [], ownerId: 1, starred: false },
// Docs
{ id: 6, type: "file", name: "Product Roadmap.docx", groupId: 3, modifiedAt: ago(0), size: "1.1 MB", sharedWith: ["Sarah McIntyre", "Priya Anand", "Jordan Kim"], activeEditors: ["Marcus Webb", "Priya Anand"], ownerId: 1, starred: true },
{ id: 7, type: "file", name: "Sprint 22 Notes.md", groupId: 3, modifiedAt: ago(1), size: "18 KB", sharedWith: ["Marcus Webb"], activeEditors: [], ownerId: 3, starred: false },
{ id: 8, type: "file", name: "Onboarding Checklist.docx", groupId: 1, modifiedAt: ago(5), size: "245 KB", sharedWith: ["Sarah McIntyre", "Priya Anand"], activeEditors: [], ownerId: 1, starred: false },
// Spreadsheets
{ id: 9, type: "file", name: "Budget 2026.xlsx", groupId: 2, modifiedAt: ago(2), size: "3.3 MB", sharedWith: ["Priya Anand"], activeEditors: ["Jordan Kim"], ownerId: 1, starred: true },
{ id: 10, type: "file", name: "Member Directory.csv", groupId: 4, modifiedAt: ago(7), size: "88 KB", sharedWith: ["Priya Anand", "Marcus Webb", "Sarah McIntyre"], activeEditors: [], ownerId: 2, starred: false },
{ id: 11, type: "file", name: "Event Attendance.xlsx", groupId: 3, modifiedAt: ago(10), size: "1.4 MB", sharedWith: [], activeEditors: [], ownerId: 3, starred: false },
// Images
{ id: 12, type: "file", name: "Logo Final.png", groupId: 1, modifiedAt: ago(21), size: "2.1 MB", sharedWith: ["Jordan Kim", "Sarah McIntyre"], activeEditors: [], ownerId: 2, starred: false },
{ id: 13, type: "file", name: "Homepage Hero.jpg", groupId: 3, modifiedAt: ago(4), size: "5.6 MB", sharedWith: ["Sarah McIntyre"], activeEditors: ["Sarah McIntyre"], ownerId: 1, starred: false },
{ id: 14, type: "file", name: "Team Photo 2026.jpg", groupId: 1, modifiedAt: ago(30), size: "8.9 MB", sharedWith: ["Priya Anand", "Marcus Webb", "Jordan Kim"], activeEditors: [], ownerId: 1, starred: true },
// Code / config
{ id: 15, type: "file", name: "api-config.json", groupId: 2, modifiedAt: ago(0), size: "4 KB", sharedWith: ["Marcus Webb"], activeEditors: ["Marcus Webb"], ownerId: 3, starred: false },
// Presentations
{ id: 16, type: "file", name: "Investor Deck May.pptx", groupId: 2, modifiedAt: ago(6), size: "18.4 MB", sharedWith: ["Priya Anand"], activeEditors: [], ownerId: 1, starred: true },
// External shared
{ id: 17, type: "file", name: "Vendor Contract.pdf", groupId: 5, modifiedAt: ago(45), size: "2.2 MB", sharedWith: ["Priya Anand"], activeEditors: [], ownerId: 1, starred: false },
{ id: 18, type: "file", name: "External Proposal.docx", groupId: 5, modifiedAt: ago(8), size: "0.7 MB", sharedWith: [], activeEditors: [], ownerId: 1, starred: false },
]
}
get locationLabel() {
return this.locations.find(l => l.id === this.activeLocation)?.label || "All Files"
}
get visibleFiles() {
let files = this.files;
// Filter by active groups
files = files.filter(f => this.activeGroupIds.has(f.groupId));
// Filter by location
if (this.activeLocation === "mine") files = files.filter(f => f.ownerId === 1);
if (this.activeLocation === "shared") files = files.filter(f => f.sharedWith?.length > 0);
if (this.activeLocation === "starred") files = files.filter(f => f.starred);
if (this.activeLocation === "recent") files = files.sort((a, b) => new Date(b.modifiedAt) - new Date(a.modifiedAt)).slice(0, 10);
// Search
if (this.searchText) {
const q = this.searchText.toLowerCase();
files = files.filter(f => f.name.toLowerCase().includes(q));
}
return files;
}
render() {
HStack(() => {
// Left sidebar
VStack(() => {
DesktopFilesSidebar(
this.groups,
this.activeGroupIds,
(groupId) => {
if (this.activeGroupIds.has(groupId)) {
if (this.activeGroupIds.size > 1) this.activeGroupIds.delete(groupId);
} else {
this.activeGroupIds.add(groupId);
}
this.rerender();
},
this.locations,
this.activeLocation,
(locId) => { this.activeLocation = locId; this.rerender(); }
)
})
.width(220, px)
.minWidth(200, px)
.height(100, pct)
.borderRight("1px solid var(--divider)")
.flexShrink(0)
.overflow("hidden")
// Main area
VStack(() => {
DesktopFilesToolbar(
this.locationLabel,
this.viewMode,
this.searchText,
(mode) => { this.viewMode = mode; this.rerender(); },
(text) => { this.searchText = text; this.rerender(); },
() => {}
)
DesktopFilesGrid(
this.visibleFiles,
this.groups,
this.viewMode,
() => {}
)
.flex(1)
.minHeight(0)
.width(100, pct)
.overflow("hidden")
})
.flex(1)
.height(100, pct)
.overflow("hidden")
})
.height(100, pct)
.width(100, pct)
.overflow("hidden")
}
}
register(Files)

3
files/icons/files.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="152" height="128" viewBox="0 0 152 128" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M135.189 18.0274H70.1495L56.0594 1.23569C55.7346 0.848813 55.3292 0.537769 54.8719 0.324324C54.4145 0.110879 53.9162 0.000189693 53.4117 1.17244e-07H3.45833C3.00415 -0.000118302 2.55438 0.0894701 2.13475 0.263644C1.71511 0.437819 1.33382 0.693166 1.01266 1.0151C0.6915 1.33703 0.436765 1.71924 0.26301 2.13989C0.0892542 2.56054 -0.000118018 3.01138 1.16963e-07 3.46667V111.766C0.00492427 115.986 1.67921 120.031 4.65558 123.014C7.63195 125.998 11.6674 127.676 15.8766 127.681H135.189C139.398 127.676 143.434 125.998 146.41 123.014C149.386 120.031 151.061 115.986 151.066 111.766V33.9405C151.061 29.7213 149.386 25.6764 146.41 22.6931C143.434 19.7099 139.398 18.0319 135.189 18.0274ZM6.91667 6.93333H51.8007L61.1119 18.0274H6.91667V6.93333ZM144.149 111.766C144.146 114.147 143.202 116.43 141.522 118.114C139.842 119.798 137.565 120.745 135.189 120.748H15.8766C13.501 120.745 11.2235 119.798 9.54377 118.114C7.864 116.43 6.9192 114.147 6.91667 111.766V24.9608H135.189C137.564 24.9633 139.842 25.9101 141.521 27.5935C143.201 29.2769 144.146 31.5595 144.149 33.9405V111.766Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,3 @@
<svg width="152" height="128" viewBox="0 0 152 128" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M135.189 18.0274H70.1495L56.0594 1.23569C55.7346 0.848813 55.3292 0.537769 54.8719 0.324324C54.4145 0.110879 53.9162 0.000189693 53.4117 1.17244e-07H3.45833C3.00415 -0.000118302 2.55438 0.0894701 2.13475 0.263644C1.71511 0.437819 1.33382 0.693166 1.01266 1.0151C0.6915 1.33703 0.436765 1.71924 0.26301 2.13989C0.0892542 2.56054 -0.000118018 3.01138 1.16963e-07 3.46667V111.766C0.00492427 115.986 1.67921 120.031 4.65558 123.014C7.63195 125.998 11.6674 127.676 15.8766 127.681H135.189C139.398 127.676 143.434 125.998 146.41 123.014C149.386 120.031 151.061 115.986 151.066 111.766V33.9405C151.061 29.7213 149.386 25.6764 146.41 22.6931C143.434 19.7099 139.398 18.0319 135.189 18.0274ZM6 5.5H53.4117L64.0654 18.0274H6V5.5ZM145.5 111.766C145.5 113 143.745 118.316 142.065 120C140.386 121.684 134.876 121.997 132.5 122L15.8766 122.5C13.501 122.497 10.2452 121.184 8.56543 119.5C6.88567 117.816 5.50254 114.147 5.5 111.766L6 23L70.1495 22.6931H135.189C137.564 22.6956 141.386 25.3166 143.065 27C144.745 28.6834 145.497 32.619 145.5 35V74V111.766Z" fill="#FEE8C8"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB