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

157
calendar/EventFileList.js Normal file
View File

@@ -0,0 +1,157 @@
class EventFileList extends Shadow {
constructor(existingAttachments, onDeleteExisting = null, onDeleteNew = null, onPreview = null) {
super();
this.existingAttachments = existingAttachments ?? [];
this.newFiles = [];
this.objectURLs = new Map();
this.onDeleteExisting = onDeleteExisting;
this.onDeleteNew = onDeleteNew;
this.onPreview = onPreview;
}
update(files) {
this.newFiles.push(...Array.from(files));
this.rerender();
}
removeExisting(fileId) {
this.existingAttachments = this.existingAttachments.filter(f => f.id !== fileId);
this.rerender();
}
removeNew(index) {
const file = this.newFiles[index];
if (this.objectURLs.has(file)) {
URL.revokeObjectURL(this.objectURLs.get(file));
this.objectURLs.delete(file);
}
this.newFiles.splice(index, 1);
this.rerender();
}
commitNew(insertedFiles) {
this.newFiles.forEach(file => {
if (this.objectURLs.has(file)) {
URL.revokeObjectURL(this.objectURLs.get(file));
this.objectURLs.delete(file);
}
});
this.newFiles = [];
this.existingAttachments = [...this.existingAttachments, ...insertedFiles];
this.rerender();
}
getObjectURL(file) {
if (!this.objectURLs.has(file)) {
this.objectURLs.set(file, URL.createObjectURL(file));
}
return this.objectURLs.get(file);
}
render() {
const hasFiles = this.existingAttachments.length > 0 || this.newFiles.length > 0;
this.style.display = hasFiles ? "flex" : "none";
this.style.padding = "1em";
this.style.paddingTop = "0.5em";
this.style.boxSizing = "border-box";
VStack(() => {
this.existingAttachments.forEach(file => {
const isImage = file.type?.startsWith("image/");
const url = `${config.SERVER}/db/images/events/${file.name}`
const row = HStack(() => {
if (isImage) {
img(`${config.UI}/db/images/events/${file.name}`, "1.5em", "1.5em")
.objectFit("cover")
.borderRadius(3, px)
.flexShrink(0)
} else {
span("📎")
}
span(file.original_name ?? file.name)
.overflow("hidden")
.textOverflow("ellipsis")
.whiteSpace("nowrap")
.flex(1)
span(file.size_bytes != null ? `${(file.size_bytes / 1024).toFixed(1)} KB` : "")
.opacity(0.5)
.flexShrink(0)
.fontSize(0.85, rem)
.width("5em")
if (this.onDeleteExisting) {
span("×")
.color("var(--quillred)")
.opacity(0.7)
.fontSize(1.2, em)
.fontWeight("600")
.flexShrink(0)
.cursor("pointer")
.padding(0.5, em)
.margin(-0.5, em)
.onTap((e) => { e?.stopPropagation(); if (window.isMobile() === true) this.onDeleteExisting(file.id) })
.onClick((done, e) => { e?.stopPropagation(); if (done && window.isMobile() === false) this.onDeleteExisting(file.id) })
}
})
.alignItems("center")
.gap(0.5, em)
if (this.onPreview) {
row.cursor("pointer")
.onClick((done) => { if (done) this.onPreview(file, url) })
}
})
this.newFiles.forEach((file, index) => {
const isImage = file.type.startsWith("image/");
const url = this.getObjectURL(file)
const row = HStack(() => {
if (isImage) {
img(url, "1.5em", "1.5em")
.objectFit("cover")
.borderRadius(3, px)
.flexShrink(0)
} else {
span("📎")
}
span(file.name)
.overflow("hidden")
.textOverflow("ellipsis")
.whiteSpace("nowrap")
.flex(1)
span(`${(file.size / 1024).toFixed(1)} KB`)
.opacity(0.5)
.flexShrink(0)
.fontSize(0.85, rem)
.width("5em")
if (this.onDeleteNew) {
span("×")
.color("var(--quillred)")
.opacity(0.7)
.fontSize(1.2, em)
.fontWeight("600")
.flexShrink(0)
.cursor("pointer")
.padding(0.5, em)
.margin(-0.5, em)
.onTap((e) => { e?.stopPropagation(); if (window.isMobile() === true) this.onDeleteNew(index) })
.onClick((done, e) => { e?.stopPropagation(); if (done && window.isMobile() === false) this.onDeleteNew(index) })
}
})
.alignItems("center")
.gap(0.5, em)
if (this.onPreview) {
row.cursor("pointer")
.onClick((done) => { if (done) this.onPreview(file, url) })
}
})
})
.gap(1, em)
.color("var(--text)")
.fontSize(0.85, rem)
.fontFamily("Arial")
}
}
register(EventFileList)