-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathfloating-panel-ui-interaction.js
More file actions
312 lines (277 loc) · 13.1 KB
/
floating-panel-ui-interaction.js
File metadata and controls
312 lines (277 loc) · 13.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
// floating-panel-ui-interaction.js
// Version: 1.1
//
// Documentation:
// This file contains UI interaction and state management methods for the floating panel.
// It handles toggling panel visibility, moving buttons between containers,
// updating the panel appearance from settings, restoring state, and refreshing buttons.
//
// Methods included:
// - togglePanel(): Toggles the floating panel’s visibility and manages button movement.
// - updatePanelFromSettings(): Updates the panel’s appearance and position.
//
// Dependencies:
// - floating-panel.js provides the namespace (window.MaxExtensionFloatingPanel).
// - utils.js for logging via logConCgp.
//
'use strict';
/**
* Toggles the visibility of the floating panel using a "destroy and re-create" approach.
* This function is for user-driven toggling after the initial page load.
* @param {Event} [event] - The click event that triggered the toggle.
*/
window.MaxExtensionFloatingPanel.togglePanel = async function (event) {
// Suppress the resiliency watchdog while we perform this intentional DOM surgery.
window.OneClickPrompts_isTogglingPanel = true;
try {
// Ensure panel DOM structure is available, creating it if it's the first time.
await this.createFloatingPanel();
if (!this.panelElement) {
logConCgp('[floating-panel] Panel creation failed, aborting toggle.');
return;
}
this.isPanelVisible = !this.isPanelVisible;
this.currentPanelSettings.isVisible = this.isPanelVisible;
// Await the save to ensure state is persisted before re-initializing. This is critical.
await this.savePanelSettings();
if (this.isPanelVisible) {
logConCgp('[floating-panel] Toggling panel ON. Re-creating buttons in panel.');
// 1. Destroy the inline buttons container.
const originalContainer = document.getElementById(window.InjectionTargetsOnWebsite.selectors.buttonsContainerId);
if (originalContainer) {
originalContainer.remove();
}
// 2. Create buttons directly in the panel's buttons area.
const buttonsArea = document.getElementById('max-extension-buttons-area');
if (buttonsArea) {
buttonsArea.innerHTML = ''; // Ensure it's clean before creating.
window.MaxExtensionButtonsInit.createAndInsertCustomElements(buttonsArea);
}
// 3. Show and position the panel.
this.panelElement.style.display = 'flex';
// Prefer previously saved position when settings are loaded; otherwise fall back
if (this.panelSettingsLoaded) {
this.updatePanelFromSettings();
} else if (event) {
// Early user summon before settings arrive: place near cursor but do not persist
this.positionPanelAtCursor(event);
} else {
// Non-user summon: ensure we have a sane position
logConCgp('[floating-panel][fallback] Non-user summon path engaged; ensuring default position (bottom-right) if needed.');
this.updatePanelFromSettings();
}
// One-time bounds correction to avoid initial out-of-viewport spawn
if (typeof this.ensurePanelWithinViewport === 'function') {
requestAnimationFrame(() => this.ensurePanelWithinViewport());
}
} else {
logConCgp('[floating-panel] Toggling panel OFF. Re-initializing extension for inline buttons.');
// When closing panel, disable and reset the queue.
if (window.globalMaxExtensionConfig) {
window.globalMaxExtensionConfig.enableQueueMode = false;
if (typeof this.saveCurrentProfileConfig === 'function') {
this.saveCurrentProfileConfig();
}
}
if (typeof this.resetQueue === 'function') {
this.resetQueue();
}
// 1. Destroy buttons inside the panel's buttons area.
const buttonsArea = document.getElementById('max-extension-buttons-area');
if (buttonsArea) {
buttonsArea.innerHTML = '';
}
// 2. Hide the panel.
this.panelElement.style.display = 'none';
// 3. Re-run the entire, robust initialization script.
// This will correctly detect that the panel is now hidden (since we just saved that setting)
// and will proceed with the standard, resilient inline injection.
publicStaticVoidMain();
}
} catch (error) {
logConCgp('[floating-panel] CRITICAL ERROR in togglePanel try block:', error);
}
finally {
// Re-enable the watchdog after a short delay to allow the DOM to settle.
setTimeout(() => {
window.OneClickPrompts_isTogglingPanel = false;
logConCgp('[button-injection] Panel toggling complete. Resiliency check resumed.');
}, 150);
}
};
/**
* Toggles the collapsed state of the panel header and saves it.
*/
window.MaxExtensionFloatingPanel.toggleHeaderCollapse = function () {
if (!this.panelElement || !this.currentPanelSettings) return;
// Toggle the state
this.currentPanelSettings.isHeaderCollapsed = !this.currentPanelSettings.isHeaderCollapsed;
// Apply the visual change
this.applyHeaderCollapsedState(this.currentPanelSettings.isHeaderCollapsed);
// Save the new state
this.debouncedSavePanelSettings();
};
/**
* Applies the visual collapsed/expanded state to the header elements.
* This function only handles the DOM changes.
* @param {boolean} isCollapsed - The desired state.
*/
window.MaxExtensionFloatingPanel.applyHeaderCollapsedState = function (isCollapsed) {
const header = document.getElementById('max-extension-floating-panel-header');
const collapseButton = document.getElementById('max-extension-panel-collapse-btn');
const transparencyPopover = document.getElementById('max-extension-transparency-popover');
const transparencyButton = document.getElementById('max-extension-panel-transparency-btn');
if (!header || !collapseButton) return;
if (isCollapsed) {
header.classList.add('collapsed');
collapseButton.classList.add('collapsed');
collapseButton.title = 'Expand header';
// Expose a state class on the root to drive layout/stacking (CSS uses it to add top padding)
if (this.panelElement) {
this.panelElement.classList.add('has-collapsed-header'); // Allows content to reserve click-safe strip
}
// Hide transparency popover when header collapses
if (transparencyPopover) {
transparencyPopover.style.display = 'none';
}
// Hide the transparency button itself when header is collapsed
if (transparencyButton) {
transparencyButton.style.display = 'none';
}
} else {
header.classList.remove('collapsed');
collapseButton.classList.remove('collapsed');
collapseButton.title = 'Collapse header - This will collapse this header to save some window space. You can then click again to uncollapse. ';
if (this.panelElement) {
this.panelElement.classList.remove('has-collapsed-header');
}
// Show the transparency button again when header is expanded
if (transparencyButton) {
transparencyButton.style.display = '';
}
}
};
/**
* Toggles the collapsed state of the panel footer and saves it.
*/
window.MaxExtensionFloatingPanel.toggleFooterCollapse = function () {
if (!this.panelElement || !this.currentPanelSettings) return;
// Toggle the state
this.currentPanelSettings.isFooterCollapsed = !this.currentPanelSettings.isFooterCollapsed;
// Apply the visual change
this.applyFooterCollapsedState(this.currentPanelSettings.isFooterCollapsed);
// Save the new state
this.debouncedSavePanelSettings();
};
/**
* Applies the visual collapsed/expanded state to the footer elements.
* This function only handles the DOM changes.
* @param {boolean} isCollapsed - The desired state.
*/
window.MaxExtensionFloatingPanel.applyFooterCollapsedState = function (isCollapsed) {
const footer = document.getElementById('max-extension-profile-switcher');
const collapseButton = document.getElementById('max-extension-panel-collapse-footer-btn');
if (!footer || !collapseButton) return;
if (isCollapsed) {
footer.classList.add('collapsed');
collapseButton.classList.add('collapsed');
collapseButton.title = 'Expand footer';
if (this.panelElement) {
this.panelElement.classList.add('has-collapsed-footer');
}
} else {
footer.classList.remove('collapsed');
collapseButton.classList.remove('collapsed');
collapseButton.title = 'Collapse footer - This will collapse this footer to save some window space. You can then click again to uncollapse. ';
if (this.panelElement) {
this.panelElement.classList.remove('has-collapsed-footer');
}
}
if (typeof this.updateQueueTogglePlacement === 'function') {
this.updateQueueTogglePlacement();
}
};
/**
* Updates the panel's dynamic styles based on current settings.
* Static styles are now in floating-panel-files/floating-panel.css.
*/
/**
* Updates the panel’s dynamic styles based on current settings.
* If the saved position is off-screen or invalid, repositions to bottom-right.
*/
window.MaxExtensionFloatingPanel.updatePanelFromSettings = function () {
if (!this.panelElement) return;
// size
this.panelElement.style.width = `${this.currentPanelSettings.width}px`;
this.panelElement.style.height = `${this.currentPanelSettings.height}px`;
// validate position
let intendedLeft = this.currentPanelSettings.posX;
let intendedTop = this.currentPanelSettings.posY;
const panelWidth = this.currentPanelSettings.width;
const panelHeight = this.currentPanelSettings.height;
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const positionIsValid =
typeof intendedLeft === 'number' &&
typeof intendedTop === 'number' &&
intendedLeft >= 0 && intendedTop >= 0 &&
intendedLeft <= viewportWidth - panelWidth &&
intendedTop <= viewportHeight - panelHeight;
if (positionIsValid) {
this.panelElement.style.left = `${intendedLeft}px`;
this.panelElement.style.top = `${intendedTop}px`;
} else {
// Prefer TOP-right if available, else fall back to bottom-right
if (typeof this.positionPanelTopRight === 'function') {
this.positionPanelTopRight();
return;
}
if (typeof this.positionPanelBottomRight === 'function') {
this.positionPanelBottomRight();
return;
}
}
// Restore header collapsed state from settings
if (typeof this.applyHeaderCollapsedState === 'function') {
// Ensure settings has the property, fallback to default if not.
const isCollapsed = this.currentPanelSettings.isHeaderCollapsed ?? this.defaultPanelSettings.isHeaderCollapsed;
this.applyHeaderCollapsedState(isCollapsed);
}
// Restore footer collapsed state from settings
if (typeof this.applyFooterCollapsedState === 'function') {
const isCollapsed = this.currentPanelSettings.isFooterCollapsed ?? this.defaultPanelSettings.isFooterCollapsed;
this.applyFooterCollapsedState(isCollapsed);
}
// Opacity (clamped)
let bgOpacity = this.currentPanelSettings.opacity;
if (typeof bgOpacity !== 'number' || Number.isNaN(bgOpacity)) {
bgOpacity = this.defaultPanelSettings.opacity;
}
// Clamp to avoid fully invisible panel or over-opaque values
bgOpacity = Math.max(0, Math.min(1, bgOpacity));
this.panelElement.style.backgroundColor = `rgba(50, 50, 50, ${bgOpacity})`;
// Header, footer, and queue section opacity
const headerFooterOpacity = Math.min(1, bgOpacity + 0.1);
const header = document.getElementById('max-extension-floating-panel-header');
const footer = document.getElementById('max-extension-profile-switcher');
const queueSection = document.getElementById('max-extension-queue-section');
if (header) {
header.style.backgroundColor = `rgba(40, 40, 40, ${headerFooterOpacity})`;
}
if (footer) {
footer.style.backgroundColor = `rgba(40, 40, 40, ${headerFooterOpacity})`;
}
if (queueSection) {
queueSection.style.backgroundColor = `rgba(60, 60, 60, ${headerFooterOpacity})`;
}
// Keep transparency slider and label aligned with the applied opacity.
const transparencySlider = document.getElementById('max-extension-transparency-slider');
const transparencyValue = document.getElementById('max-extension-transparency-value');
const transparencyPercent = Math.min(100, Math.max(0, Math.round((1 - bgOpacity) * 100)));
if (transparencySlider && String(transparencySlider.value) !== String(transparencyPercent)) {
transparencySlider.value = transparencyPercent;
}
if (transparencyValue) {
transparencyValue.textContent = `${transparencyPercent}%`;
}
};