import server from "/@server/server.js" import calendarUtil from "../calendarUtil.js" import "./EventForm.js" import "../../components/BottomSheet.js" import "../../components/BackButton.js" import "../../components/Avatar.js" css(` eventdetails- { display: flex; flex-direction: column; height: 100%; scrollbar-width: none; -ms-overflow-style: none; } eventdetails- ::-webkit-scrollbar { display: none; width: 0; height: 0; } eventdetails- ::-webkit-scrollbar-thumb { background: transparent; } eventdetails- ::-webkit-scrollbar-track { background: transparent; } #eventdetails-toast-wrap { transition: max-height 0.25s ease, opacity 0.22s ease, padding-top 0.25s ease; } `) class EventDetails extends Shadow { attachmentsOpen = false; selectedCalendars = []; _pendingCalendars = null; _prevCalendars = null; constructor(calendars, event = null, onEventEdited = null, onEventDeleted = null) { super() this.calendars = calendars; this.event = event; this.onEventEdited = onEventEdited; this.onEventDeleted = onEventDeleted; this.selectedCalendars = calendars.filter(c => event?.calendars?.includes(c.id)); } render() { this.editSheet = BottomSheet(100) // separate sheet for the edit form, layered above this one const isOwner = this.event?.creator_id === global.profile?.id; VStack(() => { this.renderHeader(isOwner) // ── Error toast ─────────────────────────────────────── VStack(() => { p("") .attr({ id: "eventdetails-toast" }) .margin(0) .padding("0.55em 1.1em") .background("var(--quillred)") .color("white") .fontSize(0.85, em) .fontWeight("500") .fontFamily("Arial") .borderRadius("0.5em") .boxShadow("0 2px 10px rgba(0,0,0,0.15)") .whiteSpace("nowrap") }) .attr({ id: "eventdetails-toast-wrap" }) .alignItems("center") .overflow("hidden") .maxHeight(0) .opacity(0) .flexShrink(0) // ── Scrollable body ─────────────────────────────────── VStack(() => { if (!this.event) return // VStack(() => {}).height(0.85, em).flexShrink(0) // ── Calendar / Location / Notes card ────────────── VStack(() => { // Calendar row — tappable to expand HStack(() => { p("Calendar") .margin(0).fontSize(0.92, em).color("var(--headertext)").flexShrink(0) HStack(() => { this.selectedCalendars.forEach(cal => { HStack(() => { p("").width(0.6, em).height(0.6, em) .background(cal.color) .borderRadius(50, pct) .flexShrink(0) p(cal.name) .margin(0) .fontSize(0.82, em) .fontWeight("600") .color("var(--text)") .whiteSpace("nowrap") }) .gap(0.3, em) .alignItems("center") }) }) .attr({ id: "calendar-display" }) .flex(1) .justifyContent("flex-end") .flexWrap("wrap") .gap(0.5, em) let chevron = p("›") chevron .attr({ id: "cal-chevron" }) .margin(0).opacity(0.3).fontSize(1.1, em).flexShrink(0) .transition("transform 0.25s ease") .state(chevron.parentElement, "open", function (value) { if(value === "true") { this.style.transform = "rotate(90deg)" } else { console.log("no trans") this.style.transform = "" } }) }) .attr({id: "calendar-row"}) .paddingHorizontal(1, em).paddingVertical(0.78, em) .alignItems("center").gap(0.5, em) .borderBottom("1px solid var(--divider)").cursor("pointer") .onTap(function () { const isOpen = this.getAttribute("open") === "true" if (isOpen) { this.setAttribute("open", "false") } else { this.setAttribute("open", "true") } }) // Calendar Expandable List VStack(() => { this.calendars.forEach(cal => { const isSelected = this.selectedCalendars.some(c => c.id === cal.id) HStack(() => { HStack(() => { p("").width(0.65, em).height(0.65, em) .background(cal.color).borderRadius(50, pct).flexShrink(0) p(cal.name) .margin(0).fontSize(0.9, em).color("var(--text)").fontFamily("Arial") }) .gap(0.45, em).alignItems("center").flex(1) p("✓") .attr({ id: `cal-check-${cal.id}` }) .margin(0).fontSize(0.88, em) .color("var(--quillred)").fontWeight("700") .display(isSelected ? "" : "none") }) .paddingHorizontal(1.25, em).paddingVertical(0.72, em) .alignItems("center") .borderBottom("1px solid var(--divider)") .cursor("pointer") .onTap(() => { const prevCalendars = [...this.selectedCalendars] const i = this.selectedCalendars.findIndex(c => c.id === cal.id) if (i >= 0) { if (this.selectedCalendars.length > 1) { this.selectedCalendars.splice(i, 1) const check = this.$(`#cal-check-${cal.id}`) if (check) check.style.display = "none" } } else { this.selectedCalendars.push(cal) const check = this.$(`#cal-check-${cal.id}`) if (check) check.style.display = "" } this.updateCalendarDisplay() this.saveCalendars(prevCalendars) }) }) }) .state(this.$("#calendar-row"), "open", function (value) { if(value === "false") { this.style.maxHeight = "0" } else { this.style.maxHeight = this.scrollHeight + "px" } }) .attr({ id: "cal-picker"}) .overflow("hidden").maxHeight(0) .transition("max-height 0.3s ease") // Location row if (this.event.location) { HStack(() => { p("📍").margin(0).fontSize(0.85, em).flexShrink(0) p(this.event.location) .margin(0).fontSize(0.9, em).color("var(--text)").fontFamily("Arial") }) .paddingHorizontal(1, em).paddingVertical(0.78, em) .alignItems("center").gap(0.65, em) .borderBottom("1px solid var(--divider)") } // Notes row if (this.event.description) { HStack(() => { p("📝").margin(0).fontSize(0.85, em).flexShrink(0).alignSelf("flex-start").paddingTop(0.1, em) p(this.event.description) .margin(0).fontSize(0.9, em).color("var(--text)").fontFamily("Arial") .whiteSpace("pre-wrap").lineHeight("1.45") }) .paddingHorizontal(1, em).paddingVertical(0.78, em) .alignItems("flex-start").gap(0.65, em) } }) .background("var(--darkaccent)").border("1px solid var(--divider)") .borderRadius(12, px).marginHorizontal(1, em).overflow("hidden").flexShrink(0) // ── Attachments card ────────────────────────────── if (this.event.attachments?.length > 0) { // VStack(() => {}).height(0.85, em).flexShrink(0) VStack(() => { HStack(() => { p("📎").margin(0).fontSize(0.85, em).flexShrink(0) p("Attachments") .margin(0).fontSize(0.92, em).color("var(--headertext)").flex(1) p("▼") .attr({ id: "attachments-chevron" }) .margin(0).fontSize(0.7, em).color("var(--text)").opacity(0.5) .display("inline-block") .transition("transform 0.3s ease") .transform(this.attachmentsOpen ? "rotate(180deg)" : "rotate(0deg)") }) .paddingHorizontal(1, em).paddingVertical(0.78, em) .alignItems("center").gap(0.65, em).cursor("pointer") .onTap(() => this.toggleAttachments()) VStack(() => { VStack(() => { this.event.attachments.forEach(file => this.renderFile(file)) }) .gap(0.75, em).width(100, pct) .padding("0 1em 0.75em").boxSizing("border-box") }) .attr({ id: "attachments-content" }) .overflow("hidden").maxHeight("0") .transition("max-height 0.5s ease") }) .background("var(--darkaccent)").border("1px solid var(--divider)") .borderRadius(12, px).marginHorizontal(1, em).overflow("hidden").flexShrink(0) } }) .overflowY("scroll").flex(1).minHeight(0).paddingTop(0.85, em).paddingBottom(1.5, em).gap(0.85, em) // ── Footer: creator avatar + timestamps ─────────────── if (this.event) { const members = global.currentNetwork.data?.members || [] const creator = members.find(m => m.id === this.event.creator_id) if (creator) { HStack(() => { Avatar(creator, 2) VStack(() => { p(`Created ${calendarUtil.timeAgo(this.event.created)} by ${creator.first_name}`) .margin(0).fontSize(0.9, 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.9, em).color("var(--headertext)").opacity(0.4) } }) .gap(0.15, em) }) .paddingHorizontal(1, em) .paddingVertical(0.65, em) .alignItems("center") .gap(0.5, em) .flexShrink(0) } } }) .height(100, pct) } renderHeader(isOwner) { VStack(() => { HStack(() => { BackButton(false, true, () => $("bottomsheet-").toggle()) if (isOwner) { HStack(() => { // ── Delete button ───────────────────────────────── button("Delete") .attr({ type: "button" }) .padding(0.4, rem) .fontSize(1.25, em) .boxSizing("border-box") .outline("none") .border("none") .background("transparent") .color("var(--quillred)") .onTap(() => this.handleDelete()) button("Edit") .padding(0.4, rem) .fontSize(1.25, em) .color("var(--darkaccent)") .boxSizing("border-box") .outline("none") .border("none") .zIndex(3) .onTap((e) => { e.preventDefault() let formEl const closeForm = () => { this.editSheet._closeOverride = null this.editSheet.setSheet(false) } const onSaveError = () => { this.editSheet._closeOverride = () => this.editSheet.forceClose() } this.editSheet.show(() => { // For override rows, attach template dates so scope='all' anchors correctly let eventForForm = 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) { eventForForm = { ...this.event, _templateStart: new Date(template.time_start), _templateEnd: new Date(template.time_end) }; } } formEl = EventForm( this.calendars, (updateResult) => { closeForm() const updatedEvent = updateResult?.scope ? updateResult.event : updateResult; this.event = { ...updatedEvent, time_start: new Date(updatedEvent.time_start), time_end: new Date(updatedEvent.time_end) } this.selectedCalendars = this.calendars.filter(c => updatedEvent.calendars?.includes(c.id)) setTimeout(() => { this.onEventEdited(updateResult) this.rerender() }, 300) }, eventForForm, closeForm, (deleteResult) => { closeForm() $("bottomsheet-").toggle() this.onEventDeleted(deleteResult) }, null, onSaveError ) }) this.editSheet._closeOverride = () => { this.editSheet.setSheet(true) formEl?.handleBack() } }) }) .fontFamily("Arial") .cursor("pointer") .paddingHorizontal(0.8, rem) .gap(0.4, rem) } }) .width(100, pct) .justifyContent("space-between") .alignItems("center") VStack(() => { h2(this.event?.title ?? "") .color("var(--headertext)") .fontFamily("Arial") .margin(0) .fontSize(1.4, em) p(this.event ? calendarUtil.formatEventTime(this.event) : "") .margin(0) .color("var(--headertext)") .opacity(0.7) .fontSize(0.85, em) }) .paddingHorizontal(1, em) .paddingBottom(1, em) .gap(0.3, em) .alignItems("flex-start") }) .width(100, pct) .background(util.darkMode() ? "var(--darkred)" : "var(--sidebottombars)") .borderTopLeftRadius("10px").borderTopRightRadius("10px") .border("1px solid var(--divider)") .boxSizing("border-box").flexShrink(0) .alignItems("flex-start") } updateCalendarDisplay() { const el = this.$("#calendar-display") if (!el) return el.innerHTML = this.selectedCalendars.map(cal => `
${cal.name}