Skip to main content

max / goingson

4.2 KB · 159 lines History Blame Raw
1 /**
2 * GoingsOn - Application State
3 * Centralized state management with pub/sub for reactivity
4 */
5
6 (function() {
7 'use strict';
8
9 // ============ State Class ============
10
11 class AppStateManager {
12 constructor() {
13 // Subscribers for state changes
14 this._subscribers = {};
15
16 // ============ Data State (single source of truth) ============
17
18 // Domain data
19 this.projects = [];
20 this.tasks = [];
21 this.emails = [];
22 this.emailAccounts = [];
23
24 // ============ UI State ============
25
26 // Current view/selection
27 this.currentView = 'projects';
28 this.currentProjectId = null;
29
30 // Day planning
31 this.dayPlanDate = new Date();
32 this.dayPlanData = null;
33
34 // Drag and drop
35 this.draggedTaskId = null;
36
37 // Timeline
38 this.currentTimeIndicatorInterval = null;
39 this.paintingState = null; // { startSlot, startTime, endSlot, endTime, preview }
40
41 // Keyboard navigation
42 this.pendingKey = null;
43 this.pendingKeyTimeout = null;
44 this.selectedItemIndex = -1;
45
46 // Bulk selection (keeping as Sets for performance)
47 this.selectedTaskIds = new Set();
48 this.selectedEmailIds = new Set();
49
50 // Pagination
51 this.taskPage = 1;
52 this.emailPage = 1;
53 this.itemsPerPage = 50;
54
55 // View switch tracking (in-memory only, reset on restart)
56 this.viewSwitchCounts = {};
57 this.lastSubviewByTab = {};
58 this.sessionStart = Date.now();
59 }
60
61 /**
62 * Set a state property and notify subscribers
63 * @param {string} key - Property name
64 * @param {*} value - New value
65 */
66 set(key, value) {
67 const oldValue = this[key];
68 this[key] = value;
69 this.notify(key, value, oldValue);
70 }
71
72 /**
73 * Subscribe to changes on a state property
74 * @param {string} key - Property name to watch
75 * @param {Function} callback - Called with (newValue, oldValue)
76 * @returns {Function} Unsubscribe function
77 */
78 subscribe(key, callback) {
79 if (!this._subscribers[key]) {
80 this._subscribers[key] = [];
81 }
82 this._subscribers[key].push(callback);
83
84 // Return unsubscribe function
85 return () => {
86 const idx = this._subscribers[key].indexOf(callback);
87 if (idx > -1) {
88 this._subscribers[key].splice(idx, 1);
89 }
90 };
91 }
92
93 /**
94 * Notify all subscribers of a state change
95 * @param {string} key - Property that changed
96 * @param {*} newValue - New value
97 * @param {*} oldValue - Previous value
98 */
99 notify(key, newValue, oldValue) {
100 const callbacks = this._subscribers[key];
101 if (callbacks) {
102 for (const cb of callbacks) {
103 try {
104 cb(newValue, oldValue);
105 } catch (err) {
106 console.error(`State subscriber error for "${key}":`, err);
107 }
108 }
109 }
110 }
111
112 /**
113 * Batch update multiple properties
114 * @param {Object} updates - Object with key-value pairs to update
115 */
116 update(updates) {
117 for (const [key, value] of Object.entries(updates)) {
118 this.set(key, value);
119 }
120 }
121
122 /**
123 * Reset pagination for a domain
124 * @param {string} domain - 'task' or 'email'
125 */
126 resetPagination(domain) {
127 if (domain === 'task') {
128 this.set('taskPage', 1);
129 } else if (domain === 'email') {
130 this.set('emailPage', 1);
131 }
132 }
133
134 /**
135 * Clear selection for a domain
136 * @param {string} domain - 'task' or 'email'
137 */
138 clearSelection(domain) {
139 if (domain === 'task') {
140 this.selectedTaskIds.clear();
141 this.notify('selectedTaskIds', this.selectedTaskIds, null);
142 } else if (domain === 'email') {
143 this.selectedEmailIds.clear();
144 this.notify('selectedEmailIds', this.selectedEmailIds, null);
145 }
146 }
147 }
148
149 // ============ Create Global Instance ============
150
151 const AppState = new AppStateManager();
152
153 // ============ Populate GoingsOn.state ============
154
155 GoingsOn.state = AppState;
156
157 })();
158
159