@monygroupcorp/micro-web3 1.2.4 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  // src/components/SettingsModal/SettingsModal.js
2
2
 
3
+ import { Component, h, render } from '@monygroupcorp/microact';
3
4
  import { IndexerSettings } from '../../storage/index.js';
4
5
 
5
6
  /**
@@ -9,363 +10,385 @@ import { IndexerSettings } from '../../storage/index.js';
9
10
  * const modal = new SettingsModal({ eventBus });
10
11
  * modal.show();
11
12
  */
12
- class SettingsModal {
13
- constructor({ eventBus }) {
14
- this.eventBus = eventBus;
15
- this.element = null;
16
- this.overlay = null;
17
- this.isVisible = false;
18
- this.state = {
19
- storageEnabled: true,
20
- storageUsed: 0,
21
- storageQuota: 0,
22
- clearing: false
23
- };
24
- }
25
-
26
- async show() {
27
- if (this.isVisible) return;
28
-
29
- // Load current settings
30
- const settings = IndexerSettings.get();
31
- const estimate = await IndexerSettings.getStorageEstimate();
32
-
33
- this.state = {
34
- storageEnabled: settings.storageEnabled,
35
- storageUsed: estimate.used || 0,
36
- storageQuota: estimate.quota || 0,
37
- clearing: false
38
- };
39
-
40
- this._createModal();
41
- this.isVisible = true;
42
- }
43
-
44
- hide() {
45
- if (!this.isVisible) return;
46
-
47
- if (this.overlay && this.overlay.parentNode) {
48
- this.overlay.parentNode.removeChild(this.overlay);
13
+ class SettingsModal extends Component {
14
+ constructor(props) {
15
+ super(props);
16
+ this.eventBus = props.eventBus;
17
+ this.overlay = null;
18
+ this.state = {
19
+ isVisible: false,
20
+ storageEnabled: true,
21
+ storageUsed: 0,
22
+ storageQuota: 0,
23
+ clearing: false
24
+ };
25
+ }
26
+
27
+ async show() {
28
+ if (this.state.isVisible) return;
29
+
30
+ // Load current settings
31
+ const settings = IndexerSettings.get();
32
+ const estimate = await IndexerSettings.getStorageEstimate();
33
+
34
+ this.setState({
35
+ isVisible: true,
36
+ storageEnabled: settings.storageEnabled,
37
+ storageUsed: estimate.used || 0,
38
+ storageQuota: estimate.quota || 0,
39
+ clearing: false
40
+ });
41
+
42
+ this._createModal();
43
+ }
44
+
45
+ hide() {
46
+ if (!this.state.isVisible) return;
47
+
48
+ if (this.overlay && this.overlay.parentNode) {
49
+ this.overlay.parentNode.removeChild(this.overlay);
50
+ }
51
+ this.overlay = null;
52
+
53
+ // Remove ESC handler
54
+ if (this._escHandler) {
55
+ document.removeEventListener('keydown', this._escHandler);
56
+ this._escHandler = null;
57
+ }
58
+
59
+ this.setState({ isVisible: false });
60
+ }
61
+
62
+ _createModal() {
63
+ // Inject styles if needed
64
+ SettingsModal.injectStyles();
65
+
66
+ // Create overlay
67
+ this.overlay = document.createElement('div');
68
+ this.overlay.className = 'mw3-settings-overlay';
69
+ this.overlay.addEventListener('click', (e) => {
70
+ if (e.target === this.overlay) this.hide();
71
+ });
72
+
73
+ // Create modal container
74
+ const modalContainer = document.createElement('div');
75
+ modalContainer.className = 'mw3-settings-modal';
76
+ this.overlay.appendChild(modalContainer);
77
+ document.body.appendChild(this.overlay);
78
+
79
+ // Render the modal content
80
+ render(h(SettingsModalContent, {
81
+ ...this.state,
82
+ eventBus: this.eventBus,
83
+ onClose: () => this.hide(),
84
+ onStorageToggle: (enabled) => this._handleStorageToggle(enabled),
85
+ onClearData: () => this._handleClearData()
86
+ }), modalContainer);
87
+
88
+ // ESC key to close
89
+ this._escHandler = (e) => {
90
+ if (e.key === 'Escape') this.hide();
91
+ };
92
+ document.addEventListener('keydown', this._escHandler);
49
93
  }
50
- this.overlay = null;
51
- this.element = null;
52
- this.isVisible = false;
53
- }
54
-
55
- _createModal() {
56
- // Inject styles if needed
57
- SettingsModal.injectStyles();
58
-
59
- // Create overlay
60
- this.overlay = document.createElement('div');
61
- this.overlay.className = 'mw3-settings-overlay';
62
- this.overlay.addEventListener('click', (e) => {
63
- if (e.target === this.overlay) this.hide();
64
- });
65
-
66
- // Create modal
67
- this.element = document.createElement('div');
68
- this.element.className = 'mw3-settings-modal';
69
- this.element.innerHTML = this._render();
70
-
71
- this.overlay.appendChild(this.element);
72
- document.body.appendChild(this.overlay);
73
-
74
- this._setupEventListeners();
75
- }
76
-
77
- _render() {
78
- const { storageEnabled, storageUsed, storageQuota, clearing } = this.state;
79
- const usedMB = (storageUsed / (1024 * 1024)).toFixed(1);
80
- const quotaMB = (storageQuota / (1024 * 1024)).toFixed(0);
81
-
82
- return `
83
- <div class="mw3-settings-header">
84
- <h2>Settings</h2>
85
- <button class="mw3-settings-close" data-action="close">&times;</button>
86
- </div>
87
-
88
- <div class="mw3-settings-content">
89
- <div class="mw3-settings-section">
90
- <h3>Data Storage</h3>
91
- <p class="mw3-settings-description">
92
- Event data is cached locally to improve performance. You can disable this to save space.
93
- </p>
94
-
95
- <div class="mw3-settings-row">
96
- <label class="mw3-settings-toggle">
97
- <input type="checkbox" ${storageEnabled ? 'checked' : ''} data-setting="storageEnabled">
98
- <span class="mw3-toggle-slider"></span>
99
- <span class="mw3-toggle-label">Enable local event storage</span>
100
- </label>
101
- </div>
102
-
103
- <div class="mw3-settings-info">
104
- <span>Storage used: ${usedMB} MB${quotaMB > 0 ? ` / ${quotaMB} MB` : ''}</span>
105
- </div>
106
-
107
- <div class="mw3-settings-row">
108
- <button class="mw3-settings-btn mw3-settings-btn--danger" data-action="clearData" ${clearing ? 'disabled' : ''}>
109
- ${clearing ? 'Clearing...' : 'Clear All Cached Data'}
110
- </button>
111
- </div>
112
-
113
- <p class="mw3-settings-note">
114
- Disabling storage means event history will need to be re-fetched each session.
115
- </p>
116
- </div>
117
- </div>
118
-
119
- <div class="mw3-settings-footer">
120
- <button class="mw3-settings-btn mw3-settings-btn--primary" data-action="close">Done</button>
121
- </div>
122
- `;
123
- }
124
-
125
- _setupEventListeners() {
126
- // Close button
127
- this.element.querySelectorAll('[data-action="close"]').forEach(btn => {
128
- btn.addEventListener('click', () => this.hide());
129
- });
130
-
131
- // Storage toggle
132
- const toggle = this.element.querySelector('[data-setting="storageEnabled"]');
133
- if (toggle) {
134
- toggle.addEventListener('change', (e) => {
135
- const enabled = e.target.checked;
136
- IndexerSettings.set({ storageEnabled: enabled });
137
- this.state.storageEnabled = enabled;
138
94
 
139
- // Emit event so indexer can react
95
+ _handleStorageToggle(enabled) {
96
+ IndexerSettings.set({ storageEnabled: enabled });
97
+ this.setState({ storageEnabled: enabled });
140
98
  this.eventBus.emit('settings:storageChanged', { enabled });
141
- });
99
+ this._updateModalContent();
142
100
  }
143
101
 
144
- // Clear data button
145
- const clearBtn = this.element.querySelector('[data-action="clearData"]');
146
- if (clearBtn) {
147
- clearBtn.addEventListener('click', async () => {
102
+ async _handleClearData() {
148
103
  if (this.state.clearing) return;
149
104
 
150
- this.state.clearing = true;
151
- this._updateContent();
105
+ this.setState({ clearing: true });
106
+ this._updateModalContent();
152
107
 
153
108
  try {
154
- await IndexerSettings.clearAllData();
109
+ await IndexerSettings.clearAllData();
110
+
111
+ // Update storage estimate
112
+ const estimate = await IndexerSettings.getStorageEstimate();
113
+ this.setState({
114
+ storageUsed: estimate.used || 0,
115
+ storageQuota: estimate.quota || 0,
116
+ clearing: false
117
+ });
118
+
119
+ // Emit event so indexer knows to resync
120
+ this.eventBus.emit('settings:dataCleared', {});
121
+ } catch (error) {
122
+ console.error('[SettingsModal] Failed to clear data:', error);
123
+ this.setState({ clearing: false });
124
+ }
155
125
 
156
- // Update storage estimate
157
- const estimate = await IndexerSettings.getStorageEstimate();
158
- this.state.storageUsed = estimate.used || 0;
159
- this.state.storageQuota = estimate.quota || 0;
126
+ this._updateModalContent();
127
+ }
160
128
 
161
- // Emit event so indexer knows to resync
162
- this.eventBus.emit('settings:dataCleared', {});
163
- } catch (error) {
164
- console.error('[SettingsModal] Failed to clear data:', error);
129
+ _updateModalContent() {
130
+ if (this.overlay) {
131
+ const modalContainer = this.overlay.querySelector('.mw3-settings-modal');
132
+ if (modalContainer) {
133
+ render(h(SettingsModalContent, {
134
+ ...this.state,
135
+ eventBus: this.eventBus,
136
+ onClose: () => this.hide(),
137
+ onStorageToggle: (enabled) => this._handleStorageToggle(enabled),
138
+ onClearData: () => this._handleClearData()
139
+ }), modalContainer);
140
+ }
165
141
  }
142
+ }
166
143
 
167
- this.state.clearing = false;
168
- this._updateContent();
169
- });
144
+ static injectStyles() {
145
+ if (document.getElementById('mw3-settings-styles')) return;
146
+
147
+ const style = document.createElement('style');
148
+ style.id = 'mw3-settings-styles';
149
+ style.textContent = `
150
+ .mw3-settings-overlay {
151
+ position: fixed;
152
+ top: 0;
153
+ left: 0;
154
+ right: 0;
155
+ bottom: 0;
156
+ background: rgba(0, 0, 0, 0.7);
157
+ display: flex;
158
+ align-items: center;
159
+ justify-content: center;
160
+ z-index: 10000;
161
+ backdrop-filter: blur(4px);
162
+ }
163
+
164
+ .mw3-settings-modal {
165
+ background: linear-gradient(135deg, #1a1a2e, #16213e);
166
+ border: 1px solid rgba(218, 165, 32, 0.3);
167
+ border-radius: 12px;
168
+ width: 90%;
169
+ max-width: 400px;
170
+ color: #fff;
171
+ font-family: 'Lato', system-ui, sans-serif;
172
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
173
+ }
174
+
175
+ .mw3-settings-header {
176
+ display: flex;
177
+ align-items: center;
178
+ justify-content: space-between;
179
+ padding: 1rem 1.25rem;
180
+ border-bottom: 1px solid rgba(218, 165, 32, 0.2);
181
+ }
182
+
183
+ .mw3-settings-header h2 {
184
+ margin: 0;
185
+ font-size: 1.125rem;
186
+ font-weight: 600;
187
+ color: rgba(218, 165, 32, 0.95);
188
+ }
189
+
190
+ .mw3-settings-close {
191
+ background: none;
192
+ border: none;
193
+ color: rgba(255, 255, 255, 0.6);
194
+ font-size: 1.5rem;
195
+ cursor: pointer;
196
+ padding: 0;
197
+ line-height: 1;
198
+ }
199
+
200
+ .mw3-settings-close:hover {
201
+ color: #fff;
202
+ }
203
+
204
+ .mw3-settings-content {
205
+ padding: 1.25rem;
206
+ }
207
+
208
+ .mw3-settings-section h3 {
209
+ margin: 0 0 0.5rem 0;
210
+ font-size: 0.9rem;
211
+ font-weight: 600;
212
+ color: rgba(255, 255, 255, 0.9);
213
+ }
214
+
215
+ .mw3-settings-description {
216
+ margin: 0 0 1rem 0;
217
+ font-size: 0.8rem;
218
+ color: rgba(255, 255, 255, 0.6);
219
+ line-height: 1.4;
220
+ }
221
+
222
+ .mw3-settings-row {
223
+ margin-bottom: 1rem;
224
+ }
225
+
226
+ .mw3-settings-toggle {
227
+ display: flex;
228
+ align-items: center;
229
+ gap: 0.75rem;
230
+ cursor: pointer;
231
+ }
232
+
233
+ .mw3-settings-toggle input {
234
+ display: none;
235
+ }
236
+
237
+ .mw3-toggle-slider {
238
+ width: 44px;
239
+ height: 24px;
240
+ background: rgba(255, 255, 255, 0.2);
241
+ border-radius: 12px;
242
+ position: relative;
243
+ transition: background 0.2s;
244
+ }
245
+
246
+ .mw3-toggle-slider::after {
247
+ content: '';
248
+ position: absolute;
249
+ top: 2px;
250
+ left: 2px;
251
+ width: 20px;
252
+ height: 20px;
253
+ background: #fff;
254
+ border-radius: 50%;
255
+ transition: transform 0.2s;
256
+ }
257
+
258
+ .mw3-settings-toggle input:checked + .mw3-toggle-slider {
259
+ background: rgba(218, 165, 32, 0.8);
260
+ }
261
+
262
+ .mw3-settings-toggle input:checked + .mw3-toggle-slider::after {
263
+ transform: translateX(20px);
264
+ }
265
+
266
+ .mw3-toggle-label {
267
+ font-size: 0.875rem;
268
+ color: rgba(255, 255, 255, 0.9);
269
+ }
270
+
271
+ .mw3-settings-info {
272
+ font-size: 0.75rem;
273
+ color: rgba(255, 255, 255, 0.5);
274
+ margin-bottom: 1rem;
275
+ }
276
+
277
+ .mw3-settings-note {
278
+ font-size: 0.75rem;
279
+ color: rgba(255, 255, 255, 0.4);
280
+ margin: 0;
281
+ font-style: italic;
282
+ }
283
+
284
+ .mw3-settings-btn {
285
+ padding: 0.625rem 1rem;
286
+ border-radius: 6px;
287
+ font-size: 0.875rem;
288
+ font-weight: 500;
289
+ cursor: pointer;
290
+ border: none;
291
+ transition: all 0.2s;
292
+ }
293
+
294
+ .mw3-settings-btn--primary {
295
+ background: rgba(218, 165, 32, 0.9);
296
+ color: #1a1a2e;
297
+ }
298
+
299
+ .mw3-settings-btn--primary:hover {
300
+ background: rgba(218, 165, 32, 1);
301
+ }
302
+
303
+ .mw3-settings-btn--danger {
304
+ background: rgba(255, 99, 71, 0.2);
305
+ color: rgba(255, 99, 71, 0.9);
306
+ border: 1px solid rgba(255, 99, 71, 0.3);
307
+ }
308
+
309
+ .mw3-settings-btn--danger:hover {
310
+ background: rgba(255, 99, 71, 0.3);
311
+ }
312
+
313
+ .mw3-settings-btn:disabled {
314
+ opacity: 0.5;
315
+ cursor: not-allowed;
316
+ }
317
+
318
+ .mw3-settings-footer {
319
+ padding: 1rem 1.25rem;
320
+ border-top: 1px solid rgba(218, 165, 32, 0.2);
321
+ display: flex;
322
+ justify-content: flex-end;
323
+ }
324
+ `;
325
+ document.head.appendChild(style);
170
326
  }
327
+ }
171
328
 
172
- // ESC key to close
173
- this._escHandler = (e) => {
174
- if (e.key === 'Escape') this.hide();
175
- };
176
- document.addEventListener('keydown', this._escHandler);
177
- }
178
-
179
- _updateContent() {
180
- if (this.element) {
181
- this.element.innerHTML = this._render();
182
- this._setupEventListeners();
329
+ /**
330
+ * Inner component for the settings modal content
331
+ */
332
+ class SettingsModalContent extends Component {
333
+ render() {
334
+ const { storageEnabled, storageUsed, storageQuota, clearing, onClose, onStorageToggle, onClearData } = this.props;
335
+ const usedMB = (storageUsed / (1024 * 1024)).toFixed(1);
336
+ const quotaMB = (storageQuota / (1024 * 1024)).toFixed(0);
337
+
338
+ return h('div', null,
339
+ h('div', { className: 'mw3-settings-header' },
340
+ h('h2', null, 'Settings'),
341
+ h('button', {
342
+ className: 'mw3-settings-close',
343
+ onClick: onClose
344
+ }, '\u00D7')
345
+ ),
346
+
347
+ h('div', { className: 'mw3-settings-content' },
348
+ h('div', { className: 'mw3-settings-section' },
349
+ h('h3', null, 'Data Storage'),
350
+ h('p', { className: 'mw3-settings-description' },
351
+ 'Event data is cached locally to improve performance. You can disable this to save space.'
352
+ ),
353
+
354
+ h('div', { className: 'mw3-settings-row' },
355
+ h('label', { className: 'mw3-settings-toggle' },
356
+ h('input', {
357
+ type: 'checkbox',
358
+ checked: storageEnabled,
359
+ onChange: (e) => onStorageToggle(e.target.checked)
360
+ }),
361
+ h('span', { className: 'mw3-toggle-slider' }),
362
+ h('span', { className: 'mw3-toggle-label' }, 'Enable local event storage')
363
+ )
364
+ ),
365
+
366
+ h('div', { className: 'mw3-settings-info' },
367
+ h('span', null, `Storage used: ${usedMB} MB${quotaMB > 0 ? ` / ${quotaMB} MB` : ''}`)
368
+ ),
369
+
370
+ h('div', { className: 'mw3-settings-row' },
371
+ h('button', {
372
+ className: 'mw3-settings-btn mw3-settings-btn--danger',
373
+ disabled: clearing,
374
+ onClick: onClearData
375
+ }, clearing ? 'Clearing...' : 'Clear All Cached Data')
376
+ ),
377
+
378
+ h('p', { className: 'mw3-settings-note' },
379
+ 'Disabling storage means event history will need to be re-fetched each session.'
380
+ )
381
+ )
382
+ ),
383
+
384
+ h('div', { className: 'mw3-settings-footer' },
385
+ h('button', {
386
+ className: 'mw3-settings-btn mw3-settings-btn--primary',
387
+ onClick: onClose
388
+ }, 'Done')
389
+ )
390
+ );
183
391
  }
184
- }
185
-
186
- static injectStyles() {
187
- if (document.getElementById('mw3-settings-styles')) return;
188
-
189
- const style = document.createElement('style');
190
- style.id = 'mw3-settings-styles';
191
- style.textContent = `
192
- .mw3-settings-overlay {
193
- position: fixed;
194
- top: 0;
195
- left: 0;
196
- right: 0;
197
- bottom: 0;
198
- background: rgba(0, 0, 0, 0.7);
199
- display: flex;
200
- align-items: center;
201
- justify-content: center;
202
- z-index: 10000;
203
- backdrop-filter: blur(4px);
204
- }
205
-
206
- .mw3-settings-modal {
207
- background: linear-gradient(135deg, #1a1a2e, #16213e);
208
- border: 1px solid rgba(218, 165, 32, 0.3);
209
- border-radius: 12px;
210
- width: 90%;
211
- max-width: 400px;
212
- color: #fff;
213
- font-family: 'Lato', system-ui, sans-serif;
214
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
215
- }
216
-
217
- .mw3-settings-header {
218
- display: flex;
219
- align-items: center;
220
- justify-content: space-between;
221
- padding: 1rem 1.25rem;
222
- border-bottom: 1px solid rgba(218, 165, 32, 0.2);
223
- }
224
-
225
- .mw3-settings-header h2 {
226
- margin: 0;
227
- font-size: 1.125rem;
228
- font-weight: 600;
229
- color: rgba(218, 165, 32, 0.95);
230
- }
231
-
232
- .mw3-settings-close {
233
- background: none;
234
- border: none;
235
- color: rgba(255, 255, 255, 0.6);
236
- font-size: 1.5rem;
237
- cursor: pointer;
238
- padding: 0;
239
- line-height: 1;
240
- }
241
-
242
- .mw3-settings-close:hover {
243
- color: #fff;
244
- }
245
-
246
- .mw3-settings-content {
247
- padding: 1.25rem;
248
- }
249
-
250
- .mw3-settings-section h3 {
251
- margin: 0 0 0.5rem 0;
252
- font-size: 0.9rem;
253
- font-weight: 600;
254
- color: rgba(255, 255, 255, 0.9);
255
- }
256
-
257
- .mw3-settings-description {
258
- margin: 0 0 1rem 0;
259
- font-size: 0.8rem;
260
- color: rgba(255, 255, 255, 0.6);
261
- line-height: 1.4;
262
- }
263
-
264
- .mw3-settings-row {
265
- margin-bottom: 1rem;
266
- }
267
-
268
- .mw3-settings-toggle {
269
- display: flex;
270
- align-items: center;
271
- gap: 0.75rem;
272
- cursor: pointer;
273
- }
274
-
275
- .mw3-settings-toggle input {
276
- display: none;
277
- }
278
-
279
- .mw3-toggle-slider {
280
- width: 44px;
281
- height: 24px;
282
- background: rgba(255, 255, 255, 0.2);
283
- border-radius: 12px;
284
- position: relative;
285
- transition: background 0.2s;
286
- }
287
-
288
- .mw3-toggle-slider::after {
289
- content: '';
290
- position: absolute;
291
- top: 2px;
292
- left: 2px;
293
- width: 20px;
294
- height: 20px;
295
- background: #fff;
296
- border-radius: 50%;
297
- transition: transform 0.2s;
298
- }
299
-
300
- .mw3-settings-toggle input:checked + .mw3-toggle-slider {
301
- background: rgba(218, 165, 32, 0.8);
302
- }
303
-
304
- .mw3-settings-toggle input:checked + .mw3-toggle-slider::after {
305
- transform: translateX(20px);
306
- }
307
-
308
- .mw3-toggle-label {
309
- font-size: 0.875rem;
310
- color: rgba(255, 255, 255, 0.9);
311
- }
312
-
313
- .mw3-settings-info {
314
- font-size: 0.75rem;
315
- color: rgba(255, 255, 255, 0.5);
316
- margin-bottom: 1rem;
317
- }
318
-
319
- .mw3-settings-note {
320
- font-size: 0.75rem;
321
- color: rgba(255, 255, 255, 0.4);
322
- margin: 0;
323
- font-style: italic;
324
- }
325
-
326
- .mw3-settings-btn {
327
- padding: 0.625rem 1rem;
328
- border-radius: 6px;
329
- font-size: 0.875rem;
330
- font-weight: 500;
331
- cursor: pointer;
332
- border: none;
333
- transition: all 0.2s;
334
- }
335
-
336
- .mw3-settings-btn--primary {
337
- background: rgba(218, 165, 32, 0.9);
338
- color: #1a1a2e;
339
- }
340
-
341
- .mw3-settings-btn--primary:hover {
342
- background: rgba(218, 165, 32, 1);
343
- }
344
-
345
- .mw3-settings-btn--danger {
346
- background: rgba(255, 99, 71, 0.2);
347
- color: rgba(255, 99, 71, 0.9);
348
- border: 1px solid rgba(255, 99, 71, 0.3);
349
- }
350
-
351
- .mw3-settings-btn--danger:hover {
352
- background: rgba(255, 99, 71, 0.3);
353
- }
354
-
355
- .mw3-settings-btn:disabled {
356
- opacity: 0.5;
357
- cursor: not-allowed;
358
- }
359
-
360
- .mw3-settings-footer {
361
- padding: 1rem 1.25rem;
362
- border-top: 1px solid rgba(218, 165, 32, 0.2);
363
- display: flex;
364
- justify-content: flex-end;
365
- }
366
- `;
367
- document.head.appendChild(style);
368
- }
369
392
  }
370
393
 
371
394
  export { SettingsModal };