397 lines
15 KiB
JavaScript
397 lines
15 KiB
JavaScript
import server from "/@server/server.js"
|
|
import calendarUtil from "../../calendarUtil.js"
|
|
import "../../../components/Avatar.js"
|
|
|
|
class DesktopEventDetails extends Shadow {
|
|
attachmentsOpen = false
|
|
|
|
constructor(calendars, event, onUpdated = null, onDeleted = null, onEdit = null) {
|
|
super()
|
|
this.calendars = calendars
|
|
this.event = event
|
|
this.attachmentsOpen = (event?.attachments?.length > 0)
|
|
this.onUpdated = onUpdated
|
|
this.onDeleted = onDeleted
|
|
this.onEdit = onEdit
|
|
}
|
|
|
|
render() {
|
|
if (!this.event) return
|
|
|
|
const eventCals = this.calendars.filter(c => this.event.calendars?.includes(c.id))
|
|
const isOwner = this.event.creator_id === global.profile?.id
|
|
|
|
VStack(() => {
|
|
this.renderHeader(isOwner)
|
|
this.renderBody(eventCals)
|
|
HStack(() => {
|
|
const members = global.currentNetwork.data?.members || []
|
|
const creator = members.find(m => m.id === this.event.creator_id)
|
|
if (creator) {
|
|
Avatar(creator, 1.6)
|
|
VStack(() => {
|
|
p(`Created ${calendarUtil.timeAgo(this.event.created)} by ${creator.first_name}`)
|
|
.margin(0).fontSize(0.7, em).color("var(--headertext)").opacity(0.5)
|
|
if (this.event.updated_at && this.event.updated_at !== this.event.created) {
|
|
p(`Last updated ${calendarUtil.timeAgo(this.event.updated_at)}`)
|
|
.margin(0).fontSize(0.7, em).color("var(--headertext)").opacity(0.4)
|
|
}
|
|
})
|
|
.gap(0.15, em)
|
|
}
|
|
})
|
|
.paddingHorizontal(1, em)
|
|
.paddingVertical(0.65, em)
|
|
.boxSizing("border-box")
|
|
.alignItems("center")
|
|
.gap(0.5, em)
|
|
.flexShrink(0)
|
|
})
|
|
.height(100, pct)
|
|
.boxSizing("border-box")
|
|
}
|
|
|
|
// ── Header ────────────────────────────────────────────────────────────────
|
|
|
|
renderHeader(isOwner) {
|
|
HStack(() => {
|
|
VStack(() => {
|
|
h2(this.event.title || "Untitled")
|
|
.margin(0)
|
|
.fontSize(1.45, em)
|
|
.fontWeight("700")
|
|
.color("var(--headertext)")
|
|
.lineHeight("1.2")
|
|
})
|
|
.flex(1)
|
|
.paddingHorizontal(1.4, em)
|
|
.paddingTop(2.5, em)
|
|
.paddingBottom(0.5, em)
|
|
.justifyContent("center")
|
|
|
|
if (isOwner) {
|
|
button("Delete")
|
|
.paddingVertical(0.34, em)
|
|
.paddingHorizontal(0.85, em)
|
|
.border("1px solid var(--quillred)")
|
|
.borderRadius(0.45, em)
|
|
.background("transparent")
|
|
.color("var(--quillred)")
|
|
.cursor("pointer")
|
|
.fontSize(0.8, em)
|
|
.fontWeight("600")
|
|
.marginRight(0.5, em)
|
|
.marginTop("auto")
|
|
.marginBottom(1, em)
|
|
.flexShrink(0)
|
|
.onClick((done) => { if (done) this.handleDelete() })
|
|
.onHover(function(hovering) {
|
|
this.style.background = hovering ? "var(--quillred)" : "transparent";
|
|
this.style.color = hovering ? "white" : "var(--quillred)";
|
|
})
|
|
|
|
button("Edit")
|
|
.paddingVertical(0.34, em)
|
|
.paddingHorizontal(0.85, em)
|
|
.border("1px solid var(--divider)")
|
|
.borderRadius(0.45, em)
|
|
.background("transparent")
|
|
.color("var(--headertext)")
|
|
.cursor("pointer")
|
|
.fontSize(0.8, em)
|
|
.marginRight(1.4, em)
|
|
.marginTop("auto")
|
|
.marginBottom(1, em)
|
|
.flexShrink(0)
|
|
.onClick((done) => {
|
|
if (!done) return
|
|
// Attach template dates for override events so scope='all' anchors correctly
|
|
let eventForEdit = this.event
|
|
if (this.event.recurrence_parent_id && !this.event._templateStart) {
|
|
const template = global.currentNetwork.data.events.find(e => e.id === this.event.recurrence_parent_id)
|
|
if (template) {
|
|
eventForEdit = {
|
|
...this.event,
|
|
_templateStart: new Date(template.time_start),
|
|
_templateEnd: new Date(template.time_end)
|
|
}
|
|
}
|
|
}
|
|
this.onEdit(eventForEdit)
|
|
})
|
|
.onHover(function(hovering) {
|
|
this.style.background = hovering ? "var(--divider)" : "transparent";
|
|
})
|
|
}
|
|
})
|
|
.width(100, pct)
|
|
.alignItems("stretch")
|
|
.background("var(--darkaccent)")
|
|
.borderBottom("1px solid var(--divider)")
|
|
.boxSizing("border-box")
|
|
.flexShrink(0)
|
|
}
|
|
|
|
// ── Body ─────────────────────────────────────────────────────────────────
|
|
|
|
renderBody(eventCals) {
|
|
VStack(() => {
|
|
|
|
VStack(() => {
|
|
this.prop("WHEN", () => {
|
|
p(calendarUtil.formatEventTime(this.event))
|
|
.margin(0)
|
|
.fontSize(0.88, em)
|
|
.color("var(--headertext)")
|
|
})
|
|
|
|
if (this.event.recurrence) {
|
|
this.prop("REPEATS", () => {
|
|
p(this._recurrenceLabel())
|
|
.margin(0)
|
|
.fontSize(0.88, em)
|
|
.color("var(--headertext)")
|
|
})
|
|
}
|
|
|
|
this.prop("CALENDARS", () => {
|
|
HStack(() => {
|
|
eventCals.forEach(cal => {
|
|
p(cal.name)
|
|
.margin(0)
|
|
.fontSize(0.78, em)
|
|
.fontWeight("600")
|
|
.color("white")
|
|
.paddingHorizontal(0.65, em)
|
|
.paddingVertical(0.28, em)
|
|
.background(cal.color)
|
|
.borderRadius(0.45, em)
|
|
})
|
|
})
|
|
.flexWrap("wrap")
|
|
.gap(0.45, em)
|
|
})
|
|
|
|
if (this.event.location) {
|
|
this.prop("LOCATION", () => {
|
|
p(this.event.location)
|
|
.margin(0)
|
|
.fontSize(0.88, em)
|
|
.color("var(--headertext)")
|
|
.lineHeight("1.5")
|
|
})
|
|
}
|
|
|
|
if (this.event.description) {
|
|
this.prop("DESCRIPTION", () => {
|
|
p(this.event.description)
|
|
.margin(0)
|
|
.fontSize(0.88, em)
|
|
.color("var(--headertext)")
|
|
.lineHeight("1.65")
|
|
.whiteSpace("pre-wrap")
|
|
})
|
|
}
|
|
})
|
|
|
|
if (this.event.attachments?.length > 0) {
|
|
this.renderAttachments()
|
|
}
|
|
|
|
})
|
|
.flex(1)
|
|
.overflowY("scroll")
|
|
.width(100, pct)
|
|
.boxSizing("border-box")
|
|
}
|
|
|
|
prop(label, contentFn) {
|
|
VStack(() => {
|
|
p(label)
|
|
.margin(0)
|
|
.marginBottom(1, em)
|
|
.fontSize(0.67, em)
|
|
.fontWeight("600")
|
|
.letterSpacing("0.06em")
|
|
.color("var(--headertext)")
|
|
.opacity(0.38)
|
|
|
|
VStack(() => { contentFn() })
|
|
.width(100, pct)
|
|
})
|
|
.paddingHorizontal(1.5, em)
|
|
.paddingTop(0.75, em)
|
|
.paddingBottom(0.4, em)
|
|
.boxSizing("border-box")
|
|
.width(100, pct)
|
|
}
|
|
|
|
_recurrenceLabel() {
|
|
const r = this.event.recurrence
|
|
if (!r) return ""
|
|
if (r.frequency === 'daily') return "Daily"
|
|
if (r.frequency === 'weekly' && r.interval === 2) return "Every 2 weeks"
|
|
if (r.frequency === 'weekly') return "Weekly"
|
|
if (r.frequency === 'monthly') return "Monthly"
|
|
if (r.frequency === 'yearly') return "Yearly"
|
|
return ""
|
|
}
|
|
|
|
// ── Attachments ───────────────────────────────────────────────────────────
|
|
|
|
renderAttachments() {
|
|
VStack(() => {
|
|
HStack(() => {
|
|
p("Attachments")
|
|
.margin(0)
|
|
.fontSize(0.82, em)
|
|
.fontWeight("600")
|
|
.color("var(--headertext)")
|
|
.opacity(0.4)
|
|
p("▼")
|
|
.attr({ id: "desktop-attachments-chevron" })
|
|
.margin(0)
|
|
.fontSize(0.65, em)
|
|
.color("var(--headertext)")
|
|
.opacity(0.4)
|
|
.display("inline-block")
|
|
.transition("transform 0.22s ease")
|
|
.transform(this.attachmentsOpen ? "rotate(180deg)" : "rotate(0deg)")
|
|
.userSelect("none")
|
|
})
|
|
.gap(0.5, em)
|
|
.alignItems("center")
|
|
.cursor("pointer")
|
|
.onClick((done) => { if (!done) return; this.toggleAttachments() })
|
|
|
|
VStack(() => {
|
|
const images = this.event.attachments.filter(f => f.type?.startsWith("image/"))
|
|
const files = this.event.attachments.filter(f => !f.type?.startsWith("image/"))
|
|
|
|
if (images.length > 0) {
|
|
HStack(() => {
|
|
images.forEach(file => {
|
|
const url = `${config.SERVER}/db/images/events/${file.name}`
|
|
VStack(() => {
|
|
img(url, "100%", "100%")
|
|
.objectFit("cover")
|
|
.display("block")
|
|
})
|
|
.width("6.5em")
|
|
.height("6.5em")
|
|
.flexShrink(0)
|
|
.border("1px solid var(--divider)")
|
|
.borderRadius(6, px)
|
|
.overflow("hidden")
|
|
.cursor("pointer")
|
|
.onClick((done) => { if (!done) return; $("filepreview-").open(file, url) })
|
|
})
|
|
})
|
|
.flexWrap("wrap")
|
|
.gap(0.5, em)
|
|
.boxSizing("border-box")
|
|
.width(100, pct)
|
|
}
|
|
|
|
if (files.length > 0) {
|
|
VStack(() => {
|
|
files.forEach(file => this.renderFile(file))
|
|
})
|
|
.gap(0.5, em)
|
|
.width("max-content")
|
|
.boxSizing("border-box")
|
|
}
|
|
})
|
|
.attr({ id: "desktop-attachments-content" })
|
|
.width(100, pct)
|
|
.display(this.attachmentsOpen ? "" : "none")
|
|
.gap(1, em)
|
|
})
|
|
.width(100, pct)
|
|
.boxSizing("border-box")
|
|
.paddingHorizontal(1.5, em)
|
|
.paddingVertical(0.85, em)
|
|
.gap(1, em)
|
|
}
|
|
|
|
renderFile(file) {
|
|
const url = `${config.SERVER}/db/images/events/${file.name}`
|
|
HStack(() => {
|
|
p("📎")
|
|
.margin(0)
|
|
.fontSize(0.9, em)
|
|
p(file.original_name ?? file.name)
|
|
.margin(0)
|
|
.color("var(--headertext)")
|
|
.fontSize(0.85, em)
|
|
.overflow("hidden")
|
|
.whiteSpace("nowrap")
|
|
.textOverflow("ellipsis")
|
|
})
|
|
.gap(0.5, em)
|
|
.alignItems("center")
|
|
.padding(0.55, em)
|
|
.background("var(--darkaccent)")
|
|
.border("1px solid var(--divider)")
|
|
.borderRadius(0.45, em)
|
|
.boxSizing("border-box")
|
|
.cursor("pointer")
|
|
.onClick((done) => { if (!done) return; $("filepreview-").open(file, url) })
|
|
}
|
|
|
|
toggleAttachments() {
|
|
this.attachmentsOpen = !this.attachmentsOpen
|
|
const content = this.$("#desktop-attachments-content")
|
|
const chevron = this.$("#desktop-attachments-chevron")
|
|
if (content) content.style.display = this.attachmentsOpen ? "" : "none"
|
|
if (chevron) chevron.style.transform = this.attachmentsOpen ? "rotate(180deg)" : "rotate(0deg)"
|
|
}
|
|
|
|
handleDelete() {
|
|
const event = this.event
|
|
const isRecurring = !!(event?._isOccurrence || event?.recurrence_parent_id || event?.recurrence_id)
|
|
if (isRecurring) {
|
|
$('actionsheetpopup-').show(
|
|
"Delete Recurring Event",
|
|
[
|
|
{ label: "Delete just this event", onTap: () => this.performDelete('single') },
|
|
{ label: "Delete this and future events", onTap: () => this.performDelete('future') },
|
|
{ label: "Delete all events in series", onTap: () => this.performDelete('all') },
|
|
],
|
|
() => {}
|
|
)
|
|
return
|
|
}
|
|
this.performDelete(null)
|
|
}
|
|
|
|
async performDelete(scope) {
|
|
const event = this.event
|
|
const isOverride = !!event.recurrence_parent_id
|
|
const templateId = isOverride ? event.recurrence_parent_id : event.id
|
|
const occurrenceDate = isOverride
|
|
? (event.recurrence_exception_date instanceof Date
|
|
? event.recurrence_exception_date.toISOString()
|
|
: event.recurrence_exception_date) ?? null
|
|
: event._occurrenceDate?.toISOString() ?? null
|
|
const serverEventId = (scope === 'single' && isOverride) ? event.id : templateId
|
|
|
|
try {
|
|
const result = await server.deleteEvent(serverEventId, global.currentNetwork.id, scope, occurrenceDate)
|
|
if (result.status === 200) {
|
|
$("modal-").forceClose()
|
|
const deleteResult = { scope: scope ?? 'all', templateId, occurrenceDate, overrideId: isOverride ? event.id : null }
|
|
if (this.onDeleted) this.onDeleted(deleteResult)
|
|
} else {
|
|
$("modal-")?.showError(result.error ?? "Failed to delete event.")
|
|
}
|
|
} catch (err) {
|
|
console.error("Failed to delete event:", err)
|
|
$("modal-")?.showError("Failed to delete event.")
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
register(DesktopEventDetails)
|