Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions app/internal_packages/main-calendar/lib/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,20 @@ const Notice = () =>
Notice.displayName = 'Notice';

export function activate() {
ComponentRegistry.register(MailspringCalendar, {
location: WorkspaceStore.Location.Center,
});
ComponentRegistry.register(Notice, {
location: WorkspaceStore.Sheet.Main.Header,
});
ComponentRegistry.register(QuickEventButton, {
location: WorkspaceStore.Location.Center.Toolbar,
});
ComponentRegistry.register(EventSearchBar, {
location: WorkspaceStore.Location.Center.Toolbar,
});
if (AppEnv.getWindowType() === 'calendar') {
ComponentRegistry.register(MailspringCalendar, {
location: WorkspaceStore.Location.Center,
});
ComponentRegistry.register(Notice, {
location: WorkspaceStore.Sheet.Main.Header,
});
ComponentRegistry.register(QuickEventButton, {
location: WorkspaceStore.Location.Center.Toolbar,
});
ComponentRegistry.register(EventSearchBar, {
location: WorkspaceStore.Location.Center.Toolbar,
});
}
}

export function deactivate() {
Expand Down
1 change: 1 addition & 0 deletions app/internal_packages/main-calendar/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"mailspring": "*"
},
"windowTypes": {
"default": true,
"calendar": true
}
}
13 changes: 4 additions & 9 deletions app/internal_packages/message-list/lib/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,15 @@ import {

import { MessageListHiddenMessagesToggle } from './message-list-hidden-messages-toggle';
import MessageList from './message-list';
import { SidebarPluginContainer } from './sidebar-plugin-container';
import { SidebarParticipantPicker } from './sidebar-participant-picker';
import { SidebarViewSwitcher } from './sidebar-view-switcher';

export function activate() {
if (AppEnv.isMainWindow()) {
// Register Message List Actions we provide globally
ComponentRegistry.register(MessageList, {
location: WorkspaceStore.Location.MessageList,
});
ComponentRegistry.register(SidebarParticipantPicker, {
location: WorkspaceStore.Location.MessageListSidebar,
});
ComponentRegistry.register(SidebarPluginContainer, {
ComponentRegistry.register(SidebarViewSwitcher, {
location: WorkspaceStore.Location.MessageListSidebar,
});
ComponentRegistry.register(MessageListHiddenMessagesToggle, {
Expand All @@ -33,7 +29,7 @@ export function activate() {
ComponentRegistry.register(MessageList, { location: WorkspaceStore.Location.Center });

// We need to locate the thread and focus it so that the MessageList displays it
DatabaseStore.find<Thread>(Thread, threadId).then(thread =>
DatabaseStore.find<Thread>(Thread, threadId).then((thread) =>
Actions.setFocus({ collection: 'thread', item: thread })
);

Expand All @@ -48,6 +44,5 @@ export function activate() {

export function deactivate() {
ComponentRegistry.unregister(MessageList);
ComponentRegistry.unregister(SidebarPluginContainer);
ComponentRegistry.unregister(SidebarParticipantPicker);
ComponentRegistry.unregister(SidebarViewSwitcher);
}
125 changes: 125 additions & 0 deletions app/internal_packages/message-list/lib/sidebar-agenda-view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import moment, { Moment } from 'moment';
import React from 'react';
import { Rx, DatabaseStore, Calendar, Actions, localized } from 'mailspring-exports';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused imports: localized is imported here but never used in this file. Also CalendarView (line 10) is imported but unused.

Suggested change
import { Rx, DatabaseStore, Calendar, Actions, localized } from 'mailspring-exports';
import { Rx, DatabaseStore, Calendar, Actions } from 'mailspring-exports';

import {
CalendarDataSource,
EventOccurrence,
FocusedEventInfo,
} from '../../main-calendar/lib/core/calendar-data-source';
import { AgendaView } from '../../main-calendar/lib/core/agenda-view';
import { CalendarView } from '../../main-calendar/lib/core/calendar-constants';
import { CalendarEventPopover } from '../../main-calendar/lib/core/calendar-event-popover';
import { Disposable } from 'rx-core';

const DISABLED_CALENDARS = 'mailspring.disabledCalendars';

interface SidebarAgendaViewState {
focusedMoment: Moment;
selectedEvents: EventOccurrence[];
focusedEvent: FocusedEventInfo | null;
disabledCalendars: string[];
calendars: Calendar[];
}

export class SidebarAgendaView extends React.Component<{}, SidebarAgendaViewState> {
static displayName = 'SidebarAgendaView';

_dataSource = new CalendarDataSource();
_configDisposable?: Disposable;
_calDisposable?: Disposable;

constructor(props: {}) {
super(props);
this.state = {
focusedMoment: moment(),
selectedEvents: [],
focusedEvent: null,
disabledCalendars: AppEnv.config.get(DISABLED_CALENDARS) || [],
calendars: [],
};
}

componentDidMount() {
this._configDisposable = Rx.Observable.fromConfig<string[] | undefined>(
DISABLED_CALENDARS
).subscribe((disabledCalendars) => {
this.setState({ disabledCalendars: disabledCalendars || [] });
});

const calQuery = DatabaseStore.findAll<Calendar>(Calendar);
this._calDisposable = Rx.Observable.fromQuery(calQuery).subscribe((calendars) => {
this.setState({ calendars });
});
}

componentWillUnmount() {
this._configDisposable?.dispose();
this._calDisposable?.dispose();
}

_getReadOnlyCalendarIds(): Set<string> {
const ids = new Set<string>();
for (const cal of this.state.calendars) {
if (cal.readOnly) {
ids.add(cal.id);
}
}
return ids;
}

_onChangeFocusedMoment = (m: Moment) => {
this.setState({ focusedMoment: m, focusedEvent: null });
};

_onChangeView = () => {
// No-op in the sidebar — we always show the agenda view
};

_onEventClick = (e: React.MouseEvent, event: EventOccurrence) => {
this.setState({ selectedEvents: [event], focusedEvent: null });
};

_onEventDoubleClick = (event: EventOccurrence) => {
const el = document.getElementById(event.id);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dead code with a latent bug: AgendaView handles double-click internally via _onAgendaEventDoubleClick (line 184 of agenda-view.tsx) using e.currentTarget, and never calls this.props.onEventDoubleClick. So this handler is never invoked.

Additionally, if it were called, document.getElementById(event.id) would always return null because AgendaView renders event elements with day-unique IDs: id={\${event.id}-${dayKey}`}. The _onEventFocused` handler (line 93) delegates here, so it has the same issue.

Consider removing these handlers and passing no-ops, or fixing the lookup to match the actual DOM IDs.

if (!el) return;
Actions.openPopover(<CalendarEventPopover event={event} />, {
originRect: el.getBoundingClientRect(),
direction: 'left',
fallbackDirection: 'right',
closeOnAppBlur: false,
});
};

_onEventFocused = (event: EventOccurrence) => {
this._onEventDoubleClick(event);
};

// No-ops for drag and mouse handlers — not needed in sidebar
_noopMouseHandler = () => {};
_noopDragStart = () => {};

render() {
return (
<div className="sidebar-agenda-view mailspring-calendar">
<AgendaView
dataSource={this._dataSource}
disabledCalendars={this.state.disabledCalendars}
focusedMoment={this.state.focusedMoment}
focusedEvent={this.state.focusedEvent}
selectedEvents={this.state.selectedEvents}
onChangeView={this._onChangeView}
onChangeFocusedMoment={this._onChangeFocusedMoment}
onEventClick={this._onEventClick}
onEventDoubleClick={this._onEventDoubleClick}
onEventFocused={this._onEventFocused}
onCalendarMouseUp={this._noopMouseHandler}
onCalendarMouseDown={this._noopMouseHandler}
onCalendarMouseMove={this._noopMouseHandler}
dragState={null}
onEventDragStart={this._noopDragStart}
readOnlyCalendarIds={this._getReadOnlyCalendarIds()}
/>
</div>
);
}
}
109 changes: 109 additions & 0 deletions app/internal_packages/message-list/lib/sidebar-view-switcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React from 'react';
import { PropTypes, FocusedContactsStore, Contact } from 'mailspring-exports';
import { InjectedComponentSet } from 'mailspring-component-kit';
import { SidebarViewToggle, SidebarView } from './sidebar-view-toggle';
import { SidebarParticipantPicker } from './sidebar-participant-picker';
import { SidebarAgendaView } from './sidebar-agenda-view';

class FocusedContactStorePropsContainer extends React.Component<
{ children: React.ReactElement<any> },
{ sortedContacts: Contact[]; focusedContact: Contact }
> {
static displayName = 'FocusedContactStorePropsContainer';

unsubscribe: () => void;

constructor(props) {
super(props);
this.state = this._getStateFromStores();
}

componentDidMount() {
this.unsubscribe = FocusedContactsStore.listen(this._onChange);
}

componentWillUnmount() {
this.unsubscribe();
}

_onChange = () => {
this.setState(this._getStateFromStores());
};

_getStateFromStores() {
return {
sortedContacts: FocusedContactsStore.sortedContacts(),
focusedContact: FocusedContactsStore.focusedContact(),
};
}

render() {
let classname = 'sidebar-section';
let inner = null;
if (this.state.focusedContact) {
classname += ' visible';
inner = React.cloneElement(this.props.children, this.state);
}
return <div className={classname}>{inner}</div>;
}
}

const SidebarPluginContainerInner = (props) => {
return (
<InjectedComponentSet
className="sidebar-contact-card"
key={props.focusedContact.email}
matching={{ role: 'MessageListSidebar:ContactCard' }}
direction="column"
exposedProps={{
contact: props.focusedContact,
}}
/>
);
};

SidebarPluginContainerInner.propTypes = {
focusedContact: PropTypes.object,
};

export class SidebarViewSwitcher extends React.Component<{}, { selectedView: SidebarView }> {
static displayName = 'SidebarViewSwitcher';

static containerStyles = {
order: 0,
flexShrink: 0,
minWidth: 150,
maxWidth: 300,
};

constructor(props: {}) {
super(props);
this.state = {
selectedView: 'participants',
};
}

_onSelectView = (view: SidebarView) => {
this.setState({ selectedView: view });
};

render() {
const { selectedView } = this.state;

return (
<div className="sidebar-view-switcher">
<SidebarViewToggle selectedView={selectedView} onSelectView={this._onSelectView} />
{selectedView === 'participants' ? (
<div className="sidebar-participants-view">
<SidebarParticipantPicker />
<FocusedContactStorePropsContainer>
<SidebarPluginContainerInner />
</FocusedContactStorePropsContainer>
</div>
) : (
<SidebarAgendaView />
)}
</div>
);
}
}
33 changes: 33 additions & 0 deletions app/internal_packages/message-list/lib/sidebar-view-toggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import { localized } from 'mailspring-exports';

export type SidebarView = 'participants' | 'calendar';

interface SidebarViewToggleProps {
selectedView: SidebarView;
onSelectView: (view: SidebarView) => void;
}

export class SidebarViewToggle extends React.Component<SidebarViewToggleProps> {
static displayName = 'SidebarViewToggle';

render() {
const { selectedView, onSelectView } = this.props;
return (
<div className="sidebar-view-toggle">
<button
className={`toggle-btn ${selectedView === 'participants' ? 'active' : ''}`}
onClick={() => onSelectView('participants')}
>
{localized('Participants')}
</button>
<button
className={`toggle-btn ${selectedView === 'calendar' ? 'active' : ''}`}
onClick={() => onSelectView('calendar')}
>
{localized('Calendar')}
</button>
</div>
);
}
}
Loading