diff --git a/src/components/Calendar/CalendarEvent.ts b/src/components/Calendar/CalendarEvent.ts deleted file mode 100644 index 45af645..0000000 --- a/src/components/Calendar/CalendarEvent.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { LitElement, css, html } from 'lit'; -import { customElement, property } from 'lit/decorators.js'; -import { when } from 'lit/directives/when.js'; - -export interface CalendarEventData { - id: string; - title: string; - start: Date; - end?: Date; - description: string; - location?: string; - participants?: string[]; - tags?: string[]; - type: 'date' | 'time'; -} - -@customElement('arx-calendar-event') -export class CalendarEvent extends LitElement { - @property({ type: Object }) - event!: CalendarEventData; - - @property({ type: String }) - locale = 'en-US'; - - static override styles = css` - .calendar-event { - background: var(--color-primary); - color: var(--color-primary-content); - padding: 0.25rem; - margin: 0.25rem 0; - border-radius: var(--radius-selector); - font-size: 0.875rem; - cursor: pointer; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - width: 100%; - } - - .event-time { - font-size: 0.75rem; - opacity: 0.8; - } - `; - - private handleClick() { - this.dispatchEvent(new CustomEvent('event-click', { detail: { event: this.event } })); - } - - override render() { - return html` -
- ${this.event.title} - ${when( - this.event.type === 'time', - () => html` -
- ${this.event.start.toLocaleTimeString(this.locale, { hour: '2-digit', minute: '2-digit' })} - ${when( - this.event.end, - () => - html` - ${this.event.end!.toLocaleTimeString(this.locale, { hour: '2-digit', minute: '2-digit' })}`, - )} -
- `, - )} -
- `; - } -} diff --git a/src/components/Calendar/CalendarEventDetailsDialog.ts b/src/components/Calendar/CalendarEventDetailsDialog.ts deleted file mode 100644 index 98b51be..0000000 --- a/src/components/Calendar/CalendarEventDetailsDialog.ts +++ /dev/null @@ -1,88 +0,0 @@ -import formatDateTime from '@/utils/formatDateTime'; -import { LitElement, css, html } from 'lit'; -import { customElement, property } from 'lit/decorators.js'; -import { when } from 'lit/directives/when.js'; -import type { CalendarEventData } from './CalendarEvent'; - -import '@components/General/Dialog'; -import '@components/General/Fieldset'; - -@customElement('arx-calendar-event-details-dialog') -export class CalendarEventDetailsDialog extends LitElement { - @property({ type: Object }) - event: CalendarEventData | null = null; - - @property({ type: Boolean }) - open = false; - - @property({ type: String }) - locale = 'en-US'; - - static override styles = css` - .event-detail { - display: flex; - gap: 0.5rem; - } - - .event-detail-label { - font-weight: 500; - color: var(--color-base-content); - min-width: 80px; - } - - .event-detail-value { - color: var(--color-base-content); - } - `; - - private handleClose() { - this.dispatchEvent(new CustomEvent('close')); - } - - override render() { - if (!this.open || !this.event) return html``; - - const { end, location, description } = this.event; - - return html` - - -
- Start: - ${formatDateTime(this.event.start)} -
- ${when( - end instanceof Date, - () => html` -
- End: - ${formatDateTime(end!)} -
- `, - )} - ${when( - location, - () => html` -
- Location: - ${location} -
- `, - )} - ${when( - description, - () => html` -
- Description: - ${description} -
- `, - )} -
-
- `; - } -} diff --git a/src/components/Calendar/CalendarEventDialog.ts b/src/components/Calendar/CalendarEventDialog.ts deleted file mode 100644 index b2bfcae..0000000 --- a/src/components/Calendar/CalendarEventDialog.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { LitElement, css, html } from 'lit'; -import { customElement, property, state } from 'lit/decorators.js'; -import { when } from 'lit/directives/when.js'; - -import '@components/General/Button'; -import { StyledInput } from '@components/General/Input'; -import { StyledTextarea } from '@components/General/Textarea'; -import { StyledToggle } from '@components/General/Toggle'; - -interface InputEvent extends Event { - detail: { - value: string; - }; -} - -@customElement('arx-calendar-event-dialog') -export class CalendarEventDialog extends LitElement { - @property({ type: Boolean }) - open = false; - - @state() - private newEvent = { - title: '', - description: '', - location: '', - startDate: '', - startTime: '', - endDate: '', - endTime: '', - allDay: false, - participants: [] as string[], - tags: [] as string[], - }; - - static override styles = css` - .form-input { - width: 100%; - padding: 0.5rem; - border: 1px solid var(--color-base-300); - border-radius: var(--radius-selector); - background: var(--color-base-200); - color: var(--color-base-content); - } - - .form-row { - display: flex; - gap: 1rem; - } - `; - - private handleClose() { - this.dispatchEvent(new CustomEvent('close')); - } - - private handleInputChange(e: Event) { - if (e.target instanceof StyledToggle) { - const checked = e.target.checked; - this.newEvent = { - ...this.newEvent, - [e.target.name]: checked, - }; - } else if (e.target instanceof StyledTextarea || e.target instanceof StyledInput) { - const inputEvent = e as InputEvent; - this.newEvent = { - ...this.newEvent, - [e.target.name]: inputEvent.detail.value, - }; - } - } - - private handleSubmit(e: Event) { - e.preventDefault(); - this.dispatchEvent(new CustomEvent('submit', { detail: { event: this.newEvent } })); - } - - override render() { - if (!this.open) return html``; - - return html` - - - - - - - - - - - - - -
- - - - ${when( - !this.newEvent.allDay, - () => html` - - - - `, - )} -
-
- - - - ${when( - !this.newEvent.allDay, - () => html` - - - - `, - )} -
- -
- `; - } -} diff --git a/src/components/Calendar/CalendarHeader.ts b/src/components/Calendar/CalendarHeader.ts deleted file mode 100644 index efb0512..0000000 --- a/src/components/Calendar/CalendarHeader.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { LitElement, css, html } from 'lit'; -import { customElement, property } from 'lit/decorators.js'; -import { when } from 'lit/directives/when.js'; - -type CalendarView = 'month' | 'week'; - -@customElement('arx-calendar-header') -export class CalendarHeader extends LitElement { - @property({ type: String }) - view: CalendarView = 'month'; - - @property({ type: Date }) - currentDate = new Date(); - - @property({ type: Boolean }) - isToday = false; - - @property({ type: String }) - dateRange = ''; - - static override styles = css` - .calendar-header { - display: flex; - flex-direction: column; - gap: 1rem; - margin-bottom: 2rem; - } - - .calendar-title-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 1rem; - } - - .calendar-title { - font-size: 1.5rem; - font-weight: 600; - color: var(--color-base-content); - text-align: center; - flex: 1; - } - - .calendar-controls { - width: 100%; - display: flex; - align-items: center; - justify-content: center; - gap: 1rem; - flex-wrap: wrap; - } - - .calendar-view-toggle { - display: flex; - align-items: center; - justify-content: center; - gap: 1rem; - } - `; - - private handlePrev() { - this.dispatchEvent(new CustomEvent('prev')); - } - - private handleNext() { - this.dispatchEvent(new CustomEvent('next')); - } - - private handleToday() { - this.dispatchEvent(new CustomEvent('today')); - } - - private handleAddEvent() { - this.dispatchEvent(new CustomEvent('add-event')); - } - - private handleViewChange(e: CustomEvent) { - this.dispatchEvent(new CustomEvent('view-change', { detail: { view: e.detail.checked ? 'week' : 'month' } })); - } - - override render() { - return html` -
-
- -

${this.dateRange}

- -
-
-
- ${when( - !this.isToday, - () => html` - - `, - )} - - - - -
-
-
- `; - } -} diff --git a/src/components/Calendar/CalendarMonthView.ts b/src/components/Calendar/CalendarMonthView.ts deleted file mode 100644 index 5580fb3..0000000 --- a/src/components/Calendar/CalendarMonthView.ts +++ /dev/null @@ -1,212 +0,0 @@ -import { LitElement, css, html } from 'lit'; -import { customElement, property } from 'lit/decorators.js'; -import type { CalendarEventData } from './CalendarEvent'; - -@customElement('arx-calendar-month-view') -export class CalendarMonthView extends LitElement { - @property({ type: Date }) - currentDate = new Date(); - - @property({ type: Date }) - selectedDate: Date | null = null; - - @property({ type: Array }) - events: CalendarEventData[] = []; - - @property({ type: String }) - locale = 'en-US'; - - static override styles = css` - .calendar-grid { - display: grid; - grid-template-columns: repeat(7, 1fr); - gap: 0.5rem; - background: var(--color-base-100); - padding: 1rem; - border-radius: var(--radius-selector); - box-shadow: var(--shadow-sm); - } - - .calendar-day-header { - text-align: center; - padding: 0.5rem; - font-weight: 500; - color: var(--color-base-content); - opacity: 0.7; - } - - .calendar-day { - aspect-ratio: 1; - display: flex; - flex-direction: column; - align-items: stretch; - justify-content: center; - min-height: 100px; - position: relative; - overflow: hidden; - border: 1px solid var(--color-base-300); - border-radius: var(--radius-selector); - cursor: pointer; - transition: all 0.2s ease; - background: var(--color-base-200); - color: var(--color-base-content); - } - - .calendar-day-number { - text-align: center; - padding: 0.25rem; - font-weight: 500; - } - - .calendar-day:hover { - background: var(--color-base-300); - transform: translateY(-2px); - } - - .calendar-day.selected { - background: var(--color-primary); - color: var(--color-primary-content); - border-color: var(--color-primary); - } - - .calendar-day.today { - border: 2px solid var(--color-primary); - font-weight: 600; - } - - .calendar-day.other-month { - opacity: 0.5; - background: var(--color-base-100); - } - - .calendar-day-events { - flex: 1; - overflow-y: auto; - max-height: 100px; - width: 100%; - } - `; - - private getDaysInMonth(date: Date): number { - return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); - } - - private getFirstDayOfMonth(date: Date): number { - return new Date(date.getFullYear(), date.getMonth(), 1).getDay(); - } - - private getCalendarDays(): Date[] { - const days: Date[] = []; - const firstDay = this.getFirstDayOfMonth(this.currentDate); - const daysInMonth = this.getDaysInMonth(this.currentDate); - const daysInPrevMonth = this.getDaysInMonth( - new Date(this.currentDate.getFullYear(), this.currentDate.getMonth() - 1), - ); - - for (let i = firstDay - 1; i >= 0; i--) { - days.push(new Date(this.currentDate.getFullYear(), this.currentDate.getMonth() - 1, daysInPrevMonth - i)); - } - - for (let i = 1; i <= daysInMonth; i++) { - days.push(new Date(this.currentDate.getFullYear(), this.currentDate.getMonth(), i)); - } - - const remainingDays = 42 - days.length; // 6 rows * 7 days - for (let i = 1; i <= remainingDays; i++) { - days.push(new Date(this.currentDate.getFullYear(), this.currentDate.getMonth() + 1, i)); - } - - return days; - } - - private isToday(date: Date): boolean { - const today = new Date(); - return ( - date.getDate() === today.getDate() && - date.getMonth() === today.getMonth() && - date.getFullYear() === today.getFullYear() - ); - } - - private isSelected(date: Date): boolean { - if (!this.selectedDate) return false; - return ( - date.getDate() === this.selectedDate.getDate() && - date.getMonth() === this.selectedDate.getMonth() && - date.getFullYear() === this.selectedDate.getFullYear() - ); - } - - private isOtherMonth(date: Date): boolean { - return date.getMonth() !== this.currentDate.getMonth(); - } - - private getEventsForDate(date: Date): CalendarEventData[] { - return this.events.filter((event) => { - const eventStart = new Date(event.start); - if ( - eventStart.getDate() === date.getDate() && - eventStart.getMonth() === date.getMonth() && - eventStart.getFullYear() === date.getFullYear() - ) { - return true; - } - - if (!event.end) return false; - - const eventEnd = new Date(event.end); - - return ( - eventEnd.getDate() === date.getDate() && - eventEnd.getMonth() === date.getMonth() && - eventEnd.getFullYear() === date.getFullYear() - ); - }); - } - - private handleDateClick(date: Date) { - this.dispatchEvent(new CustomEvent('date-click', { detail: { date } })); - } - - private handleEventClick(event: CalendarEventData) { - this.dispatchEvent(new CustomEvent('event-click', { detail: { event } })); - } - - override render() { - const firstDay = new Date(2024, 0, 1); - const dayNames = Array.from({ length: 7 }, (_, i) => { - const date = new Date(firstDay); - date.setDate(firstDay.getDate() + i); - return date.toLocaleDateString(this.locale, { weekday: 'short' }); - }); - - return html` -
- ${dayNames.map((day) => html`
${day}
`)} - ${this.getCalendarDays().map( - (date) => html` -
this.handleDateClick(date)} - > -
${date.getDate()}
-
- ${this.getEventsForDate(date).map( - (event) => html` - this.handleEventClick(e.detail.event)} - > - `, - )} -
-
- `, - )} -
- `; - } -} diff --git a/src/components/Calendar/CalendarWeekView.ts b/src/components/Calendar/CalendarWeekView.ts deleted file mode 100644 index 50a086d..0000000 --- a/src/components/Calendar/CalendarWeekView.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { LitElement, css, html } from 'lit'; -import { customElement, property } from 'lit/decorators.js'; -import type { CalendarEventData } from './CalendarEvent'; - -@customElement('arx-calendar-week-view') -export class CalendarWeekView extends LitElement { - @property({ type: Date }) - currentDate = new Date(); - - @property({ type: Array }) - events: CalendarEventData[] = []; - - @property({ type: String }) - locale = 'en-US'; - - static override styles = css` - .week-view { - display: grid; - grid-template-columns: 1fr; - gap: 0.5rem; - } - - .week-day { - display: grid; - grid-template-columns: 100px 1fr; - gap: 1rem; - padding: 1rem; - background: var(--color-base-100); - border-radius: var(--radius-selector); - } - - .week-day-header { - font-weight: 600; - color: var(--color-base-content); - } - - .week-day-content { - min-height: 100px; - border-left: 1px solid var(--color-base-300); - padding-left: 1rem; - } - `; - - private getWeekDays(): Date[] { - const days: Date[] = []; - const currentDay = this.currentDate.getDay(); - const startOfWeek = new Date(this.currentDate); - startOfWeek.setDate(this.currentDate.getDate() - currentDay); - - for (let i = 0; i < 7; i++) { - const date = new Date(startOfWeek); - date.setDate(startOfWeek.getDate() + i); - days.push(date); - } - - return days; - } - - private getEventsForDate(date: Date): CalendarEventData[] { - return this.events.filter((event) => { - const eventStart = new Date(event.start); - if ( - eventStart.getDate() === date.getDate() && - eventStart.getMonth() === date.getMonth() && - eventStart.getFullYear() === date.getFullYear() - ) { - return true; - } - - if (!event.end) return false; - - const eventEnd = new Date(event.end); - - return ( - eventEnd.getDate() === date.getDate() && - eventEnd.getMonth() === date.getMonth() && - eventEnd.getFullYear() === date.getFullYear() - ); - }); - } - - private handleEventClick(event: CalendarEventData) { - this.dispatchEvent(new CustomEvent('event-click', { detail: { event } })); - } - - override render() { - return html` -
- ${this.getWeekDays().map( - (date) => html` -
-
- ${date.toLocaleDateString(this.locale, { weekday: 'long' })} -
- ${date.toLocaleDateString(this.locale, { month: 'short', day: 'numeric' })} -
-
- ${this.getEventsForDate(date).map( - (event) => html` - this.handleEventClick(e.detail.event)} - > - `, - )} -
-
- `, - )} -
- `; - } -} diff --git a/src/components/General/Button.ts b/src/components/General/Button.ts index 2506722..86c4487 100644 --- a/src/components/General/Button.ts +++ b/src/components/General/Button.ts @@ -187,7 +187,6 @@ export class StyledButton extends LitElement { private _handleClick(e: MouseEvent) { e.preventDefault(); - e.stopPropagation(); if (this.disabled || this.loading) { return; } @@ -208,8 +207,8 @@ export class StyledButton extends LitElement { name: this.name, value: this.value, }, - bubbles: false, - composed: false, + bubbles: true, + composed: true, }), ); return; diff --git a/src/components/General/Dialog.ts b/src/components/General/Dialog.ts deleted file mode 100644 index b78740a..0000000 --- a/src/components/General/Dialog.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { LitElement, css, html } from 'lit'; -import { customElement, property, state } from 'lit/decorators.js'; -import { classMap } from 'lit/directives/class-map.js'; - -@customElement('arx-dialog') -export class EveDialog extends LitElement { - @property({ type: String }) override title = ''; - @property({ type: Boolean }) open = false; - @property({ type: Boolean }) closeOnOverlayClick = true; - @property({ type: Boolean }) closeOnEscape = true; - @property({ type: String }) width = '420px'; - @property({ type: String }) maxWidth = '90%'; - - @state() private _previousFocus: HTMLElement | null = null; - - static override styles = css` - :host { - display: block; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 999999; - } - - .overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: oklch(from var(--color-base-content) l c h / 0.6); - display: flex; - align-items: center; - justify-content: center; - z-index: 9999; - opacity: 0; - pointer-events: none; - transition: opacity 0.2s ease; - backdrop-filter: blur(4px); - } - - .overlay.active { - opacity: 1; - pointer-events: all; - } - - .dialog-container { - background-color: var(--color-base-100); - border-radius: var(--radius-box); - border: var(--border) solid var(--color-base-300); - box-shadow: calc(var(--depth) * 4px) calc(var(--depth) * 4px) - calc(var(--depth) * 8px) - oklch(from var(--color-base-content) l c h / 0.2), - calc(var(--depth) * -1px) calc(var(--depth) * -1px) - calc(var(--depth) * 4px) oklch(from var(--color-base-100) l c h / 0.4); - width: var(--dialog-width, 420px); - max-width: var(--dialog-max-width, 90%); - padding: 28px; - transform: scale(0.95) translateY(10px); - transition: transform 0.25s cubic-bezier(0.1, 1, 0.2, 1); - color: var(--color-base-content); - } - - .overlay.active .dialog-container { - transform: scale(1) translateY(0); - } - - .dialog-header { - font-size: 18px; - font-weight: 600; - margin: 0 0 16px 0; - line-height: 1.4; - color: var(--color-base-content); - } - - .dialog-content { - margin-bottom: 24px; - } - - .dialog-footer { - display: flex; - justify-content: flex-end; - gap: 12px; - } - - .dialog-container:focus-within { - box-shadow: calc(var(--depth) * 4px) calc(var(--depth) * 4px) - calc(var(--depth) * 8px) - oklch(from var(--color-base-content) l c h / 0.2), - calc(var(--depth) * -1px) calc(var(--depth) * -1px) - calc(var(--depth) * 4px) oklch(from var(--color-base-100) l c h / 0.4), - 0 0 0 2px oklch(from var(--color-accent) l c h / 0.2); - } - `; - - constructor() { - super(); - this._handleKeyDown = this._handleKeyDown.bind(this); - } - - override connectedCallback() { - super.connectedCallback(); - document.addEventListener('keydown', this._handleKeyDown); - } - - override disconnectedCallback() { - super.disconnectedCallback(); - document.removeEventListener('keydown', this._handleKeyDown); - } - - override updated(changedProps: Map) { - if (changedProps.has('open') && this.open) { - this._previousFocus = document.activeElement as HTMLElement; - // Focus the dialog container after rendering - setTimeout(() => { - const container = this.shadowRoot?.querySelector('.dialog-container') as HTMLElement; - if (container) container.focus(); - }, 50); - } else if (changedProps.has('open') && !this.open && this._previousFocus) { - this._previousFocus.focus(); - } - } - - private _handleKeyDown(e: KeyboardEvent) { - if (!this.open || !this.closeOnEscape) return; - if (e.key === 'Escape') this.close(); - } - - private _handleOverlayClick(e: MouseEvent) { - if (this.closeOnOverlayClick && e.target === e.currentTarget) { - this.close(); - } - } - - show() { - this.open = true; - } - - close() { - this.open = false; - this.dispatchEvent(new CustomEvent('close')); - } - - override render() { - return html` -
-
- ${this.title ? html`
${this.title}
` : ''} -
- -
- -
-
- `; - } -} diff --git a/src/components/General/Input.ts b/src/components/General/Input.ts index 52b19e1..3b20e82 100644 --- a/src/components/General/Input.ts +++ b/src/components/General/Input.ts @@ -13,22 +13,15 @@ export class StyledInput extends LitElement { @property() placeholder = ''; @property() value = ''; @property({ type: Boolean }) disabled = false; - @property() type: 'text' | 'number' | 'password' | 'date' | 'time' = 'text'; + @property() type: 'text' | 'number' | 'password' = 'text'; @property() name = ''; @property({ type: Boolean }) required = false; @property() label = ''; - @state() override lang = 'en-US'; @query('input') private _input!: HTMLInputElement; @state() private _value = ''; - override connectedCallback(): void { - super.connectedCallback(); - const dateTimeFormatOptions = JSON.parse(localStorage.getItem('dateTimeFormatOptions') || '{}'); - if (dateTimeFormatOptions?.locale) this.lang = dateTimeFormatOptions.locale; - } - protected override firstUpdated(_changedProperties: PropertyValues): void { this._value = this.value; } @@ -129,7 +122,6 @@ export class StyledInput extends LitElement { placeholder=${this.placeholder} type=${this.type} name=${this.name} - lang=${this.lang} @input=${this._handleInput} @focus=${this._handleFocus} @blur=${this._handleBlur} diff --git a/src/components/General/Prompt.ts b/src/components/General/Prompt.ts index ac2fab1..8bb5142 100644 --- a/src/components/General/Prompt.ts +++ b/src/components/General/Prompt.ts @@ -1,9 +1,8 @@ import type { ArxInputChangeEvent } from '@components/General/Input'; import { LitElement, css, html } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; -import { when } from 'lit/directives/when.js'; +import { classMap } from 'lit/directives/class-map.js'; -import '@components/General/Dialog'; import '@components/General/Input'; @customElement('arx-prompt') @@ -17,19 +16,122 @@ export class EvePrompt extends LitElement { @property({ type: String }) defaultValue = ''; @state() private _inputValue = ''; + private _previousFocus: HTMLElement | null = null; static override styles = css` :host { display: block; } + + .overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: oklch(from var(--color-base-content) l c h / 0.6); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + opacity: 0; + pointer-events: none; + transition: opacity 0.2s ease; + backdrop-filter: blur(4px); + } + + .overlay.active { + opacity: 1; + pointer-events: all; + } + + .prompt-container { + background-color: var(--color-base-100); + border-radius: var(--radius-box); + border: var(--border) solid var(--color-base-300); + box-shadow: calc(var(--depth) * 4px) calc(var(--depth) * 4px) + calc(var(--depth) * 8px) + oklch(from var(--color-base-content) l c h / 0.2), + calc(var(--depth) * -1px) calc(var(--depth) * -1px) + calc(var(--depth) * 4px) oklch(from var(--color-base-100) l c h / 0.4); + width: 90%; + max-width: 420px; + padding: 28px; + transform: scale(0.95) translateY(10px); + transition: transform 0.25s cubic-bezier(0.1, 1, 0.2, 1); + color: var(--color-base-content); + } + + .overlay.active .prompt-container { + transform: scale(1) translateY(0); + } + + .prompt-header { + font-size: 18px; + font-weight: 600; + margin: 0 0 16px 0; + line-height: 1.4; + color: var(--color-base-content); + } + + .buttons { + display: flex; + justify-content: flex-end; + gap: 12px; + margin-top: 24px; + } + + .prompt-container:focus-within { + box-shadow: calc(var(--depth) * 4px) calc(var(--depth) * 4px) + calc(var(--depth) * 8px) + oklch(from var(--color-base-content) l c h / 0.2), + calc(var(--depth) * -1px) calc(var(--depth) * -1px) + calc(var(--depth) * 4px) oklch(from var(--color-base-100) l c h / 0.4), + 0 0 0 2px oklch(from var(--color-accent) l c h / 0.2); + } `; + constructor() { + super(); + this._handleKeyDown = this._handleKeyDown.bind(this); + } + + override connectedCallback() { + super.connectedCallback(); + document.addEventListener('keydown', this._handleKeyDown); + } + + override disconnectedCallback() { + super.disconnectedCallback(); + document.removeEventListener('keydown', this._handleKeyDown); + } + override updated(changedProps: Map) { if (changedProps.has('open') && this.open) { this._inputValue = this.defaultValue; + this._previousFocus = document.activeElement as HTMLElement; + + // Focus the input or save button after rendering + setTimeout(() => { + if (this.showInput) { + const input = this.shadowRoot?.querySelector('.input-field') as HTMLElement; + if (input) input.focus(); + } else { + const saveBtn = this.shadowRoot?.querySelector('.save-btn') as HTMLElement; + if (saveBtn) saveBtn.focus(); + } + }, 50); + } else if (changedProps.has('open') && !this.open && this._previousFocus) { + this._previousFocus.focus(); } } + private _handleKeyDown(e: KeyboardEvent) { + if (!this.open) return; + if (e.key === 'Escape') this._handleCancel(); + if (e.key === 'Enter' && !e.shiftKey) this._handleSave(); + } + private _handleInputChange(e: ArxInputChangeEvent) { this._inputValue = e.detail.value; } @@ -50,37 +152,43 @@ export class EvePrompt extends LitElement { override render() { return html` - - ${when( - this.showInput, - () => html` - - `, - )} -
- - +
+
${this.promptText}
+ + ${ + this.showInput + ? html` + + ` + : '' + } + +
+ + + + +
- +
`; } diff --git a/src/components/General/Toggle.ts b/src/components/General/Toggle.ts index e203e99..ed9fac5 100644 --- a/src/components/General/Toggle.ts +++ b/src/components/General/Toggle.ts @@ -11,11 +11,6 @@ export class StyledToggle extends LitElement { @property({ type: Boolean }) required = false; @property() value = 'on'; - override connectedCallback() { - super.connectedCallback(); - if (!this.checked) this.value = 'off'; - } - static override styles = css` :host { display: inline-block; diff --git a/src/components/InitialSetup.ts b/src/components/InitialSetup.ts index 708e4e8..76dfd1e 100644 --- a/src/components/InitialSetup.ts +++ b/src/components/InitialSetup.ts @@ -17,7 +17,6 @@ import '@components/General/Fieldset'; import '@components/General/Input'; import '@components/LoadingView'; import '@components/ProgressSteps'; -import '@components/RelayLogs'; @customElement('arx-initial-setup') export class InitialSetup extends LitElement { @@ -396,7 +395,8 @@ export class InitialSetup extends LitElement { Relay is running with PID: ${this.relayStatus.pid}

- `, +
${this.relayStatus.logs.slice(-5).join('\n')}
+ `, )}

Having trouble? Our team is here to help if you encounter any diff --git a/src/components/RelayLogs.ts b/src/components/RelayLogs.ts deleted file mode 100644 index d3939cc..0000000 --- a/src/components/RelayLogs.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { ansiToLitHtml } from '@/utils/ansiToLitHtml'; -import { LitElement, css, html } from 'lit'; -import { customElement, property } from 'lit/decorators.js'; -import { map } from 'lit/directives/map.js'; - -@customElement('arx-relay-logs') -export class RelayLogs extends LitElement { - static override styles = css` - :host { - display: block; - font-family: var(--font-family, "Inter", system-ui, sans-serif); - margin: 0 auto; - line-height: 1.6; - color: var(--color-base-content); - } - - .relay-logs { - white-space: pre-wrap; - word-wrap: break-word; - overflow-wrap: break-word; - overflow-x: auto; - max-height: 200px; - padding: 1rem; - border: 2px solid var(--color-base-300); - border-radius: var(--radius-box); - border-radius: var(--radius-md); - background-color: var(--color-code-block); - color: var(--color-code-block-content); - } - `; - - @property({ type: Array }) logs: string[] = []; - - override render() { - return html` -

${map(this.logs, (log) => ansiToLitHtml(log))}
- `; - } -} diff --git a/src/routes/Calendar.ts b/src/routes/Calendar.ts deleted file mode 100644 index 08bb3c6..0000000 --- a/src/routes/Calendar.ts +++ /dev/null @@ -1,330 +0,0 @@ -import type { CalendarEventData } from '@/components/Calendar/CalendarEvent'; -import { getSigner, ndk } from '@/ndk'; -import { NDKEvent, type NDKKind } from '@nostr-dev-kit/ndk'; -import { LitElement, html } from 'lit'; -import { customElement, state } from 'lit/decorators.js'; -import { when } from 'lit/directives/when.js'; - -import '@components/Calendar/CalendarEvent'; -import '@components/Calendar/CalendarEventDetailsDialog'; -import '@components/Calendar/CalendarEventDialog'; -import '@components/Calendar/CalendarHeader'; -import '@components/Calendar/CalendarMonthView'; -import '@components/Calendar/CalendarWeekView'; - -type CalendarView = 'month' | 'week'; - -interface NostrEvent { - id: string; - pubkey: string; - created_at: number | undefined; - kind: number; - content: string; - tags: string[][]; -} - -@customElement('arx-calendar-route') -export default class CalendarRoute extends LitElement { - @state() - private currentDate = new Date(); - - @state() - private selectedDate: Date | null = null; - - @state() - private view: CalendarView = 'month'; - - @state() - private locale = 'en-US'; - - @state() - private events: CalendarEventData[] = []; - - @state() - private showAddEventDialog = false; - - @state() - private selectedEvent: CalendarEventData | null = null; - - override connectedCallback() { - super.connectedCallback(); - this.loadLocale(); - this.loadEvents(); - } - - private loadLocale() { - try { - const options = localStorage.getItem('dateTimeFormatOptions'); - if (options) { - const { locale } = JSON.parse(options); - if (locale) { - this.locale = locale; - } - } - } catch { - // Do nothing - } - } - - private async loadEvents() { - await getSigner(); - try { - const dateEvents = await ndk.fetchEvents({ - kinds: [31922 as NDKKind], - }); - const timeEvents = await ndk.fetchEvents({ - kinds: [31923 as NDKKind], - }); - - const events = [ - ...Array.from(dateEvents).map((event) => this.parseDateEvent(event as unknown as NostrEvent)), - ...Array.from(timeEvents).map((event) => this.parseTimeEvent(event as unknown as NostrEvent)), - ]; - - this.events = events; - } catch (error) { - console.error('Error loading events:', error); - } - } - - private parseDateEvent(event: NostrEvent): CalendarEventData { - const startTag = event.tags.find((t) => t[0] === 'start'); - const titleTag = event.tags.find((t) => t[0] === 'title'); - if (!startTag || !titleTag) { - throw new Error('Invalid event: missing required tags'); - } - - const start = new Date(startTag[1]); - const endTag = event.tags.find((t) => t[0] === 'end'); - const end = endTag ? new Date(endTag[1]) : undefined; - - return { - id: event.id, - title: titleTag[1], - start, - end, - description: event.content, - location: event.tags.find((t) => t[0] === 'location')?.[1], - participants: event.tags.filter((t) => t[0] === 'p').map((t) => t[1]), - tags: event.tags.filter((t) => t[0] === 't').map((t) => t[1]), - type: 'date', - }; - } - - private parseTimeEvent(event: NostrEvent): CalendarEventData { - const startTag = event.tags.find((t) => t[0] === 'start'); - const titleTag = event.tags.find((t) => t[0] === 'title'); - if (!startTag || !titleTag) { - throw new Error('Invalid event: missing required tags'); - } - - const start = new Date(Number.parseInt(startTag[1]) * 1000); - const endTag = event.tags.find((t) => t[0] === 'end'); - const end = endTag ? new Date(Number.parseInt(endTag[1]) * 1000) : undefined; - - return { - id: event.id, - title: titleTag[1], - start, - end, - description: event.content, - location: event.tags.find((t) => t[0] === 'location')?.[1], - participants: event.tags.filter((t) => t[0] === 'p').map((t) => t[1]), - tags: event.tags.filter((t) => t[0] === 't').map((t) => t[1]), - type: 'time', - }; - } - - private handlePrev() { - if (this.view === 'month') { - const newDate = new Date(this.currentDate); - newDate.setMonth(newDate.getMonth() - 1); - if (newDate.getMonth() === this.currentDate.getMonth()) newDate.setDate(0); - this.currentDate = newDate; - } else { - const newDate = new Date(this.currentDate); - newDate.setDate(newDate.getDate() - 7); - this.currentDate = newDate; - } - } - - private handleNext() { - if (this.view === 'month') { - const newDate = new Date(this.currentDate); - newDate.setMonth(newDate.getMonth() + 1); - if (newDate.getMonth() === this.currentDate.getMonth()) newDate.setDate(32); - this.currentDate = newDate; - } else { - const newDate = new Date(this.currentDate); - newDate.setDate(newDate.getDate() + 7); - this.currentDate = newDate; - } - } - - private handleToday() { - this.currentDate = new Date(); - } - - private handleViewChange(e: CustomEvent) { - this.view = e.detail.view; - } - - private handleAddEvent() { - this.showAddEventDialog = true; - } - - private handleCloseDialog() { - this.showAddEventDialog = false; - } - - private handleDateClick(e: CustomEvent) { - this.selectedDate = e.detail.date; - } - - private handleEventClick(e: CustomEvent) { - this.selectedEvent = e.detail.event; - } - - private handleCloseEventDetails() { - this.selectedEvent = null; - } - - private async handleSubmit(e: CustomEvent) { - await getSigner(); - try { - const event = new NDKEvent(ndk); - const { allDay, ...newEvent } = e.detail.event; - event.kind = allDay ? 31922 : 31923; - event.content = newEvent.description; - - const startDate = new Date(newEvent.startDate); - if (allDay) { - startDate.setHours(0, 0, 0, 0); - } else if (newEvent.startTime) { - const [hours, minutes] = newEvent.startTime.split(':'); - startDate.setHours(Number.parseInt(hours), Number.parseInt(minutes)); - } - - const tags = [ - ['title', newEvent.title], - ['start', allDay ? startDate.toISOString().split('T')[0] : Math.floor(startDate.getTime() / 1000).toString()], - ]; - - if (newEvent.endDate) { - const endDate = new Date(newEvent.endDate); - if (allDay) { - endDate.setHours(0, 0, 0, 0); - } else if (newEvent.endTime) { - const [hours, minutes] = newEvent.endTime.split(':'); - endDate.setHours(Number.parseInt(hours), Number.parseInt(minutes)); - } - tags.push([ - 'end', - allDay ? endDate.toISOString().split('T')[0] : Math.floor(endDate.getTime() / 1000).toString(), - ]); - } - - if (newEvent.location) { - tags.push(['location', newEvent.location]); - } - - for (const participant of newEvent.participants) { - tags.push(['p', participant]); - } - - for (const tag of newEvent.tags) { - tags.push(['t', tag]); - } - - event.tags = tags; - await event.sign(); - await event.publish(); - - this.handleCloseDialog(); - await this.loadEvents(); - } catch (error) { - console.error('Error creating event:', error); - } - } - - private formatDateRange(): string { - if (this.view === 'month') { - return this.currentDate.toLocaleDateString(this.locale, { month: 'long', year: 'numeric' }); - } - - const weekDays = this.getWeekDays(); - const firstDay = weekDays[0]; - const lastDay = weekDays[6]; - - if (firstDay.getMonth() === lastDay.getMonth()) - return `${firstDay.toLocaleDateString(this.locale, { month: 'long', day: 'numeric' })} - ${lastDay.toLocaleDateString(this.locale, { day: 'numeric' })}, ${firstDay.getFullYear()}`; - - return `${firstDay.toLocaleDateString(this.locale, { month: 'long', day: 'numeric' })} - ${lastDay.toLocaleDateString(this.locale, { month: 'long', day: 'numeric' })}, ${firstDay.getFullYear()}`; - } - - private getWeekDays(): Date[] { - const days: Date[] = []; - const currentDay = this.currentDate.getDay(); - const startOfWeek = new Date(this.currentDate); - startOfWeek.setDate(this.currentDate.getDate() - currentDay); - - for (let i = 0; i < 7; i++) { - const date = new Date(startOfWeek); - date.setDate(startOfWeek.getDate() + i); - days.push(date); - } - - return days; - } - - override render() { - return html` - - - ${when( - this.view === 'month', - () => html` - - `, - () => html` - - `, - )} - - - - - `; - } -} diff --git a/src/routes/Settings.ts b/src/routes/Settings.ts index 32edf75..4886845 100644 --- a/src/routes/Settings.ts +++ b/src/routes/Settings.ts @@ -12,7 +12,6 @@ import '@components/General/Button'; import '@components/General/Card'; import '@components/General/Fieldset'; import '@components/General/Input'; -import '@components/RelayLogs'; @customElement('arx-settings') export class EveSettings extends LitElement { @@ -62,22 +61,11 @@ export class EveSettings extends LitElement { @state() private profile: NDKUserProfile | undefined; @state() private error: string | undefined; @state() private darkMode = false; - @state() private relayStatus: { running: boolean; pid: number | null; logs: string[] } = { - running: false, - pid: null, - logs: [], - }; - - private async updateRelayStatus() { - this.relayStatus = await window.relay.getStatus(); - if (this.relayStatus.running) setTimeout(() => this.updateRelayStatus(), 2000); - } protected override async firstUpdated() { try { this.profile = await getUserProfile(); this.darkMode = localStorage.getItem('darkMode') === 'true'; - this.updateRelayStatus(); this.loading = false; } catch (err) { this.error = 'Failed to load profile'; @@ -188,14 +176,6 @@ export class EveSettings extends LitElement { > - - -

- ${this.relayStatus.running ? `Relay is running. PID: ${this.relayStatus.pid}` : 'Relay is not running'} -

- -
-
`; } } diff --git a/src/routes/router.ts b/src/routes/router.ts index 7e93762..ab7a796 100644 --- a/src/routes/router.ts +++ b/src/routes/router.ts @@ -4,7 +4,6 @@ import '@routes/Arbor/Home'; import '@routes/Arbor/NewPost'; import '@routes/Arbor/NewTopic'; import '@routes/Arbor/TopicView'; -import '@routes/Calendar'; import '@routes/Home'; import '@routes/Profile'; import '@routes/Settings'; @@ -37,11 +36,6 @@ export default class EveRouter extends LitElement { params: {}, component: literal`arx-eve-home`, }, - { - pattern: 'calendar', - params: {}, - component: literal`arx-calendar-route`, - }, { pattern: 'profile/:npub', params: {}, diff --git a/src/style.css b/src/style.css index cee2999..bb02569 100644 --- a/src/style.css +++ b/src/style.css @@ -34,8 +34,6 @@ --color-warning-content: oklch(41% 0.112 45.904); --color-error: oklch(70% 0.191 22.216); --color-error-content: oklch(39% 0.141 25.723); - --color-code-block: oklch(95% 0.038 75.164); - --color-code-block-content: oklch(40% 0.123 38.172); --radius-selector: 1rem; --radius-field: 0.5rem; @@ -70,8 +68,6 @@ body.dark { --color-warning-content: oklch(19.106% 0.026 112.757); --color-error: oklch(68.22% 0.206 24.43); --color-error-content: oklch(13.644% 0.041 24.43); - --color-code-block: oklch(20% 0.05 270); - --color-code-block-content: oklch(90% 0.076 70.697); --radius-selector: 1rem; --radius-field: 0.5rem; --radius-box: 1rem; diff --git a/src/utils/ansiToLitHtml.ts b/src/utils/ansiToLitHtml.ts deleted file mode 100644 index e1d2aa9..0000000 --- a/src/utils/ansiToLitHtml.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { unsafeHTML } from 'lit/directives/unsafe-html.js'; - -export function ansiToLitHtml(text: string) { - const textColors: Record = { - '30': 'black', - '31': 'red', - '32': 'green', - '33': 'yellow', - '34': 'blue', - '35': 'magenta', - '36': 'cyan', - '37': 'white', - '90': 'gray', - '91': 'lightred', - '92': 'lightgreen', - '93': 'lightyellow', - '94': 'lightblue', - '95': 'lightmagenta', - '96': 'lightcyan', - '97': 'white', - }; - - const bgColors: Record = { - '40': 'black', - '41': 'red', - '42': 'green', - '43': 'yellow', - '44': 'blue', - '45': 'magenta', - '46': 'cyan', - '47': 'white', - '100': 'gray', - '101': 'lightred', - '102': 'lightgreen', - '103': 'lightyellow', - '104': 'lightblue', - '105': 'lightmagenta', - '106': 'lightcyan', - '107': 'white', - }; - - const styles: Record = { - '1': 'font-weight: bold;', - '3': 'font-style: italic;', - '4': 'text-decoration: underline;', - }; - - const ESC = '\u001b'; - const ansiPattern = new RegExp(`${ESC}\\[(\\d+(?:;\\d+)*)m`, 'g'); - const openTags: string[] = []; - - let result = text.replace(ansiPattern, (_, codes) => { - const codeArray = codes.split(';'); - - if (codeArray.includes('0')) { - const closeTags = openTags.length > 0 ? ''.repeat(openTags.length) : ''; - openTags.length = 0; - return closeTags; - } - - let style = ''; - - for (const code of codeArray) { - if (textColors[code]) { - style += `color: ${textColors[code]};`; - } else if (bgColors[code]) { - style += `background-color: ${bgColors[code]};`; - } else if (styles[code]) { - style += styles[code]; - } - } - - if (style) { - openTags.push('span'); - return ``; - } - - return ''; - }); - - if (openTags.length > 0) result += ''.repeat(openTags.length); - - return unsafeHTML(`${result}\n`); -}