227 lines
6.4 KiB
TypeScript
227 lines
6.4 KiB
TypeScript
|
|
/**
|
||
|
|
* Drawer Controller
|
||
|
|
*
|
||
|
|
* Handles all drawer functionality including profile, notifications, and todo drawers
|
||
|
|
*/
|
||
|
|
|
||
|
|
export type DrawerName = 'profile' | 'notification' | 'todo' | 'newTodo';
|
||
|
|
|
||
|
|
export class DrawerController {
|
||
|
|
private profileDrawer: HTMLElement | null = null;
|
||
|
|
private notificationDrawer: HTMLElement | null = null;
|
||
|
|
private todoDrawer: HTMLElement | null = null;
|
||
|
|
private newTodoDrawer: HTMLElement | null = null;
|
||
|
|
private overlay: HTMLElement | null = null;
|
||
|
|
private activeDrawer: DrawerName | null = null;
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
this.profileDrawer = document.getElementById('profileDrawer');
|
||
|
|
this.notificationDrawer = document.getElementById('notificationDrawer');
|
||
|
|
this.todoDrawer = document.getElementById('todoDrawer');
|
||
|
|
this.newTodoDrawer = document.getElementById('newTodoDrawer');
|
||
|
|
this.overlay = document.getElementById('drawerOverlay');
|
||
|
|
|
||
|
|
this.setupListeners();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get currently active drawer name
|
||
|
|
*/
|
||
|
|
get active(): DrawerName | null {
|
||
|
|
return this.activeDrawer;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Open a drawer by name
|
||
|
|
*/
|
||
|
|
open(name: DrawerName): void {
|
||
|
|
this.closeAll();
|
||
|
|
|
||
|
|
const drawer = this.getDrawer(name);
|
||
|
|
if (drawer && this.overlay) {
|
||
|
|
drawer.classList.add('active');
|
||
|
|
this.overlay.classList.add('active');
|
||
|
|
document.body.style.overflow = 'hidden';
|
||
|
|
this.activeDrawer = name;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Close a specific drawer
|
||
|
|
*/
|
||
|
|
close(name: DrawerName): void {
|
||
|
|
const drawer = this.getDrawer(name);
|
||
|
|
drawer?.classList.remove('active');
|
||
|
|
|
||
|
|
// Only hide overlay if no drawers are active
|
||
|
|
if (this.overlay && !document.querySelector('.active[class*="drawer"]')) {
|
||
|
|
this.overlay.classList.remove('active');
|
||
|
|
document.body.style.overflow = '';
|
||
|
|
}
|
||
|
|
|
||
|
|
if (this.activeDrawer === name) {
|
||
|
|
this.activeDrawer = null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Close all drawers
|
||
|
|
*/
|
||
|
|
closeAll(): void {
|
||
|
|
[this.profileDrawer, this.notificationDrawer, this.todoDrawer, this.newTodoDrawer]
|
||
|
|
.forEach(drawer => drawer?.classList.remove('active'));
|
||
|
|
|
||
|
|
this.overlay?.classList.remove('active');
|
||
|
|
document.body.style.overflow = '';
|
||
|
|
this.activeDrawer = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Open profile drawer
|
||
|
|
*/
|
||
|
|
openProfile(): void {
|
||
|
|
this.open('profile');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Open notification drawer
|
||
|
|
*/
|
||
|
|
openNotification(): void {
|
||
|
|
this.open('notification');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Open todo drawer (slides on top of profile)
|
||
|
|
*/
|
||
|
|
openTodo(): void {
|
||
|
|
this.todoDrawer?.classList.add('active');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Close todo drawer
|
||
|
|
*/
|
||
|
|
closeTodo(): void {
|
||
|
|
this.todoDrawer?.classList.remove('active');
|
||
|
|
this.closeNewTodo();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Open new todo drawer
|
||
|
|
*/
|
||
|
|
openNewTodo(): void {
|
||
|
|
this.newTodoDrawer?.classList.add('active');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Close new todo drawer
|
||
|
|
*/
|
||
|
|
closeNewTodo(): void {
|
||
|
|
this.newTodoDrawer?.classList.remove('active');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Mark all notifications as read
|
||
|
|
*/
|
||
|
|
markAllNotificationsRead(): void {
|
||
|
|
if (!this.notificationDrawer) return;
|
||
|
|
|
||
|
|
const unreadItems = this.notificationDrawer.querySelectorAll<HTMLElement>(
|
||
|
|
'swp-notification-item[data-unread="true"]'
|
||
|
|
);
|
||
|
|
unreadItems.forEach(item => item.removeAttribute('data-unread'));
|
||
|
|
|
||
|
|
const badge = document.querySelector<HTMLElement>('swp-notification-badge');
|
||
|
|
if (badge) {
|
||
|
|
badge.style.display = 'none';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private getDrawer(name: DrawerName): HTMLElement | null {
|
||
|
|
switch (name) {
|
||
|
|
case 'profile': return this.profileDrawer;
|
||
|
|
case 'notification': return this.notificationDrawer;
|
||
|
|
case 'todo': return this.todoDrawer;
|
||
|
|
case 'newTodo': return this.newTodoDrawer;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private setupListeners(): void {
|
||
|
|
// Profile drawer triggers
|
||
|
|
document.getElementById('profileTrigger')
|
||
|
|
?.addEventListener('click', () => this.openProfile());
|
||
|
|
document.getElementById('drawerClose')
|
||
|
|
?.addEventListener('click', () => this.close('profile'));
|
||
|
|
|
||
|
|
// Notification drawer triggers
|
||
|
|
document.getElementById('notificationsBtn')
|
||
|
|
?.addEventListener('click', () => this.openNotification());
|
||
|
|
document.getElementById('notificationDrawerClose')
|
||
|
|
?.addEventListener('click', () => this.close('notification'));
|
||
|
|
document.getElementById('markAllRead')
|
||
|
|
?.addEventListener('click', () => this.markAllNotificationsRead());
|
||
|
|
|
||
|
|
// Todo drawer triggers
|
||
|
|
document.getElementById('openTodoDrawer')
|
||
|
|
?.addEventListener('click', () => this.openTodo());
|
||
|
|
document.getElementById('todoDrawerBack')
|
||
|
|
?.addEventListener('click', () => this.closeTodo());
|
||
|
|
|
||
|
|
// New todo drawer triggers
|
||
|
|
document.getElementById('addTodoBtn')
|
||
|
|
?.addEventListener('click', () => this.openNewTodo());
|
||
|
|
document.getElementById('newTodoDrawerBack')
|
||
|
|
?.addEventListener('click', () => this.closeNewTodo());
|
||
|
|
document.getElementById('cancelNewTodo')
|
||
|
|
?.addEventListener('click', () => this.closeNewTodo());
|
||
|
|
document.getElementById('saveNewTodo')
|
||
|
|
?.addEventListener('click', () => this.closeNewTodo());
|
||
|
|
|
||
|
|
// Overlay click closes all
|
||
|
|
this.overlay?.addEventListener('click', () => this.closeAll());
|
||
|
|
|
||
|
|
// Escape key closes all
|
||
|
|
document.addEventListener('keydown', (e: KeyboardEvent) => {
|
||
|
|
if (e.key === 'Escape') this.closeAll();
|
||
|
|
});
|
||
|
|
|
||
|
|
// Todo interactions
|
||
|
|
this.todoDrawer?.addEventListener('click', (e) => this.handleTodoClick(e));
|
||
|
|
|
||
|
|
// Visibility options
|
||
|
|
document.addEventListener('click', (e) => this.handleVisibilityClick(e));
|
||
|
|
}
|
||
|
|
|
||
|
|
private handleTodoClick(e: Event): void {
|
||
|
|
const target = e.target as HTMLElement;
|
||
|
|
const todoItem = target.closest<HTMLElement>('swp-todo-item');
|
||
|
|
const checkbox = target.closest<HTMLElement>('swp-todo-checkbox');
|
||
|
|
|
||
|
|
if (checkbox && todoItem) {
|
||
|
|
const isCompleted = todoItem.dataset.completed === 'true';
|
||
|
|
if (isCompleted) {
|
||
|
|
todoItem.removeAttribute('data-completed');
|
||
|
|
} else {
|
||
|
|
todoItem.dataset.completed = 'true';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Toggle section collapse
|
||
|
|
const sectionHeader = target.closest<HTMLElement>('swp-todo-section-header');
|
||
|
|
if (sectionHeader) {
|
||
|
|
const section = sectionHeader.closest<HTMLElement>('swp-todo-section');
|
||
|
|
section?.classList.toggle('collapsed');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private handleVisibilityClick(e: Event): void {
|
||
|
|
const target = e.target as HTMLElement;
|
||
|
|
const option = target.closest<HTMLElement>('swp-visibility-option');
|
||
|
|
|
||
|
|
if (option) {
|
||
|
|
document.querySelectorAll<HTMLElement>('swp-visibility-option')
|
||
|
|
.forEach(o => o.classList.remove('active'));
|
||
|
|
option.classList.add('active');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|