import server from "/@server/server.js" class DesktopCalendarForm extends Shadow { static COLORS = [ "#9E1C29", "#3D6FAD", "#2A8636", "#B38A1E", "#B85A1F", "#7A3FA3", "#B23D6B", "#2D8A87", "#3C9A5F", "#6E9A23", "#7A8428", "#9A2F7D", "#4F54A8", "#8A5A32", "#546B86", "#A67A1F", ] constructor(calendars, onSaved, editCalendar = null, onDelete = null, onBack = null) { super() this.calendars = calendars this.onSaved = onSaved this.editCalendar = editCalendar this.onDelete = onDelete this.onBack = onBack this.selectedColor = editCalendar?.color ?? DesktopCalendarForm.COLORS[0] if (editCalendar) { this.originalFormData = { name: editCalendar.name ?? "", description: editCalendar.description || "", color: editCalendar.color ?? "" } } else { this.originalFormData = { name: "", description: "", color: DesktopCalendarForm.COLORS[0] } } } fieldStyles(el) { return el .border("1px solid var(--divider)") .borderRadius(0.35, em) .outline("none") .background("transparent") .color("var(--headertext)") .fontSize(0.88, em) .padding(0.4, em) .boxSizing("border-box") .onHover(function(hovering) { this.style.border = `1px solid ${hovering ? "var(--lightDivider)" : "var(--divider)"}` }) } 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) } render() { const isEdit = !!this.editCalendar form(() => { VStack(() => { this.renderHeader(isEdit) this.renderBody(isEdit) }) .height(100, pct) .boxSizing("border-box") }) .height(100, pct) .onSubmit(e => { e.preventDefault(); this.handleSave() }) .onKeyDown(e => { if (e.key === "Enter" && e.target.tagName !== "TEXTAREA" && e.target.tagName !== "BUTTON") e.preventDefault() }) } renderHeader(isEdit) { HStack(() => { VStack(() => { input("", "100%") .attr({ name: "name", type: "text", placeholder: "Enter calendar name...", value: this.editCalendar?.name ?? "" }) .border("none") .outline("none") .background("transparent") .color("var(--headertext)") .fontSize(1.45, em) .fontWeight("700") .padding(0) .onHover(function(hovering) { this.style.opacity = hovering ? 0.82 : 1; }) }) .flex(1) .paddingHorizontal(1.4, em) .paddingTop(2.5, em) .paddingBottom(0.5, em) .justifyContent("center") if (isEdit) { button("Delete") .attr({ type: "button" }) .fontSize(0.8, em) .fontWeight("600") .background("transparent") .color("var(--quillred)") .paddingVertical(0.34, em) .paddingHorizontal(0.85, em) .marginRight(1.4, em) .marginBottom(1, em) .border("1px solid var(--quillred)") .borderRadius(0.45, em) .flexShrink(0) .cursor("pointer") .boxSizing("border-box") .onClick((done) => { if (done) this.handleDelete() }) .onHover(function(hovering) { this.style.background = hovering ? "var(--quillred)" : "transparent"; this.style.color = hovering ? "white" : "var(--quillred)" this.style.opacity = hovering ? 0.82 : 1; }) } button("Save") .attr({ type: "submit" }) .paddingVertical(0.34, em) .paddingHorizontal(0.85, em) .border("none") .borderRadius(0.45, em) .background("var(--quillred)") .color("white") .cursor("pointer") .fontSize(0.8, em) .fontWeight("600") .marginRight(1.4, em) .marginTop("auto") .marginBottom(1, em) .flexShrink(0) .onHover(function(hovering) { this.style.opacity = hovering ? 0.82 : 1; }) }) .width(100, pct) .alignItems("flex-end") .background("var(--darkaccent)") .borderBottom("1px solid var(--divider)") .boxSizing("border-box") .flexShrink(0) } renderBody() { VStack(() => { this.prop("COLOR", () => { const renderSwatch = (color) => { const selected = this.selectedColor === color p("") .flex(1) .height(1.6, em) .background(color) .borderRadius(5, px) .border(`3px solid ${selected ? "var(--quillred)" : color}`) .boxSizing("border-box") .cursor("pointer") .attr({ "data-color": color }) .onClick((done) => { if (!done) return const prev = this.$(`[data-color="${this.selectedColor}"]`) if (prev) prev.style.border = `3px solid ${this.selectedColor}` this.selectedColor = color const next = this.$(`[data-color="${color}"]`) if (next) next.style.border = `3px solid var(--quillred)` }) .onHover(function(hovering) { this.style.opacity = hovering ? 0.82 : 1; }) } VStack(() => { HStack(() => { DesktopCalendarForm.COLORS.slice(0, 8).forEach(renderSwatch) }) .gap(0.55, em) HStack(() => { DesktopCalendarForm.COLORS.slice(8, 16).forEach(renderSwatch) }) .gap(0.55, em) }) .gap(0.55, em) }) this.prop("DESCRIPTION", () => { textarea(this.editCalendar?.description ?? "") .attr({ name: "description" }) .styles(this.fieldStyles) .lineHeight("1.65") .width(100, pct) .minHeight("3em") .resize("none") .fieldSizing("content") .fontFamily("Arial") .onAppear(function() { this.value = this.placeholder; }) }) }) .flex(1) .overflowY("scroll") .width(100, pct) .boxSizing("border-box") } showError(msg) { $("modal-")?.showError(msg) } getFormData() { const val = name => this.$(`[name="${name}"]`).value return { name: val("name"), description: val("description") || null, color: this.selectedColor } } isUnchanged(data) { const o = this.originalFormData return ( data.name === o.name && data.color === o.color && (data.description || "") === o.description ) } isNewCalendarDirty() { const data = this.getFormData() const o = this.originalFormData return ( data.name !== o.name || (data.description || "") !== o.description || data.color !== o.color ) } async trySave() { if (!this.editCalendar) { // New calendar: only save if the user made edits if (!this.isNewCalendarDirty()) return false const data = this.getFormData() const payload = { name: data.name || "New calendar", description: data.description, color: data.color } const result = await server.addCalendar(payload, global.currentNetwork.id) if (result.status === 200) return result.calendar this.showError(result.error ?? "Failed to save calendar.") return null } const data = this.getFormData() if (this.isUnchanged(data)) return this.editCalendar const result = await server.editCalendar( this.editCalendar.id, { ...data, name: data.name || "New calendar" }, global.currentNetwork.id ) if (result.status === 200) return result.calendar this.showError(result.error ?? "Failed to save calendar.") return null } async handleSave() { $("modal-")?.showError("") const data = this.getFormData() if (this.editCalendar) { if (this.isUnchanged(data)) { if (this.onBack) this.onBack() return } } const payload = { name: data.name || "New calendar", description: data.description, color: data.color } const result = this.editCalendar ? await server.editCalendar(this.editCalendar.id, payload, global.currentNetwork.id) : await server.addCalendar(payload, global.currentNetwork.id) if (result.status === 200) { if (this.editCalendar) { this.onSaved(result.calendar) } else { // Use forceClose so _closeOverride doesn't re-trigger trySave $("modal-").forceClose() this.onSaved(result.calendar) } } else { this.showError(result.error ?? "Failed to save calendar.") } } async handleDelete() { const result = await server.deleteCalendar(this.editCalendar.id, global.currentNetwork.id) if (result.status === 200) { $("modal-").forceClose() if (this.onDelete) this.onDelete(this.editCalendar.id) } else { this.showError(result.error ?? "Failed to delete calendar.") } } } register(DesktopCalendarForm)