figrecipe 0.7.4__py3-none-any.whl → 0.9.0__py3-none-any.whl
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.
- figrecipe/__init__.py +74 -76
- figrecipe/__main__.py +12 -0
- figrecipe/_api/_panel.py +67 -0
- figrecipe/_api/_save.py +100 -4
- figrecipe/_cli/__init__.py +7 -0
- figrecipe/_cli/_compose.py +87 -0
- figrecipe/_cli/_convert.py +117 -0
- figrecipe/_cli/_crop.py +82 -0
- figrecipe/_cli/_edit.py +70 -0
- figrecipe/_cli/_extract.py +128 -0
- figrecipe/_cli/_fonts.py +47 -0
- figrecipe/_cli/_info.py +67 -0
- figrecipe/_cli/_main.py +58 -0
- figrecipe/_cli/_reproduce.py +79 -0
- figrecipe/_cli/_style.py +77 -0
- figrecipe/_cli/_validate.py +66 -0
- figrecipe/_cli/_version.py +50 -0
- figrecipe/_composition/__init__.py +32 -0
- figrecipe/_composition/_alignment.py +452 -0
- figrecipe/_composition/_compose.py +179 -0
- figrecipe/_composition/_import_axes.py +127 -0
- figrecipe/_composition/_visibility.py +125 -0
- figrecipe/_dev/__init__.py +2 -0
- figrecipe/_dev/browser/__init__.py +69 -0
- figrecipe/_dev/browser/_audio.py +240 -0
- figrecipe/_dev/browser/_caption.py +356 -0
- figrecipe/_dev/browser/_click_effect.py +146 -0
- figrecipe/_dev/browser/_cursor.py +196 -0
- figrecipe/_dev/browser/_highlight.py +105 -0
- figrecipe/_dev/browser/_narration.py +237 -0
- figrecipe/_dev/browser/_recorder.py +446 -0
- figrecipe/_dev/browser/_utils.py +178 -0
- figrecipe/_dev/browser/_video_trim/__init__.py +152 -0
- figrecipe/_dev/browser/_video_trim/_detection.py +223 -0
- figrecipe/_dev/browser/_video_trim/_markers.py +140 -0
- figrecipe/_editor/__init__.py +36 -36
- figrecipe/_editor/_bbox/_extract.py +155 -9
- figrecipe/_editor/_bbox/_extract_text.py +124 -0
- figrecipe/_editor/_call_overrides.py +183 -0
- figrecipe/_editor/_datatable_plot_handlers.py +249 -0
- figrecipe/_editor/_figure_layout.py +211 -0
- figrecipe/_editor/_flask_app.py +157 -16
- figrecipe/_editor/_helpers.py +17 -8
- figrecipe/_editor/_hitmap/_detect.py +89 -32
- figrecipe/_editor/_hitmap_main.py +4 -4
- figrecipe/_editor/_overrides.py +4 -1
- figrecipe/_editor/_plot_types_registry.py +190 -0
- figrecipe/_editor/_render_overrides.py +38 -11
- figrecipe/_editor/_renderer.py +46 -1
- figrecipe/_editor/_routes_annotation.py +114 -0
- figrecipe/_editor/_routes_axis.py +35 -6
- figrecipe/_editor/_routes_captions.py +130 -0
- figrecipe/_editor/_routes_composition.py +270 -0
- figrecipe/_editor/_routes_core.py +15 -173
- figrecipe/_editor/_routes_datatable.py +364 -0
- figrecipe/_editor/_routes_element.py +37 -19
- figrecipe/_editor/_routes_files.py +443 -0
- figrecipe/_editor/_routes_image.py +200 -0
- figrecipe/_editor/_routes_snapshot.py +94 -0
- figrecipe/_editor/_routes_style.py +28 -8
- figrecipe/_editor/_templates/__init__.py +40 -2
- figrecipe/_editor/_templates/_html.py +97 -103
- figrecipe/_editor/_templates/_html_components/__init__.py +13 -0
- figrecipe/_editor/_templates/_html_components/_composition_toolbar.py +79 -0
- figrecipe/_editor/_templates/_html_components/_file_browser.py +41 -0
- figrecipe/_editor/_templates/_html_datatable.py +92 -0
- figrecipe/_editor/_templates/_scripts/__init__.py +58 -0
- figrecipe/_editor/_templates/_scripts/_accordion.py +328 -0
- figrecipe/_editor/_templates/_scripts/_annotation_drag.py +504 -0
- figrecipe/_editor/_templates/_scripts/_api.py +1 -1
- figrecipe/_editor/_templates/_scripts/_canvas_context_menu.py +182 -0
- figrecipe/_editor/_templates/_scripts/_captions.py +231 -0
- figrecipe/_editor/_templates/_scripts/_composition.py +283 -0
- figrecipe/_editor/_templates/_scripts/_core.py +94 -37
- figrecipe/_editor/_templates/_scripts/_datatable/__init__.py +59 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_cell_edit.py +97 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_clipboard.py +164 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_context_menu.py +221 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_core.py +150 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_editable.py +511 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_import.py +161 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_plot.py +261 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_selection.py +438 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_table.py +256 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_tabs.py +354 -0
- figrecipe/_editor/_templates/_scripts/_element_editor.py +17 -2
- figrecipe/_editor/_templates/_scripts/_files.py +274 -40
- figrecipe/_editor/_templates/_scripts/_files_context_menu.py +240 -0
- figrecipe/_editor/_templates/_scripts/_hitmap.py +87 -84
- figrecipe/_editor/_templates/_scripts/_image_drop.py +428 -0
- figrecipe/_editor/_templates/_scripts/_legend_drag.py +5 -0
- figrecipe/_editor/_templates/_scripts/_multi_select.py +198 -0
- figrecipe/_editor/_templates/_scripts/_panel_drag.py +219 -48
- figrecipe/_editor/_templates/_scripts/_panel_drag_snapshot.py +33 -0
- figrecipe/_editor/_templates/_scripts/_panel_position.py +238 -54
- figrecipe/_editor/_templates/_scripts/_panel_resize.py +230 -0
- figrecipe/_editor/_templates/_scripts/_panel_snap.py +307 -0
- figrecipe/_editor/_templates/_scripts/_region_select.py +255 -0
- figrecipe/_editor/_templates/_scripts/_selection.py +8 -1
- figrecipe/_editor/_templates/_scripts/_sync.py +242 -0
- figrecipe/_editor/_templates/_scripts/_undo_redo.py +348 -0
- figrecipe/_editor/_templates/_scripts/_zoom.py +52 -19
- figrecipe/_editor/_templates/_styles/__init__.py +9 -0
- figrecipe/_editor/_templates/_styles/_base.py +47 -0
- figrecipe/_editor/_templates/_styles/_buttons.py +127 -6
- figrecipe/_editor/_templates/_styles/_composition.py +87 -0
- figrecipe/_editor/_templates/_styles/_controls.py +168 -3
- figrecipe/_editor/_templates/_styles/_datatable/__init__.py +40 -0
- figrecipe/_editor/_templates/_styles/_datatable/_editable.py +203 -0
- figrecipe/_editor/_templates/_styles/_datatable/_panel.py +268 -0
- figrecipe/_editor/_templates/_styles/_datatable/_table.py +479 -0
- figrecipe/_editor/_templates/_styles/_datatable/_toolbar.py +384 -0
- figrecipe/_editor/_templates/_styles/_datatable/_vars.py +123 -0
- figrecipe/_editor/_templates/_styles/_dynamic_props.py +5 -5
- figrecipe/_editor/_templates/_styles/_file_browser.py +466 -0
- figrecipe/_editor/_templates/_styles/_forms.py +98 -0
- figrecipe/_editor/_templates/_styles/_hitmap.py +7 -0
- figrecipe/_editor/_templates/_styles/_modals.py +29 -0
- figrecipe/_editor/_templates/_styles/_overlays.py +5 -5
- figrecipe/_editor/_templates/_styles/_preview.py +213 -8
- figrecipe/_editor/_templates/_styles/_spinner.py +117 -0
- figrecipe/_editor/static/audio/click.mp3 +0 -0
- figrecipe/_editor/static/click.mp3 +0 -0
- figrecipe/_editor/static/icons/favicon.ico +0 -0
- figrecipe/_integrations/__init__.py +17 -0
- figrecipe/_integrations/_scitex_stats.py +298 -0
- figrecipe/_params/_DECORATION_METHODS.py +2 -0
- figrecipe/_recorder.py +28 -3
- figrecipe/_reproducer/_core.py +60 -49
- figrecipe/_utils/__init__.py +3 -0
- figrecipe/_utils/_bundle.py +205 -0
- figrecipe/_wrappers/_axes.py +150 -2
- figrecipe/_wrappers/_caption_generator.py +218 -0
- figrecipe/_wrappers/_figure.py +26 -1
- figrecipe/_wrappers/_stat_annotation.py +274 -0
- figrecipe/styles/_style_applier.py +10 -2
- figrecipe/styles/presets/SCITEX.yaml +11 -4
- {figrecipe-0.7.4.dist-info → figrecipe-0.9.0.dist-info}/METADATA +144 -146
- figrecipe-0.9.0.dist-info/RECORD +277 -0
- figrecipe-0.9.0.dist-info/entry_points.txt +2 -0
- figrecipe-0.7.4.dist-info/RECORD +0 -188
- {figrecipe-0.7.4.dist-info → figrecipe-0.9.0.dist-info}/WHEEL +0 -0
- {figrecipe-0.7.4.dist-info → figrecipe-0.9.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Caption controls JavaScript for the figure editor.
|
|
4
|
+
|
|
5
|
+
This module contains the JavaScript code for:
|
|
6
|
+
- Scientific figure captions (Fig. 1. Description...)
|
|
7
|
+
- Panel captions for multi-panel figures
|
|
8
|
+
- Caption preview and backend synchronization
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
SCRIPTS_CAPTIONS = """
|
|
12
|
+
// ===== CAPTION CONTROLS (Scientific Figure Captions) =====
|
|
13
|
+
|
|
14
|
+
// Initialize caption input event handlers
|
|
15
|
+
function initializeCaptionInputs() {
|
|
16
|
+
const figNumInput = document.getElementById('caption_figure_number');
|
|
17
|
+
const figTextInput = document.getElementById('caption_figure_text');
|
|
18
|
+
const panelTextInput = document.getElementById('caption_panel_text');
|
|
19
|
+
const previewEl = document.getElementById('caption-preview-text');
|
|
20
|
+
|
|
21
|
+
// Update caption preview when figure number changes
|
|
22
|
+
if (figNumInput) {
|
|
23
|
+
figNumInput.addEventListener('input', function() {
|
|
24
|
+
updateCaptionPreview();
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Update caption preview and send to backend when caption text changes
|
|
29
|
+
if (figTextInput) {
|
|
30
|
+
let timeout;
|
|
31
|
+
figTextInput.addEventListener('input', function() {
|
|
32
|
+
updateCaptionPreview();
|
|
33
|
+
clearTimeout(timeout);
|
|
34
|
+
timeout = setTimeout(() => {
|
|
35
|
+
updateFigureCaption();
|
|
36
|
+
}, UPDATE_DEBOUNCE);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Update panel caption on input
|
|
41
|
+
if (panelTextInput) {
|
|
42
|
+
let timeout;
|
|
43
|
+
panelTextInput.addEventListener('input', function() {
|
|
44
|
+
clearTimeout(timeout);
|
|
45
|
+
timeout = setTimeout(() => {
|
|
46
|
+
updatePanelCaption();
|
|
47
|
+
}, UPDATE_DEBOUNCE);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Initial preview update
|
|
52
|
+
updateCaptionPreview();
|
|
53
|
+
loadCaptions();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Update the caption preview text (composed caption in both properties panel and canvas pane)
|
|
57
|
+
function updateCaptionPreview() {
|
|
58
|
+
const figNumInput = document.getElementById('caption_figure_number');
|
|
59
|
+
const figTextInput = document.getElementById('caption_figure_text');
|
|
60
|
+
const composedEl = document.getElementById('composed-caption-text');
|
|
61
|
+
const canvasCaptionEl = document.getElementById('canvas-caption-text');
|
|
62
|
+
|
|
63
|
+
const figNum = figNumInput?.value || '1';
|
|
64
|
+
const figText = figTextInput?.value || '';
|
|
65
|
+
|
|
66
|
+
// Build composed caption HTML
|
|
67
|
+
let html = `<b>Fig. ${figNum}.</b>`;
|
|
68
|
+
if (figText) {
|
|
69
|
+
html += ` ${figText}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Add panel captions if available
|
|
73
|
+
const panelCaptions = getPanelCaptions();
|
|
74
|
+
if (panelCaptions.length > 0) {
|
|
75
|
+
const panelHtml = panelCaptions
|
|
76
|
+
.map((pc, i) => pc ? `<span class="panel-caption">(${String.fromCharCode(65 + i)}) ${pc}</span>` : '')
|
|
77
|
+
.filter(s => s) // Filter empty strings
|
|
78
|
+
.join(' ');
|
|
79
|
+
if (panelHtml) {
|
|
80
|
+
html += ' ' + panelHtml;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Update both preview locations
|
|
85
|
+
if (composedEl) composedEl.innerHTML = html;
|
|
86
|
+
if (canvasCaptionEl) canvasCaptionEl.innerHTML = html;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Get all panel captions from stored state
|
|
90
|
+
function getPanelCaptions() {
|
|
91
|
+
// Start with loaded panel captions from server
|
|
92
|
+
const captions = [...loadedPanelCaptions];
|
|
93
|
+
|
|
94
|
+
// Check for UI overrides
|
|
95
|
+
for (let i = 0; i < 9; i++) { // Support up to 9 panels (A-I)
|
|
96
|
+
const input = document.querySelector(`[data-panel-caption="${i}"]`);
|
|
97
|
+
if (input && input.value) {
|
|
98
|
+
captions[i] = input.value;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Also check current panel caption input
|
|
102
|
+
const currentPanel = document.getElementById('caption_panel_text');
|
|
103
|
+
if (currentPanel && currentPanel.value && selectedElement?.ax_index !== undefined) {
|
|
104
|
+
captions[selectedElement.ax_index] = currentPanel.value;
|
|
105
|
+
}
|
|
106
|
+
return captions; // Keep all entries (including empty) for proper indexing
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Store loaded panel captions globally
|
|
110
|
+
let loadedPanelCaptions = [];
|
|
111
|
+
|
|
112
|
+
// Update panel caption input when panel is selected
|
|
113
|
+
function updatePanelCaptionInput(axIndex) {
|
|
114
|
+
const panelTextInput = document.getElementById('caption_panel_text');
|
|
115
|
+
if (!panelTextInput) return;
|
|
116
|
+
|
|
117
|
+
// Get caption for this panel index
|
|
118
|
+
const caption = loadedPanelCaptions[axIndex] || '';
|
|
119
|
+
panelTextInput.value = caption;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Load existing captions from server
|
|
123
|
+
async function loadCaptions() {
|
|
124
|
+
try {
|
|
125
|
+
const response = await fetch('/get_captions');
|
|
126
|
+
const data = await response.json();
|
|
127
|
+
|
|
128
|
+
if (data.figure_number) {
|
|
129
|
+
const figNumInput = document.getElementById('caption_figure_number');
|
|
130
|
+
if (figNumInput) figNumInput.value = data.figure_number;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (data.figure_caption) {
|
|
134
|
+
const figTextInput = document.getElementById('caption_figure_text');
|
|
135
|
+
if (figTextInput) figTextInput.value = data.figure_caption;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Store panel captions for composed caption
|
|
139
|
+
if (data.panel_captions && Array.isArray(data.panel_captions)) {
|
|
140
|
+
loadedPanelCaptions = data.panel_captions;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
updateCaptionPreview();
|
|
144
|
+
console.log('Loaded captions:', data);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.log('Captions not loaded (endpoint may not exist yet):', error.message);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Update figure caption on server
|
|
151
|
+
async function updateFigureCaption() {
|
|
152
|
+
const figNumInput = document.getElementById('caption_figure_number');
|
|
153
|
+
const figTextInput = document.getElementById('caption_figure_text');
|
|
154
|
+
|
|
155
|
+
const figNum = parseInt(figNumInput?.value) || 1;
|
|
156
|
+
const figText = figTextInput?.value || '';
|
|
157
|
+
|
|
158
|
+
console.log(`Updating figure caption: Fig. ${figNum}. ${figText}`);
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
const response = await fetch('/update_caption', {
|
|
162
|
+
method: 'POST',
|
|
163
|
+
headers: { 'Content-Type': 'application/json' },
|
|
164
|
+
body: JSON.stringify({
|
|
165
|
+
type: 'figure',
|
|
166
|
+
figure_number: figNum,
|
|
167
|
+
text: figText
|
|
168
|
+
})
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const data = await response.json();
|
|
172
|
+
|
|
173
|
+
if (data.success) {
|
|
174
|
+
console.log('Figure caption updated');
|
|
175
|
+
} else {
|
|
176
|
+
console.error('Figure caption update failed:', data.error);
|
|
177
|
+
}
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.error('Figure caption update failed:', error);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Update panel caption on server
|
|
184
|
+
async function updatePanelCaption() {
|
|
185
|
+
const panelTextInput = document.getElementById('caption_panel_text');
|
|
186
|
+
const panelText = panelTextInput?.value || '';
|
|
187
|
+
|
|
188
|
+
// Get current panel index - prefer currentSelectedPanelIndex, fallback to selectedElement
|
|
189
|
+
let panelIndex = 0;
|
|
190
|
+
if (typeof currentSelectedPanelIndex !== 'undefined' && currentSelectedPanelIndex !== null) {
|
|
191
|
+
panelIndex = currentSelectedPanelIndex;
|
|
192
|
+
} else if (selectedElement && selectedElement.ax_index !== undefined) {
|
|
193
|
+
panelIndex = selectedElement.ax_index;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Update local cache
|
|
197
|
+
while (loadedPanelCaptions.length <= panelIndex) {
|
|
198
|
+
loadedPanelCaptions.push('');
|
|
199
|
+
}
|
|
200
|
+
loadedPanelCaptions[panelIndex] = panelText;
|
|
201
|
+
|
|
202
|
+
console.log(`Updating panel ${panelIndex} caption: ${panelText}`);
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
const response = await fetch('/update_caption', {
|
|
206
|
+
method: 'POST',
|
|
207
|
+
headers: { 'Content-Type': 'application/json' },
|
|
208
|
+
body: JSON.stringify({
|
|
209
|
+
type: 'panel',
|
|
210
|
+
panel_index: panelIndex,
|
|
211
|
+
text: panelText
|
|
212
|
+
})
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const data = await response.json();
|
|
216
|
+
|
|
217
|
+
if (data.success) {
|
|
218
|
+
console.log('Panel caption updated');
|
|
219
|
+
updateCaptionPreview(); // Update composed caption preview
|
|
220
|
+
} else {
|
|
221
|
+
console.error('Panel caption update failed:', data.error);
|
|
222
|
+
}
|
|
223
|
+
} catch (error) {
|
|
224
|
+
console.error('Panel caption update failed:', error);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
__all__ = ["SCRIPTS_CAPTIONS"]
|
|
230
|
+
|
|
231
|
+
# EOF
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""JavaScript for composition features (alignment, visibility, import)."""
|
|
4
|
+
|
|
5
|
+
SCRIPTS_COMPOSITION = """
|
|
6
|
+
// ============================================================
|
|
7
|
+
// Composition: Panel Visibility, Alignment, Distribution
|
|
8
|
+
// ============================================================
|
|
9
|
+
|
|
10
|
+
// Spinner helper functions (CSS-based using body.loading class)
|
|
11
|
+
function showSpinner(message) {
|
|
12
|
+
const textEl = document.querySelector('.spinner-text');
|
|
13
|
+
if (textEl && message) textEl.textContent = message;
|
|
14
|
+
document.body.classList.add('loading');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function hideSpinner() {
|
|
18
|
+
document.body.classList.remove('loading');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Update preview image with new base64 data
|
|
22
|
+
function updatePreviewImage(imageBase64) {
|
|
23
|
+
const img = document.getElementById('preview-image');
|
|
24
|
+
if (img && imageBase64) {
|
|
25
|
+
img.src = 'data:image/png;base64,' + imageBase64;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Update bounding boxes (refresh hit regions)
|
|
30
|
+
function updateBboxes(newBboxes) {
|
|
31
|
+
if (typeof bboxes !== 'undefined' && newBboxes) {
|
|
32
|
+
Object.assign(bboxes, newBboxes);
|
|
33
|
+
if (typeof updateHitRegions === 'function') {
|
|
34
|
+
updateHitRegions();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Track panel visibility states
|
|
40
|
+
let panelVisibility = {};
|
|
41
|
+
|
|
42
|
+
// Initialize panel visibility from server
|
|
43
|
+
async function initPanelVisibility() {
|
|
44
|
+
try {
|
|
45
|
+
const response = await fetch('/api/panel-info');
|
|
46
|
+
const data = await response.json();
|
|
47
|
+
if (data.panels) {
|
|
48
|
+
data.panels.forEach(panel => {
|
|
49
|
+
const key = `${panel.position[0]}_${panel.position[1]}`;
|
|
50
|
+
panelVisibility[key] = panel.visible;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
} catch (e) {
|
|
54
|
+
console.error('Failed to init panel visibility:', e);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Toggle panel visibility
|
|
59
|
+
async function togglePanelVisibility(row, col) {
|
|
60
|
+
const key = `${row}_${col}`;
|
|
61
|
+
const currentVisible = panelVisibility[key] !== false;
|
|
62
|
+
const newVisible = !currentVisible;
|
|
63
|
+
|
|
64
|
+
showSpinner('Updating visibility...');
|
|
65
|
+
try {
|
|
66
|
+
const response = await fetch('/api/panel-visibility', {
|
|
67
|
+
method: 'POST',
|
|
68
|
+
headers: { 'Content-Type': 'application/json' },
|
|
69
|
+
body: JSON.stringify({
|
|
70
|
+
position: [row, col],
|
|
71
|
+
visible: newVisible
|
|
72
|
+
})
|
|
73
|
+
});
|
|
74
|
+
const data = await response.json();
|
|
75
|
+
if (data.success) {
|
|
76
|
+
panelVisibility[key] = newVisible;
|
|
77
|
+
updatePreviewImage(data.image);
|
|
78
|
+
updateBboxes(data.bboxes);
|
|
79
|
+
console.log(`Panel (${row},${col}) visibility: ${newVisible}`);
|
|
80
|
+
} else {
|
|
81
|
+
console.error('Toggle visibility failed:', data.error);
|
|
82
|
+
}
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.error('Toggle visibility error:', e);
|
|
85
|
+
} finally {
|
|
86
|
+
hideSpinner();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Hide panel
|
|
91
|
+
async function hidePanel(row, col) {
|
|
92
|
+
showSpinner('Hiding panel...');
|
|
93
|
+
try {
|
|
94
|
+
const response = await fetch('/api/panel-visibility', {
|
|
95
|
+
method: 'POST',
|
|
96
|
+
headers: { 'Content-Type': 'application/json' },
|
|
97
|
+
body: JSON.stringify({
|
|
98
|
+
position: [row, col],
|
|
99
|
+
visible: false
|
|
100
|
+
})
|
|
101
|
+
});
|
|
102
|
+
const data = await response.json();
|
|
103
|
+
if (data.success) {
|
|
104
|
+
panelVisibility[`${row}_${col}`] = false;
|
|
105
|
+
updatePreviewImage(data.image);
|
|
106
|
+
updateBboxes(data.bboxes);
|
|
107
|
+
}
|
|
108
|
+
} catch (e) {
|
|
109
|
+
console.error('Hide panel error:', e);
|
|
110
|
+
} finally {
|
|
111
|
+
hideSpinner();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Show panel
|
|
116
|
+
async function showPanel(row, col) {
|
|
117
|
+
showSpinner('Showing panel...');
|
|
118
|
+
try {
|
|
119
|
+
const response = await fetch('/api/panel-visibility', {
|
|
120
|
+
method: 'POST',
|
|
121
|
+
headers: { 'Content-Type': 'application/json' },
|
|
122
|
+
body: JSON.stringify({
|
|
123
|
+
position: [row, col],
|
|
124
|
+
visible: true
|
|
125
|
+
})
|
|
126
|
+
});
|
|
127
|
+
const data = await response.json();
|
|
128
|
+
if (data.success) {
|
|
129
|
+
panelVisibility[`${row}_${col}`] = true;
|
|
130
|
+
updatePreviewImage(data.image);
|
|
131
|
+
updateBboxes(data.bboxes);
|
|
132
|
+
}
|
|
133
|
+
} catch (e) {
|
|
134
|
+
console.error('Show panel error:', e);
|
|
135
|
+
} finally {
|
|
136
|
+
hideSpinner();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Align selected panels
|
|
141
|
+
async function alignPanels(mode) {
|
|
142
|
+
const panels = getSelectedPanelPositions();
|
|
143
|
+
if (panels.length < 2) {
|
|
144
|
+
console.log('Need at least 2 panels selected for alignment');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
showSpinner(`Aligning ${mode}...`);
|
|
149
|
+
try {
|
|
150
|
+
const response = await fetch('/api/align-panels', {
|
|
151
|
+
method: 'POST',
|
|
152
|
+
headers: { 'Content-Type': 'application/json' },
|
|
153
|
+
body: JSON.stringify({
|
|
154
|
+
panels: panels,
|
|
155
|
+
mode: mode
|
|
156
|
+
})
|
|
157
|
+
});
|
|
158
|
+
const data = await response.json();
|
|
159
|
+
if (data.success) {
|
|
160
|
+
updatePreviewImage(data.image);
|
|
161
|
+
updateBboxes(data.bboxes);
|
|
162
|
+
console.log(`Aligned ${panels.length} panels: ${mode}`);
|
|
163
|
+
} else {
|
|
164
|
+
console.error('Align panels failed:', data.error);
|
|
165
|
+
}
|
|
166
|
+
} catch (e) {
|
|
167
|
+
console.error('Align panels error:', e);
|
|
168
|
+
} finally {
|
|
169
|
+
hideSpinner();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Distribute panels evenly
|
|
174
|
+
async function distributePanels(direction) {
|
|
175
|
+
const panels = getSelectedPanelPositions();
|
|
176
|
+
if (panels.length < 2) {
|
|
177
|
+
console.log('Need at least 2 panels selected for distribution');
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
showSpinner(`Distributing ${direction}...`);
|
|
182
|
+
try {
|
|
183
|
+
const response = await fetch('/api/distribute-panels', {
|
|
184
|
+
method: 'POST',
|
|
185
|
+
headers: { 'Content-Type': 'application/json' },
|
|
186
|
+
body: JSON.stringify({
|
|
187
|
+
panels: panels,
|
|
188
|
+
direction: direction
|
|
189
|
+
})
|
|
190
|
+
});
|
|
191
|
+
const data = await response.json();
|
|
192
|
+
if (data.success) {
|
|
193
|
+
updatePreviewImage(data.image);
|
|
194
|
+
updateBboxes(data.bboxes);
|
|
195
|
+
console.log(`Distributed ${panels.length} panels: ${direction}`);
|
|
196
|
+
} else {
|
|
197
|
+
console.error('Distribute panels failed:', data.error);
|
|
198
|
+
}
|
|
199
|
+
} catch (e) {
|
|
200
|
+
console.error('Distribute panels error:', e);
|
|
201
|
+
} finally {
|
|
202
|
+
hideSpinner();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Smart align all panels
|
|
207
|
+
async function smartAlign() {
|
|
208
|
+
showSpinner('Smart aligning...');
|
|
209
|
+
try {
|
|
210
|
+
const response = await fetch('/api/smart-align', {
|
|
211
|
+
method: 'POST',
|
|
212
|
+
headers: { 'Content-Type': 'application/json' },
|
|
213
|
+
body: JSON.stringify({})
|
|
214
|
+
});
|
|
215
|
+
const data = await response.json();
|
|
216
|
+
if (data.success) {
|
|
217
|
+
updatePreviewImage(data.image);
|
|
218
|
+
updateBboxes(data.bboxes);
|
|
219
|
+
console.log('Smart align complete');
|
|
220
|
+
} else {
|
|
221
|
+
console.error('Smart align failed:', data.error);
|
|
222
|
+
}
|
|
223
|
+
} catch (e) {
|
|
224
|
+
console.error('Smart align error:', e);
|
|
225
|
+
} finally {
|
|
226
|
+
hideSpinner();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Import axes from another recipe
|
|
231
|
+
async function importPanel(sourcePath, sourceAxes, targetRow, targetCol) {
|
|
232
|
+
showSpinner('Importing panel...');
|
|
233
|
+
try {
|
|
234
|
+
const response = await fetch('/api/import-panel', {
|
|
235
|
+
method: 'POST',
|
|
236
|
+
headers: { 'Content-Type': 'application/json' },
|
|
237
|
+
body: JSON.stringify({
|
|
238
|
+
source: sourcePath,
|
|
239
|
+
source_axes: sourceAxes || 'ax_0_0',
|
|
240
|
+
target_position: [targetRow, targetCol]
|
|
241
|
+
})
|
|
242
|
+
});
|
|
243
|
+
const data = await response.json();
|
|
244
|
+
if (data.success) {
|
|
245
|
+
updatePreviewImage(data.image);
|
|
246
|
+
updateBboxes(data.bboxes);
|
|
247
|
+
console.log(`Imported ${sourceAxes} from ${sourcePath} to (${targetRow},${targetCol})`);
|
|
248
|
+
} else {
|
|
249
|
+
console.error('Import panel failed:', data.error);
|
|
250
|
+
}
|
|
251
|
+
} catch (e) {
|
|
252
|
+
console.error('Import panel error:', e);
|
|
253
|
+
} finally {
|
|
254
|
+
hideSpinner();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Get positions of selected panels (from bboxes with 'axes_' prefix)
|
|
259
|
+
function getSelectedPanelPositions() {
|
|
260
|
+
const positions = [];
|
|
261
|
+
// Get all panel bboxes (axes_X_Y format)
|
|
262
|
+
if (typeof bboxes !== 'undefined') {
|
|
263
|
+
for (const key in bboxes) {
|
|
264
|
+
if (key.startsWith('axes_')) {
|
|
265
|
+
const parts = key.split('_');
|
|
266
|
+
if (parts.length >= 3) {
|
|
267
|
+
const row = parseInt(parts[1]);
|
|
268
|
+
const col = parseInt(parts[2]);
|
|
269
|
+
positions.push([row, col]);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return positions;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Initialize on load
|
|
278
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
279
|
+
initPanelVisibility();
|
|
280
|
+
});
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
__all__ = ["SCRIPTS_COMPOSITION"]
|