streamlit-plugins 0.7.13__tar.gz → 0.7.14__tar.gz
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.
- {streamlit_plugins-0.7.13/streamlit_plugins.egg-info → streamlit_plugins-0.7.14}/PKG-INFO +1 -1
- streamlit_plugins-0.7.14/streamlit_plugins/components/document_blocks/__init__.py +736 -0
- streamlit_plugins-0.7.14/streamlit_plugins/extension/dynamic_container/__init__.py +156 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14/streamlit_plugins.egg-info}/PKG-INFO +1 -1
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins.egg-info/SOURCES.txt +2 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/LICENSE +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/README.md +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/setup.cfg +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/setup.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/__init__.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/__init__.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/annotated_text/__init__.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/input_chat_advance/__init__.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/__init__.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/asset-manifest.json +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/bootstrap.min.css +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/custom.css +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/index.html +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/static/css/2.4f758a59.chunk.css +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/static/css/2.4f758a59.chunk.css.map +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/static/js/2.524324ba.chunk.js +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/static/js/2.524324ba.chunk.js.LICENSE.txt +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/static/js/2.524324ba.chunk.js.map +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/static/js/main.34b82d10.chunk.js +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/static/js/main.34b82d10.chunk.js.map +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/static/js/runtime-main.65660c44.js +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/static/js/runtime-main.65660c44.js.map +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/loader/__init__.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/__init__.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/config.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/frontend/build/CrossOriginInterface.js +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/frontend/build/asset-manifest.json +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/frontend/build/bootstrap.min.css +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/frontend/build/bootstrap.min.css.map +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/frontend/build/index.html +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/frontend/build/material-fonts.css +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/frontend/build/material.woff2 +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/frontend/build/st-styles.css +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/frontend/build/static/js/main.8bc23bfa.js +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/frontend/build/static/js/main.8bc23bfa.js.LICENSE.txt +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/frontend/build/static/js/main.8bc23bfa.js.map +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/frontend/build/style.css +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/inject_script.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/sankey_input/__init__.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/selectbox_enrich/__init__.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/skeleton/__init__.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/skeleton/template_builder.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/__init__.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/asset-manifest.json +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/index.html +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/drawsvg.js +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/drawsvg.v7.js +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/favicon.ico +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/images/sort_asc.png +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/images/sort_both.png +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/images/sort_desc.png +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/js/2.9cedfdc4.chunk.js +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/js/2.9cedfdc4.chunk.js.LICENSE.txt +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/js/2.9cedfdc4.chunk.js.map +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/js/main.1dfd8582.chunk.js +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/js/main.1dfd8582.chunk.js.map +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/js/runtime-main.8efd821a.js +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/js/runtime-main.8efd821a.js.map +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/snakeviz.css +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/snakeviz.js +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/vendor/d3.v3.min.js +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/vendor/immutable.min.js +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/vendor/jquery-3.2.1.min.js +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/vendor/jquery.dataTables.min.css +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/vendor/jquery.dataTables.min.js +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/vendor/lodash.min.js +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/templates/dir.html +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/templates/viz.html +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/theme_changer/__init__.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/theme_changer/config.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/theme_changer/entity.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/theme_changer/frontend/build/CrossOriginInterface.js +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/theme_changer/inject_script.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/extension/__init__.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/extension/button_group/__init__.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/framework/__init__.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/framework/multilit/__init__.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/framework/multilit/app_wrapper.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/framework/multilit/loading_engine.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/framework/multilit/multilit.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/framework/reactlit/__init__.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/framework/reactlit/advanced_patterns.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/framework/reactlit/lab.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/framework/reactlit/reactive.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/framework/reactlit/reactlit.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/framework/reactlit/test_reactive.py +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins.egg-info/dependency_links.txt +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins.egg-info/requires.txt +0 -0
- {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,736 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import mimetypes
|
|
3
|
+
from io import BytesIO
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional, Union, Literal
|
|
6
|
+
|
|
7
|
+
from PIL import Image
|
|
8
|
+
from streamlit.components.v2 import component as create_component
|
|
9
|
+
from streamlit.elements.lib.layout_utils import Width, Height
|
|
10
|
+
|
|
11
|
+
___HTML = """
|
|
12
|
+
<div id="image-viewer-container">
|
|
13
|
+
<div id="filters-bar" class="filters-bar"></div>
|
|
14
|
+
<div class="image-wrapper">
|
|
15
|
+
<img id="doc-image" src="" alt="Documento" />
|
|
16
|
+
<svg id="reading-order-parents-svg" class="reading-order-layer"></svg>
|
|
17
|
+
<div id="bboxes-overlay"></div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
"""
|
|
21
|
+
__CSS = """
|
|
22
|
+
/* COMPONENTE */
|
|
23
|
+
#image-viewer-container {
|
|
24
|
+
flex: 1;
|
|
25
|
+
max-width: 550px;
|
|
26
|
+
background: #fff;
|
|
27
|
+
padding: 10px;
|
|
28
|
+
border-radius: 8px;
|
|
29
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.image-wrapper {
|
|
33
|
+
position: relative;
|
|
34
|
+
display: inline-block;
|
|
35
|
+
width: 100%;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#doc-image {
|
|
39
|
+
display: block;
|
|
40
|
+
width: 100%;
|
|
41
|
+
height: auto;
|
|
42
|
+
border-radius: 4px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
#bboxes-overlay {
|
|
46
|
+
position: absolute;
|
|
47
|
+
top: 0;
|
|
48
|
+
left: 0;
|
|
49
|
+
width: 100%;
|
|
50
|
+
height: 100%;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* CAJAS */
|
|
54
|
+
.bbox {
|
|
55
|
+
position: absolute;
|
|
56
|
+
box-sizing: border-box;
|
|
57
|
+
cursor: pointer;
|
|
58
|
+
/* Usamos variables CSS para inyectar los colores desde JS */
|
|
59
|
+
border: 2px solid var(--border-color);
|
|
60
|
+
background-color: var(--bg-normal);
|
|
61
|
+
transition: background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.bbox:hover {
|
|
65
|
+
background-color: var(--bg-hover);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.bbox-tooltip {
|
|
69
|
+
position: absolute;
|
|
70
|
+
bottom: 100%;
|
|
71
|
+
left: -2px;
|
|
72
|
+
color: #fff;
|
|
73
|
+
padding: 0.25rem 0.5rem;
|
|
74
|
+
font-size: 0.875rem;
|
|
75
|
+
font-weight: bold;
|
|
76
|
+
white-space: nowrap;
|
|
77
|
+
border-radius: 3px 3px 3px 0;
|
|
78
|
+
opacity: 0;
|
|
79
|
+
visibility: hidden;
|
|
80
|
+
transition: opacity 0.15s ease;
|
|
81
|
+
pointer-events: none;
|
|
82
|
+
z-index: 100;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.bbox:hover > .bbox-tooltip {
|
|
86
|
+
opacity: 1;
|
|
87
|
+
visibility: visible;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.bbox-parent {
|
|
91
|
+
z-index: 10;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.bbox-child {
|
|
95
|
+
opacity: 0;
|
|
96
|
+
pointer-events: none;
|
|
97
|
+
z-index: 20;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Manejo del hover nativo a nivel de grupo (Reemplaza los eventos de JS) */
|
|
101
|
+
.bbox-group:hover .bbox-child {
|
|
102
|
+
opacity: 1;
|
|
103
|
+
pointer-events: auto;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* ESTADO SELECCIONADO AL HACER CLICK */
|
|
107
|
+
.bbox.is-selected {
|
|
108
|
+
background-color: var(--bg-hover) !important;
|
|
109
|
+
border-width: 3px !important;
|
|
110
|
+
box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.9), 0 0 10px rgba(0, 0, 0, 0.6);
|
|
111
|
+
/* Estas dos líneas aseguran que un hijo seleccionado siempre sea visible */
|
|
112
|
+
opacity: 1 !important;
|
|
113
|
+
pointer-events: auto !important;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.bbox.is-selected > .bbox-tooltip {
|
|
117
|
+
opacity: 1;
|
|
118
|
+
visibility: visible;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.bbox-group:has(.bbox-child.is-selected) .bbox-parent > .bbox-tooltip {
|
|
122
|
+
opacity: 1;
|
|
123
|
+
visibility: visible;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* BARRA DE FILTROS */
|
|
127
|
+
.filters-bar {
|
|
128
|
+
display: flex;
|
|
129
|
+
gap: 8px;
|
|
130
|
+
flex-wrap: wrap;
|
|
131
|
+
margin-bottom: 12px;
|
|
132
|
+
min-height: 28px;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.filter-btn {
|
|
136
|
+
border: 1px solid #ccc;
|
|
137
|
+
padding: 0.25rem 0.5rem;
|
|
138
|
+
border-radius: 12px;
|
|
139
|
+
font-size: 0.875rem;
|
|
140
|
+
font-weight: bold;
|
|
141
|
+
cursor: pointer;
|
|
142
|
+
transition: all 0.2s ease;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.filter-btn:hover {
|
|
146
|
+
opacity: 0.85;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* Las clases activas usarán los estilos personalizados del objeto */
|
|
150
|
+
.filter-btn.is-active {
|
|
151
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* OCULTAR CAJAS FILTRADAS */
|
|
155
|
+
.bbox.is-filtered-out {
|
|
156
|
+
display: none !important;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* PADRE "FANTASMA" (No hace match, pero su hijo sí) */
|
|
160
|
+
.bbox.is-ghost {
|
|
161
|
+
border-color: #b0b0b0 !important;
|
|
162
|
+
background-color: rgba(176, 176, 176, 0.15) !important;
|
|
163
|
+
pointer-events: none !important; /* No se puede hacer click */
|
|
164
|
+
z-index: 5 !important;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/* Tooltip del padre fantasma (siempre visible y en gris) */
|
|
168
|
+
.bbox.is-ghost > .bbox-tooltip {
|
|
169
|
+
background-color: #b0b0b0 !important;
|
|
170
|
+
opacity: 1 !important;
|
|
171
|
+
visibility: visible !important;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* HIJOS QUE HACEN MATCH (Visibles de inmediato sin necesidad de hover) */
|
|
175
|
+
.bbox-child.is-forced-visible {
|
|
176
|
+
opacity: 1 !important;
|
|
177
|
+
pointer-events: auto !important;
|
|
178
|
+
z-index: 25 !important;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* CAPA SVG ORDEN DE LECTURA */
|
|
182
|
+
.reading-order-layer {
|
|
183
|
+
position: absolute;
|
|
184
|
+
top: 0;
|
|
185
|
+
left: 0;
|
|
186
|
+
width: 100%;
|
|
187
|
+
height: 100%;
|
|
188
|
+
pointer-events: none; /* Crucial para no bloquear los clicks de las cajas */
|
|
189
|
+
z-index: 30; /* Por debajo del hover/selección, por encima de las hijas */
|
|
190
|
+
display: none; /* Oculto por defecto */
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* Cuando se marca el checkbox HTML */
|
|
194
|
+
.reading-order-layer.is-visible {
|
|
195
|
+
display: block;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/* Animación de la línea controlada por variable CSS --ro-speed */
|
|
199
|
+
.ro-line {
|
|
200
|
+
fill: none;
|
|
201
|
+
stroke-width: 2.5;
|
|
202
|
+
stroke-dasharray: 6;
|
|
203
|
+
animation: dash-animation var(--ro-speed, 20s) linear infinite;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* SVG local de cada grupo (para los hijos) */
|
|
207
|
+
.children-ro-layer {
|
|
208
|
+
position: absolute;
|
|
209
|
+
top: 0;
|
|
210
|
+
left: 0;
|
|
211
|
+
width: 100%;
|
|
212
|
+
height: 100%;
|
|
213
|
+
pointer-events: none;
|
|
214
|
+
z-index: 25; /* Por debajo de los tooltips, por encima de las cajas */
|
|
215
|
+
opacity: 0; /* Oculto por defecto */
|
|
216
|
+
transition: opacity 0.2s ease;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/* Solo se muestra si pasas el ratón por el grupo, o si el padre o un hijo tienen .is-selected */
|
|
220
|
+
/* Solo se muestra si el ratón pasa por el grupo O si está seleccionado,
|
|
221
|
+
PERO ÚNICAMENTE si el SVG general de padres está visible */
|
|
222
|
+
.image-wrapper:has(#reading-order-parents-svg.is-visible) .bbox-group:hover .children-ro-layer,
|
|
223
|
+
.image-wrapper:has(#reading-order-parents-svg.is-visible) .bbox-group:has(.bbox.is-selected) .children-ro-layer {
|
|
224
|
+
opacity: 1 !important;
|
|
225
|
+
}
|
|
226
|
+
/* Las clases de animación que ya tenías */
|
|
227
|
+
.ro-line-child {
|
|
228
|
+
fill: none;
|
|
229
|
+
stroke-width: 2;
|
|
230
|
+
stroke-dasharray: 4;
|
|
231
|
+
animation: dash-animation var(--ro-child-speed, 15s) linear infinite;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
@keyframes dash-animation {
|
|
235
|
+
to { stroke-dashoffset: -200; }
|
|
236
|
+
}
|
|
237
|
+
"""
|
|
238
|
+
__JS = """
|
|
239
|
+
// =====================================================================
|
|
240
|
+
// 1. LÓGICA DEL COMPONENTE (Exportable a Streamlit V2)
|
|
241
|
+
// =====================================================================
|
|
242
|
+
|
|
243
|
+
function hexToRgba(hex, alpha) {
|
|
244
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
245
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
246
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
247
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ponytail: normalize bbox from polygon/bbox format or legacy x,y,w,h
|
|
251
|
+
// converts pixel coords to % if document_size provided, otherwise returns as-is
|
|
252
|
+
function extractBBoxCoords(box, docSize) {
|
|
253
|
+
let coords = null;
|
|
254
|
+
|
|
255
|
+
if (box.bbox && Array.isArray(box.bbox)) {
|
|
256
|
+
// bbox format: [x, y, x2, y2]
|
|
257
|
+
coords = { x: box.bbox[0], y: box.bbox[1], x2: box.bbox[2], y2: box.bbox[3] };
|
|
258
|
+
} else if (box.polygon && Array.isArray(box.polygon) && box.polygon.length >= 2) {
|
|
259
|
+
// polygon format: [[x1,y1], [x2,y2], ...]
|
|
260
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
261
|
+
box.polygon.forEach(([x, y]) => {
|
|
262
|
+
minX = Math.min(minX, x);
|
|
263
|
+
minY = Math.min(minY, y);
|
|
264
|
+
maxX = Math.max(maxX, x);
|
|
265
|
+
maxY = Math.max(maxY, y);
|
|
266
|
+
});
|
|
267
|
+
coords = { x: minX, y: minY, x2: maxX, y2: maxY };
|
|
268
|
+
} else if (box.x !== undefined && box.y !== undefined && box.w !== undefined && box.h !== undefined) {
|
|
269
|
+
// legacy format: x, y, w, h (assuming percentages)
|
|
270
|
+
coords = { x: box.x, y: box.y, x2: box.x + box.w, y2: box.y + box.h, isLegacy: true };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (!coords) return null;
|
|
274
|
+
|
|
275
|
+
// normalize to percentages if docSize provided and coords are in pixels
|
|
276
|
+
if (docSize && docSize.width && docSize.height && !coords.isLegacy) {
|
|
277
|
+
const isPixel = coords.x > 100 || coords.y > 100 || coords.x2 > 100 || coords.y2 > 100;
|
|
278
|
+
if (isPixel) {
|
|
279
|
+
coords.x = (coords.x / docSize.width) * 100;
|
|
280
|
+
coords.y = (coords.y / docSize.height) * 100;
|
|
281
|
+
coords.x2 = (coords.x2 / docSize.width) * 100;
|
|
282
|
+
coords.y2 = (coords.y2 / docSize.height) * 100;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return coords;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Función auxiliar para gestionar la selección visual sin repintar todo el DOM
|
|
290
|
+
function selectBox(boxElement) {
|
|
291
|
+
// 1. Buscamos si hay algún elemento previamente seleccionado y le quitamos la clase
|
|
292
|
+
const currentSelected = document.querySelector('.bbox.is-selected');
|
|
293
|
+
if (currentSelected) {
|
|
294
|
+
currentSelected.classList.remove('is-selected');
|
|
295
|
+
}
|
|
296
|
+
// 2. Añadimos la clase al nuevo elemento clickeado
|
|
297
|
+
if (boxElement !== currentSelected) {
|
|
298
|
+
boxElement.classList.add('is-selected');
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export default function(component) {
|
|
303
|
+
const { setStateValue, setTriggerValue, parentElement, data } = component;
|
|
304
|
+
|
|
305
|
+
const imgEl = parentElement.querySelector('#doc-image');
|
|
306
|
+
const overlay = parentElement.querySelector('#bboxes-overlay');
|
|
307
|
+
const filtersBar = parentElement.querySelector('#filters-bar');
|
|
308
|
+
const svgParents = parentElement.querySelector('#reading-order-parents-svg');
|
|
309
|
+
|
|
310
|
+
if (data && data.imageUrl && imgEl.src !== data.imageUrl) {
|
|
311
|
+
imgEl.src = data.imageUrl;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
overlay.innerHTML = '';
|
|
315
|
+
filtersBar.innerHTML = '';
|
|
316
|
+
if (svgParents) svgParents.innerHTML = '';
|
|
317
|
+
if (!data || !data.blocks) return;
|
|
318
|
+
|
|
319
|
+
// 0. DOCUMENTO SIZE (para normalizar coordenadas en píxeles a porcentajes)
|
|
320
|
+
const docSize = data.document_size || null;
|
|
321
|
+
|
|
322
|
+
// 1. EXTRAEMOS ESTILOS Y CONFIGURACIÓN GLOBAL
|
|
323
|
+
const boxStyles = data.styles && data.styles.blocks ? data.styles.blocks : {};
|
|
324
|
+
const normalAlpha = boxStyles.normalAlpha !== undefined ? boxStyles.normalAlpha : 0.15;
|
|
325
|
+
const hoverAlpha = boxStyles.hoverAlpha !== undefined ? boxStyles.hoverAlpha : 0.35;
|
|
326
|
+
const childNormalAlpha = boxStyles.childNormalAlpha !== undefined ? boxStyles.childNormalAlpha : 0.25;
|
|
327
|
+
const childHoverAlpha = boxStyles.childHoverAlpha !== undefined ? boxStyles.childHoverAlpha : 0.50;
|
|
328
|
+
|
|
329
|
+
const initialSelectedId = data.selected_id || null;
|
|
330
|
+
|
|
331
|
+
// Configuración de Orden de Lectura
|
|
332
|
+
// ponytail: orderPath is true (use natural order), string (use path), or null (skip)
|
|
333
|
+
const orderPath = data.reading_order === true || typeof data.reading_order === 'string' ? data.reading_order : null;
|
|
334
|
+
const roStyles = data.styles && data.styles.reading_order ? data.styles.reading_order : {};
|
|
335
|
+
const cColor = roStyles.children_color || '#00e5ff';
|
|
336
|
+
const cSpeed = roStyles.children_dashAnimationSpeed !== undefined ? roStyles.children_dashAnimationSpeed : 1;
|
|
337
|
+
const cMarkerHTML = roStyles.children_markerHeadHTML || `
|
|
338
|
+
<marker id="arrowHeadC" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="4" markerHeight="4" orient="auto-start-reverse">
|
|
339
|
+
<path d="M 0 0 L 10 5 L 0 10 z" fill="${cColor}" />
|
|
340
|
+
</marker>
|
|
341
|
+
`;
|
|
342
|
+
|
|
343
|
+
// 2. CREACIÓN DE CAJAS Y SVGs LOCALES (HIJOS)
|
|
344
|
+
data.blocks.forEach(parentBox => {
|
|
345
|
+
const pCoords = extractBBoxCoords(parentBox, docSize);
|
|
346
|
+
if (!pCoords) return;
|
|
347
|
+
|
|
348
|
+
const group = document.createElement('div');
|
|
349
|
+
group.className = 'bbox-group';
|
|
350
|
+
group.style.position = 'absolute';
|
|
351
|
+
const pW = pCoords.x2 - pCoords.x;
|
|
352
|
+
const pH = pCoords.y2 - pCoords.y;
|
|
353
|
+
const isPercent = true;
|
|
354
|
+
const unit = '%';
|
|
355
|
+
group.style.left = `${pCoords.x}${unit}`;
|
|
356
|
+
group.style.top = `${pCoords.y}${unit}`;
|
|
357
|
+
group.style.width = `${pW}${unit}`;
|
|
358
|
+
group.style.height = `${pH}${unit}`;
|
|
359
|
+
|
|
360
|
+
// --- CAJA PADRE ---
|
|
361
|
+
const pDiv = document.createElement('div');
|
|
362
|
+
pDiv.className = 'bbox bbox-parent';
|
|
363
|
+
pDiv.id = parentBox.id;
|
|
364
|
+
pDiv.style.width = '100%';
|
|
365
|
+
pDiv.style.height = '100%';
|
|
366
|
+
|
|
367
|
+
if (initialSelectedId === parentBox.id) pDiv.classList.add('is-selected');
|
|
368
|
+
|
|
369
|
+
const pColor = parentBox.color || '#ff4b4b';
|
|
370
|
+
pDiv.style.setProperty('--border-color', pColor);
|
|
371
|
+
pDiv.style.setProperty('--bg-normal', hexToRgba(pColor, normalAlpha));
|
|
372
|
+
pDiv.style.setProperty('--bg-hover', hexToRgba(pColor, hoverAlpha));
|
|
373
|
+
|
|
374
|
+
const pTooltip = document.createElement('div');
|
|
375
|
+
pTooltip.className = 'bbox-tooltip';
|
|
376
|
+
pTooltip.style.backgroundColor = pColor;
|
|
377
|
+
pTooltip.textContent = parentBox.type;
|
|
378
|
+
pDiv.appendChild(pTooltip);
|
|
379
|
+
|
|
380
|
+
if (data.set_state_on === 'hover') {
|
|
381
|
+
pDiv.onmouseenter = () => {
|
|
382
|
+
selectBox(pDiv);
|
|
383
|
+
if (typeof setStateValue === 'function') setStateValue('selected_id', parentBox.id);
|
|
384
|
+
};
|
|
385
|
+
pDiv.onmouseleave = () => {
|
|
386
|
+
if (typeof setStateValue === 'function') setStateValue('selected_id', null);
|
|
387
|
+
};
|
|
388
|
+
} else {
|
|
389
|
+
pDiv.onclick = (e) => {
|
|
390
|
+
e.stopPropagation();
|
|
391
|
+
selectBox(pDiv);
|
|
392
|
+
if (typeof setStateValue === 'function') setStateValue('selected_id', parentBox.id);
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
group.appendChild(pDiv);
|
|
397
|
+
|
|
398
|
+
// --- CAJAS HIJAS ---
|
|
399
|
+
if (parentBox.children && parentBox.children.length > 0) {
|
|
400
|
+
parentBox.children.forEach(childBox => {
|
|
401
|
+
const cCoords = extractBBoxCoords(childBox, docSize);
|
|
402
|
+
if (!cCoords) return;
|
|
403
|
+
|
|
404
|
+
const cDiv = document.createElement('div');
|
|
405
|
+
cDiv.className = 'bbox bbox-child';
|
|
406
|
+
cDiv.id = childBox.id;
|
|
407
|
+
|
|
408
|
+
if (initialSelectedId === childBox.id) cDiv.classList.add('is-selected');
|
|
409
|
+
|
|
410
|
+
const relX = ((cCoords.x - pCoords.x) / pW) * 100;
|
|
411
|
+
const relY = ((cCoords.y - pCoords.y) / pH) * 100;
|
|
412
|
+
const cW = cCoords.x2 - cCoords.x;
|
|
413
|
+
const cH = cCoords.y2 - cCoords.y;
|
|
414
|
+
const relW = (cW / pW) * 100;
|
|
415
|
+
const relH = (cH / pH) * 100;
|
|
416
|
+
|
|
417
|
+
cDiv.style.left = `${relX}%`;
|
|
418
|
+
cDiv.style.top = `${relY}%`;
|
|
419
|
+
cDiv.style.width = `${relW}%`;
|
|
420
|
+
cDiv.style.height = `${relH}%`;
|
|
421
|
+
|
|
422
|
+
const cColorBox = childBox.color || '#00cc66';
|
|
423
|
+
cDiv.style.setProperty('--border-color', cColorBox);
|
|
424
|
+
cDiv.style.setProperty('--bg-normal', hexToRgba(cColorBox, childNormalAlpha));
|
|
425
|
+
cDiv.style.setProperty('--bg-hover', hexToRgba(cColorBox, childHoverAlpha));
|
|
426
|
+
|
|
427
|
+
const cTooltip = document.createElement('div');
|
|
428
|
+
cTooltip.className = 'bbox-tooltip';
|
|
429
|
+
cTooltip.style.backgroundColor = cColorBox;
|
|
430
|
+
cTooltip.textContent = childBox.type;
|
|
431
|
+
cDiv.appendChild(cTooltip);
|
|
432
|
+
|
|
433
|
+
if (data.set_state_on === 'hover') {
|
|
434
|
+
cDiv.onmouseenter = () => {
|
|
435
|
+
selectBox(cDiv);
|
|
436
|
+
if (typeof setStateValue === 'function') setStateValue('selected_id', childBox.id);
|
|
437
|
+
};
|
|
438
|
+
cDiv.onmouseleave = () => {
|
|
439
|
+
if (typeof setStateValue === 'function') setStateValue('selected_id', null);
|
|
440
|
+
};
|
|
441
|
+
} else {
|
|
442
|
+
cDiv.onclick = (e) => {
|
|
443
|
+
e.stopPropagation();
|
|
444
|
+
selectBox(cDiv);
|
|
445
|
+
if (typeof setStateValue === 'function') setStateValue('selected_id', childBox.id);
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
group.appendChild(cDiv);
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// --- ORDEN DE LECTURA (SVG LOCAL PARA HIJOS) ---
|
|
454
|
+
if (orderPath && parentBox.children && parentBox.children.length >= 2) {
|
|
455
|
+
const orderedChildren = parentBox.children.map((child, index) => {
|
|
456
|
+
const orderVal = orderPath === true ? index : parseInt(orderPath.split('.').reduce((acc, part) => acc && acc[part], child), 10);
|
|
457
|
+
return { child, orderVal };
|
|
458
|
+
}).filter(item => !isNaN(item.orderVal)).sort((a, b) => a.orderVal - b.orderVal);
|
|
459
|
+
|
|
460
|
+
if (orderedChildren.length > 1) {
|
|
461
|
+
const localSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
462
|
+
localSvg.setAttribute('class', 'children-ro-layer');
|
|
463
|
+
localSvg.style.setProperty('--ro-child-speed', `${15 * cSpeed}s`);
|
|
464
|
+
|
|
465
|
+
const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
|
|
466
|
+
defs.innerHTML = cMarkerHTML;
|
|
467
|
+
localSvg.appendChild(defs);
|
|
468
|
+
|
|
469
|
+
for (let i = 0; i < orderedChildren.length - 1; i++) {
|
|
470
|
+
const current = orderedChildren[i].child;
|
|
471
|
+
const next = orderedChildren[i + 1].child;
|
|
472
|
+
|
|
473
|
+
const curCoords = extractBBoxCoords(current, docSize);
|
|
474
|
+
const nextCoords = extractBBoxCoords(next, docSize);
|
|
475
|
+
if (!curCoords || !nextCoords) continue;
|
|
476
|
+
|
|
477
|
+
// Centro superior absoluto
|
|
478
|
+
const curW = curCoords.x2 - curCoords.x;
|
|
479
|
+
const nextW = nextCoords.x2 - nextCoords.x;
|
|
480
|
+
const absX1 = curCoords.x + (curW / 2);
|
|
481
|
+
const absY1 = curCoords.y;
|
|
482
|
+
const absX2 = nextCoords.x + (nextW / 2);
|
|
483
|
+
const absY2 = nextCoords.y;
|
|
484
|
+
|
|
485
|
+
// Convertimos a porcentajes relativos al grupo padre
|
|
486
|
+
const relX1 = ((absX1 - pCoords.x) / pW) * 100;
|
|
487
|
+
const relY1 = ((absY1 - pCoords.y) / pH) * 100;
|
|
488
|
+
const relX2 = ((absX2 - pCoords.x) / pW) * 100;
|
|
489
|
+
const relY2 = ((absY2 - pCoords.y) / pH) * 100;
|
|
490
|
+
|
|
491
|
+
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
492
|
+
line.setAttribute('x1', `${relX1}%`);
|
|
493
|
+
line.setAttribute('y1', `${relY1}%`);
|
|
494
|
+
line.setAttribute('x2', `${relX2}%`);
|
|
495
|
+
line.setAttribute('y2', `${relY2}%`);
|
|
496
|
+
line.setAttribute('class', 'ro-line-child');
|
|
497
|
+
line.style.stroke = cColor;
|
|
498
|
+
|
|
499
|
+
const matchIdC = cMarkerHTML.match(/id="([^"]+)"/);
|
|
500
|
+
line.setAttribute('marker-end', `url(#${matchIdC ? matchIdC[1] : 'arrowHeadC'})`);
|
|
501
|
+
|
|
502
|
+
localSvg.appendChild(line);
|
|
503
|
+
}
|
|
504
|
+
group.appendChild(localSvg);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
overlay.appendChild(group);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
// 3. MOTOR DE FILTROS
|
|
512
|
+
if (data.filters) {
|
|
513
|
+
const activeFilters = new Set();
|
|
514
|
+
const filterStylesConfig = data.styles && data.styles.filters ? data.styles.filters : {};
|
|
515
|
+
|
|
516
|
+
Object.entries(data.filters).forEach(([filterName, expression]) => {
|
|
517
|
+
const btn = document.createElement('button');
|
|
518
|
+
btn.className = 'filter-btn';
|
|
519
|
+
btn.textContent = filterName;
|
|
520
|
+
btn.style.background = '#f0f0f0';
|
|
521
|
+
btn.style.color = '#333';
|
|
522
|
+
|
|
523
|
+
const customStyle = filterStylesConfig[filterName] || {};
|
|
524
|
+
|
|
525
|
+
btn.onclick = () => {
|
|
526
|
+
const isActive = btn.classList.toggle('is-active');
|
|
527
|
+
|
|
528
|
+
if (isActive) {
|
|
529
|
+
activeFilters.add(expression);
|
|
530
|
+
Object.assign(btn.style, customStyle);
|
|
531
|
+
} else {
|
|
532
|
+
activeFilters.delete(expression);
|
|
533
|
+
btn.style.background = '#f0f0f0';
|
|
534
|
+
btn.style.color = '#333';
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (activeFilters.size === 0) {
|
|
538
|
+
parentElement.querySelectorAll('.bbox').forEach(el => {
|
|
539
|
+
el.classList.remove('is-filtered-out', 'is-ghost', 'is-forced-visible');
|
|
540
|
+
});
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const parsedFilters = Array.from(activeFilters).map(expr => {
|
|
545
|
+
const match = expr.match(/^([\\w.]+)\\s*([=><])\\s*(.*)$/);
|
|
546
|
+
return match ? { path: match[1], operator: match[2], target: match[3] } : null;
|
|
547
|
+
}).filter(Boolean);
|
|
548
|
+
|
|
549
|
+
const evaluateBox = (boxData) => {
|
|
550
|
+
return parsedFilters.some(({ path, operator, target }) => {
|
|
551
|
+
const resolvedValue = path.split('.').reduce((acc, part) => acc && acc[part], boxData);
|
|
552
|
+
const evaluate = (v) => {
|
|
553
|
+
if (v === undefined || v === null) return false;
|
|
554
|
+
if (operator === '=') {
|
|
555
|
+
const regexMatch = target.match(/^\\/(.*)\\/([gimsuy]*)$/);
|
|
556
|
+
if (regexMatch) {
|
|
557
|
+
try { return new RegExp(regexMatch[1], regexMatch[2]).test(String(v)); } catch(e) { return false; }
|
|
558
|
+
}
|
|
559
|
+
return String(v) === String(target);
|
|
560
|
+
}
|
|
561
|
+
if (operator === '>') return Number(v) > Number(target);
|
|
562
|
+
if (operator === '<') return Number(v) < Number(target);
|
|
563
|
+
return false;
|
|
564
|
+
};
|
|
565
|
+
return Array.isArray(resolvedValue) ? resolvedValue.some(evaluate) : evaluate(resolvedValue);
|
|
566
|
+
});
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
data.blocks.forEach(parentBox => {
|
|
570
|
+
const pMatch = evaluateBox(parentBox);
|
|
571
|
+
const matchingChildren = [];
|
|
572
|
+
|
|
573
|
+
if (parentBox.children) {
|
|
574
|
+
parentBox.children.forEach(childBox => {
|
|
575
|
+
if (evaluateBox(childBox)) matchingChildren.push(childBox.id);
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const pEl = document.getElementById(parentBox.id);
|
|
580
|
+
if (!pEl) return;
|
|
581
|
+
|
|
582
|
+
if (pMatch || matchingChildren.length > 0) {
|
|
583
|
+
pEl.classList.remove('is-filtered-out');
|
|
584
|
+
if (!pMatch && matchingChildren.length > 0) pEl.classList.add('is-ghost');
|
|
585
|
+
else pEl.classList.remove('is-ghost');
|
|
586
|
+
} else {
|
|
587
|
+
pEl.classList.add('is-filtered-out');
|
|
588
|
+
pEl.classList.remove('is-ghost');
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (parentBox.children) {
|
|
592
|
+
parentBox.children.forEach(childBox => {
|
|
593
|
+
const cEl = document.getElementById(childBox.id);
|
|
594
|
+
if (!cEl) return;
|
|
595
|
+
if (matchingChildren.includes(childBox.id)) {
|
|
596
|
+
cEl.classList.remove('is-filtered-out');
|
|
597
|
+
cEl.classList.add('is-forced-visible');
|
|
598
|
+
} else {
|
|
599
|
+
cEl.classList.add('is-filtered-out');
|
|
600
|
+
cEl.classList.remove('is-forced-visible');
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
};
|
|
606
|
+
filtersBar.appendChild(btn);
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// 4. ORDEN DE LECTURA (SVG GLOBAL PARA PADRES)
|
|
611
|
+
if (orderPath && svgParents) {
|
|
612
|
+
svgParents.classList.add('is-visible');
|
|
613
|
+
const pColor = roStyles.color || '#ff9800';
|
|
614
|
+
const pSpeed = roStyles.dashAnimationSpeed !== undefined ? roStyles.dashAnimationSpeed : 1;
|
|
615
|
+
svgParents.style.setProperty('--ro-speed', `${20 * pSpeed}s`);
|
|
616
|
+
|
|
617
|
+
const orderedParents = data.blocks.map((box, index) => {
|
|
618
|
+
const orderVal = orderPath === true ? index : parseInt(orderPath.split('.').reduce((acc, part) => acc && acc[part], box), 10);
|
|
619
|
+
return { box, orderVal };
|
|
620
|
+
}).filter(item => !isNaN(item.orderVal)).sort((a, b) => a.orderVal - b.orderVal);
|
|
621
|
+
|
|
622
|
+
if (orderedParents.length > 1) {
|
|
623
|
+
const defsP = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
|
|
624
|
+
defsP.innerHTML = roStyles.markerHeadHTML || `
|
|
625
|
+
<marker id="arrowHeadP" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="5" markerHeight="5" orient="auto-start-reverse">
|
|
626
|
+
<path d="M 0 0 L 10 5 L 0 10 z" fill="${pColor}" />
|
|
627
|
+
</marker>
|
|
628
|
+
`;
|
|
629
|
+
svgParents.appendChild(defsP);
|
|
630
|
+
|
|
631
|
+
for (let i = 0; i < orderedParents.length - 1; i++) {
|
|
632
|
+
const current = orderedParents[i].box;
|
|
633
|
+
const next = orderedParents[i + 1].box;
|
|
634
|
+
|
|
635
|
+
const curCoords = extractBBoxCoords(current, docSize);
|
|
636
|
+
const nextCoords = extractBBoxCoords(next, docSize);
|
|
637
|
+
if (!curCoords || !nextCoords) continue;
|
|
638
|
+
|
|
639
|
+
const curW = curCoords.x2 - curCoords.x;
|
|
640
|
+
const nextW = nextCoords.x2 - nextCoords.x;
|
|
641
|
+
|
|
642
|
+
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
643
|
+
line.setAttribute('x1', `${curCoords.x + (curW / 2)}%`);
|
|
644
|
+
line.setAttribute('y1', `${curCoords.y}%`);
|
|
645
|
+
line.setAttribute('x2', `${nextCoords.x + (nextW / 2)}%`);
|
|
646
|
+
line.setAttribute('y2', `${nextCoords.y}%`);
|
|
647
|
+
line.setAttribute('class', 'ro-line');
|
|
648
|
+
line.style.stroke = pColor;
|
|
649
|
+
|
|
650
|
+
const matchId = (roStyles.markerHeadHTML || '').match(/id="([^"]+)"/);
|
|
651
|
+
line.setAttribute('marker-end', `url(#${matchId ? matchId[1] : 'arrowHeadP'})`);
|
|
652
|
+
svgParents.appendChild(line);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
"""
|
|
658
|
+
|
|
659
|
+
_DOCUMENT_BLOCKS_COMPONENT = create_component(
|
|
660
|
+
"document_blocks",
|
|
661
|
+
html=___HTML,
|
|
662
|
+
css=__CSS,
|
|
663
|
+
js=__JS,
|
|
664
|
+
isolate_styles=False,
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
ImageLike = Union[
|
|
668
|
+
str,
|
|
669
|
+
Path,
|
|
670
|
+
Image.Image
|
|
671
|
+
]
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def _to_image_src(image: ImageLike) -> str:
|
|
675
|
+
"""Normalize supported image inputs into an HTML img.src-compatible string."""
|
|
676
|
+
if isinstance(image, str):
|
|
677
|
+
# Accept URL, relative path served by app, or already-encoded data URL.
|
|
678
|
+
return image
|
|
679
|
+
|
|
680
|
+
if isinstance(image, Path):
|
|
681
|
+
mime_type = mimetypes.guess_type(image.name)[0] or "image/png"
|
|
682
|
+
encoded = base64.b64encode(image.read_bytes()).decode("ascii")
|
|
683
|
+
return f"data:{mime_type};base64,{encoded}"
|
|
684
|
+
|
|
685
|
+
if isinstance(image, Image.Image):
|
|
686
|
+
img_format = (image.format or "PNG").upper()
|
|
687
|
+
mime_type = Image.MIME.get(img_format, f"image/{img_format.lower()}")
|
|
688
|
+
buffer = BytesIO()
|
|
689
|
+
image.save(buffer, format=img_format)
|
|
690
|
+
encoded = base64.b64encode(buffer.getvalue()).decode("ascii")
|
|
691
|
+
return f"data:{mime_type};base64,{encoded}"
|
|
692
|
+
|
|
693
|
+
raise ValueError("Unsupported image type")
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
def st_document_blocks(
|
|
697
|
+
image: ImageLike,
|
|
698
|
+
filters: dict,
|
|
699
|
+
blocks: list,
|
|
700
|
+
document_size: Optional[tuple[float | int, float | int]] = None,
|
|
701
|
+
reading_order: Optional[str | bool] = None,
|
|
702
|
+
set_state_on: Literal["hover", "click"] = "click",
|
|
703
|
+
default_selected: Optional[str | int] = None,
|
|
704
|
+
custom_styles: Optional[dict] = None,
|
|
705
|
+
key: Optional[str] = None, width: Width = "stretch", height: Height = "content"
|
|
706
|
+
) -> Optional[str|int]:
|
|
707
|
+
image_url = _to_image_src(image)
|
|
708
|
+
|
|
709
|
+
doc_size = None
|
|
710
|
+
if document_size:
|
|
711
|
+
doc_size = {"width": document_size[0], "height": document_size[1]}
|
|
712
|
+
|
|
713
|
+
data = {
|
|
714
|
+
"imageUrl": image_url,
|
|
715
|
+
"filters": filters,
|
|
716
|
+
"reading_order": reading_order,
|
|
717
|
+
"blocks": blocks,
|
|
718
|
+
"document_size": doc_size,
|
|
719
|
+
"set_state_on": set_state_on,
|
|
720
|
+
"selected_id": default_selected,
|
|
721
|
+
"styles": custom_styles,
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
result = _DOCUMENT_BLOCKS_COMPONENT(
|
|
725
|
+
key=key,
|
|
726
|
+
data=data,
|
|
727
|
+
default={'selected_id': default_selected},
|
|
728
|
+
on_selected_id_change=lambda: None,
|
|
729
|
+
width=width,
|
|
730
|
+
height=height,
|
|
731
|
+
)
|
|
732
|
+
|
|
733
|
+
return result.get("selected_id")
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
__all__ = ["st_document_blocks"]
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Generator, Any, Optional
|
|
4
|
+
|
|
5
|
+
import streamlit as st
|
|
6
|
+
from streamlit.components.v2 import component as create_component
|
|
7
|
+
from streamlit.elements.lib.layout_utils import Width, Height, HorizontalAlignment, VerticalAlignment, Gap
|
|
8
|
+
|
|
9
|
+
___HTML = """"""
|
|
10
|
+
__CSS = """
|
|
11
|
+
.scroll-select {
|
|
12
|
+
animation: scroll-select-animation 1s ease-in-out;
|
|
13
|
+
}
|
|
14
|
+
@keyframes scroll-select-animation {
|
|
15
|
+
0% { background-color: color-mix(in srgb, var(--scroll-bg-color) 50%, transparent); }
|
|
16
|
+
100% { background-color: transparent; }
|
|
17
|
+
}
|
|
18
|
+
"""
|
|
19
|
+
__JS = """
|
|
20
|
+
export default function(component) {
|
|
21
|
+
const { data } = component;
|
|
22
|
+
document.querySelector(`.st-key-${data.sourceContainerKey}-${data.targetContainerKey}-sync`).style.display = "none";
|
|
23
|
+
setTimeout(function() {
|
|
24
|
+
const sourceContainer = document.querySelector(data.sourceContainerSelector);
|
|
25
|
+
const targetContainer = document.querySelector(data.targetContainerSelector);
|
|
26
|
+
const scrollTo = document.querySelector(data.scrollToSelector);
|
|
27
|
+
|
|
28
|
+
const syncSize = () => {
|
|
29
|
+
const sourceHeight = sourceContainer.clientHeight;
|
|
30
|
+
const sourceWidth = sourceContainer.clientWidth;
|
|
31
|
+
|
|
32
|
+
if (data.mimicVertical) {
|
|
33
|
+
targetContainer.style.height = `${sourceHeight}px`;
|
|
34
|
+
targetContainer.parentElement.style.height = `${sourceHeight}px`;
|
|
35
|
+
}
|
|
36
|
+
if (data.mimicHorizontal) {
|
|
37
|
+
targetContainer.style.width = `${sourceWidth}px`;
|
|
38
|
+
targetContainer.parentElement.style.width = `${sourceWidth}px`;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Set overflow
|
|
43
|
+
if (data.mimicVertical && !data.mimicHorizontal) {
|
|
44
|
+
targetContainer.style.overflowY = 'auto';
|
|
45
|
+
}
|
|
46
|
+
if (data.mimicHorizontal && !data.mimicVertical) {
|
|
47
|
+
targetContainer.style.overflowX = 'auto';
|
|
48
|
+
}
|
|
49
|
+
if (data.mimicVertical && data.mimicHorizontal) {
|
|
50
|
+
targetContainer.style.overflow = 'auto';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
syncSize();
|
|
54
|
+
|
|
55
|
+
// Observer pattern: sync on resize
|
|
56
|
+
const observer = new ResizeObserver(syncSize);
|
|
57
|
+
observer.observe(sourceContainer);
|
|
58
|
+
window.addEventListener('resize', syncSize);
|
|
59
|
+
|
|
60
|
+
if (scrollTo) {
|
|
61
|
+
const previousActive = document.activeElement;
|
|
62
|
+
targetContainer.focus({ preventScroll: true });
|
|
63
|
+
scrollTo.scrollIntoView({
|
|
64
|
+
block: 'center', // Centra verticalmente respecto al padre
|
|
65
|
+
inline: 'center', // Centra horizontalmente si aplica
|
|
66
|
+
behavior: 'smooth'
|
|
67
|
+
});
|
|
68
|
+
if (data.flashBrightScrollTarget) {
|
|
69
|
+
if (data.highlightItemClass) {
|
|
70
|
+
document.querySelectorAll(`.${data.highlightItemClass}`).forEach(el => {
|
|
71
|
+
el.classList.remove(data.highlightItemClass);
|
|
72
|
+
});
|
|
73
|
+
scrollTo.classList.add(data.highlightItemClass);
|
|
74
|
+
}
|
|
75
|
+
scrollTo.style.setProperty("--scroll-bg-color", data.flashBrightScrollBg || "yellow")
|
|
76
|
+
scrollTo.classList.add("scroll-select");
|
|
77
|
+
setTimeout(function() {
|
|
78
|
+
scrollTo.classList.remove("scroll-select");
|
|
79
|
+
}, 1000);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
previousActive?.focus({ preventScroll: true });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
}, 0);
|
|
86
|
+
}
|
|
87
|
+
"""
|
|
88
|
+
_DYNAMIC_CONTAINER_COMPONENT = create_component(
|
|
89
|
+
"dynamic_container_script",
|
|
90
|
+
# html=___HTML,
|
|
91
|
+
css=__CSS,
|
|
92
|
+
js=__JS,
|
|
93
|
+
isolate_styles=False,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class DynamicContainerDataInput:
|
|
98
|
+
sourceContainerSelector: str
|
|
99
|
+
targetContainerSelector: str
|
|
100
|
+
sourceContainerKey: str
|
|
101
|
+
targetContainerKey: str
|
|
102
|
+
mimicVertical: bool
|
|
103
|
+
mimicHorizontal: bool
|
|
104
|
+
|
|
105
|
+
scrollToSelector: Optional[str] = None
|
|
106
|
+
flashBrightScrollTarget: Optional[bool] = None
|
|
107
|
+
flashBrightScrollBg: Optional[str] = None
|
|
108
|
+
highlightItemClass: Optional[str] = None
|
|
109
|
+
|
|
110
|
+
def to_dict(self):
|
|
111
|
+
return self.__dict__
|
|
112
|
+
|
|
113
|
+
@contextlib.contextmanager
|
|
114
|
+
def st_dynamic_container(
|
|
115
|
+
*,
|
|
116
|
+
container_source_key: str,
|
|
117
|
+
key: str,
|
|
118
|
+
mimic_vertical: bool = True,
|
|
119
|
+
mimic_horizontal: bool = False,
|
|
120
|
+
border: bool | None = None,
|
|
121
|
+
width: Width = "stretch",
|
|
122
|
+
height: Height = "content",
|
|
123
|
+
horizontal: bool = False,
|
|
124
|
+
horizontal_alignment: HorizontalAlignment = "left",
|
|
125
|
+
vertical_alignment: VerticalAlignment = "top",
|
|
126
|
+
gap: Gap | None = "small",
|
|
127
|
+
autoscroll: bool | None = None,
|
|
128
|
+
) -> Generator[DynamicContainerDataInput, Any, None]:
|
|
129
|
+
data = DynamicContainerDataInput(
|
|
130
|
+
sourceContainerSelector=f".st-key-{container_source_key}",
|
|
131
|
+
targetContainerSelector=f".st-key-{key}",
|
|
132
|
+
sourceContainerKey=container_source_key,
|
|
133
|
+
targetContainerKey=key,
|
|
134
|
+
mimicVertical=mimic_vertical,
|
|
135
|
+
mimicHorizontal=mimic_horizontal,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
with st.container(
|
|
139
|
+
key=key,
|
|
140
|
+
border=border,
|
|
141
|
+
width=width,
|
|
142
|
+
height=height,
|
|
143
|
+
horizontal=horizontal,
|
|
144
|
+
horizontal_alignment=horizontal_alignment,
|
|
145
|
+
vertical_alignment=vertical_alignment,
|
|
146
|
+
gap=gap,
|
|
147
|
+
autoscroll=autoscroll,
|
|
148
|
+
):
|
|
149
|
+
yield data
|
|
150
|
+
_DYNAMIC_CONTAINER_COMPONENT(
|
|
151
|
+
key=f"{container_source_key}-{key}-sync",
|
|
152
|
+
data=data.to_dict()
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
__all__ = ["st_dynamic_container"]
|
{streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins.egg-info/SOURCES.txt
RENAMED
|
@@ -9,6 +9,7 @@ streamlit_plugins.egg-info/requires.txt
|
|
|
9
9
|
streamlit_plugins.egg-info/top_level.txt
|
|
10
10
|
streamlit_plugins/components/__init__.py
|
|
11
11
|
streamlit_plugins/components/annotated_text/__init__.py
|
|
12
|
+
streamlit_plugins/components/document_blocks/__init__.py
|
|
12
13
|
streamlit_plugins/components/input_chat_advance/__init__.py
|
|
13
14
|
streamlit_plugins/components/label_studio/__init__.py
|
|
14
15
|
streamlit_plugins/components/label_studio/frontend/build/asset-manifest.json
|
|
@@ -77,6 +78,7 @@ streamlit_plugins/components/theme_changer/inject_script.py
|
|
|
77
78
|
streamlit_plugins/components/theme_changer/frontend/build/CrossOriginInterface.js
|
|
78
79
|
streamlit_plugins/extension/__init__.py
|
|
79
80
|
streamlit_plugins/extension/button_group/__init__.py
|
|
81
|
+
streamlit_plugins/extension/dynamic_container/__init__.py
|
|
80
82
|
streamlit_plugins/framework/__init__.py
|
|
81
83
|
streamlit_plugins/framework/multilit/__init__.py
|
|
82
84
|
streamlit_plugins/framework/multilit/app_wrapper.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/extension/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/framework/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/framework/reactlit/lab.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins.egg-info/requires.txt
RENAMED
|
File without changes
|
{streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins.egg-info/top_level.txt
RENAMED
|
File without changes
|