g360-cli 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/assets/signature/g360_flet/__init__.py +5 -0
- package/src/assets/signature/g360_flet/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/assets/signature/g360_flet/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/assets/signature/g360_flet/__pycache__/g360_signature.cpython-312.pyc +0 -0
- package/src/assets/signature/g360_flet/__pycache__/g360_signature.cpython-314.pyc +0 -0
- package/src/assets/signature/g360_flet/g360_signature.py +224 -0
- package/src/assets/signature/index.js +130 -0
- package/src/assets/templates/python-flet/src/core/g360_theme.py +21 -14
- package/src/assets/templates/web-pwa/index.html +3 -0
- package/src/cli.js +6 -1
- package/src/commands/signature.js +252 -55
package/package.json
CHANGED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""
|
|
2
|
+
g360-signature para Flet
|
|
3
|
+
Widget reutilizable del isotipo G360 (3 puntos + chevron) para apps Flet.
|
|
4
|
+
Soporta tema automatico (claro/oscuro) igual que la version web.
|
|
5
|
+
|
|
6
|
+
Uso:
|
|
7
|
+
from g360_flet.g360_signature import G360Signature
|
|
8
|
+
|
|
9
|
+
page.add(G360Signature(mode="own"))
|
|
10
|
+
page.add(G360Signature(mode="powered", version="3.1"))
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import flet as ft
|
|
14
|
+
|
|
15
|
+
# Colores G360
|
|
16
|
+
G360_GREEN = "#00d084"
|
|
17
|
+
G360_GRAY_DARK = "#94a3b8" # dark mode
|
|
18
|
+
G360_GRAY_LIGHT = "#64748b" # light mode
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class G360Signature(ft.Row):
|
|
22
|
+
"""
|
|
23
|
+
Componente de branding G360 para Flet.
|
|
24
|
+
|
|
25
|
+
Detecta automaticamente el tema de la pagina (light/dark)
|
|
26
|
+
y ajusta los colores igual que el Web Component original.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
mode: str = "own",
|
|
32
|
+
version: str | None = None,
|
|
33
|
+
opacity: float = 0.4,
|
|
34
|
+
grayscale: bool = False,
|
|
35
|
+
spacing: int = 4,
|
|
36
|
+
on_hover=None,
|
|
37
|
+
**kwargs,
|
|
38
|
+
):
|
|
39
|
+
super().__init__(**kwargs)
|
|
40
|
+
|
|
41
|
+
self._base_opacity = opacity
|
|
42
|
+
self._grayscale = grayscale
|
|
43
|
+
self.spacing = spacing
|
|
44
|
+
self.vertical_alignment = ft.CrossAxisAlignment.CENTER
|
|
45
|
+
|
|
46
|
+
self._mode = "own"
|
|
47
|
+
self._version = None
|
|
48
|
+
|
|
49
|
+
self.mode = mode
|
|
50
|
+
self.version = version
|
|
51
|
+
|
|
52
|
+
if on_hover is None:
|
|
53
|
+
self.on_hover = self._default_on_hover
|
|
54
|
+
else:
|
|
55
|
+
self.on_hover = on_hover
|
|
56
|
+
|
|
57
|
+
self.opacity = opacity
|
|
58
|
+
|
|
59
|
+
def _default_on_hover(self, e: ft.HoverEvent):
|
|
60
|
+
self.opacity = 1.0 if e.data == "true" else self._base_opacity
|
|
61
|
+
self.update()
|
|
62
|
+
|
|
63
|
+
def _is_dark(self) -> bool:
|
|
64
|
+
"""Detecta si el tema actual es oscuro."""
|
|
65
|
+
if not self._is_mounted():
|
|
66
|
+
return False
|
|
67
|
+
mode = self.page.theme_mode
|
|
68
|
+
if mode == ft.ThemeMode.DARK:
|
|
69
|
+
return True
|
|
70
|
+
if mode == ft.ThemeMode.LIGHT:
|
|
71
|
+
return False
|
|
72
|
+
# SYSTEM: detectar por color de fondo
|
|
73
|
+
bg = self.page.bgcolor
|
|
74
|
+
if bg and isinstance(bg, str):
|
|
75
|
+
bg_lower = bg.lower()
|
|
76
|
+
if bg_lower.startswith("#"):
|
|
77
|
+
r = int(bg_lower[1:3], 16)
|
|
78
|
+
g = int(bg_lower[3:5], 16)
|
|
79
|
+
b = int(bg_lower[5:7], 16)
|
|
80
|
+
lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255
|
|
81
|
+
return lum < 0.5
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
def _get_colors(self):
|
|
85
|
+
"""Retorna colores segun el tema actual."""
|
|
86
|
+
if self._grayscale:
|
|
87
|
+
return G360_GRAY_DARK, G360_GRAY_DARK
|
|
88
|
+
if self._is_dark():
|
|
89
|
+
return G360_GREEN, G360_GRAY_DARK
|
|
90
|
+
return G360_GREEN, G360_GRAY_LIGHT
|
|
91
|
+
|
|
92
|
+
def _build(self):
|
|
93
|
+
green, gray = self._get_colors()
|
|
94
|
+
|
|
95
|
+
# 3 puntos verticales
|
|
96
|
+
dots = ft.Column(
|
|
97
|
+
controls=[
|
|
98
|
+
ft.Container(width=4, height=4, border_radius=2, bgcolor=gray),
|
|
99
|
+
ft.Container(width=4, height=4, border_radius=2, bgcolor=green),
|
|
100
|
+
ft.Container(width=4, height=4, border_radius=2, bgcolor=gray),
|
|
101
|
+
],
|
|
102
|
+
spacing=2,
|
|
103
|
+
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Chevron ">"
|
|
107
|
+
chevron = ft.Text(
|
|
108
|
+
">",
|
|
109
|
+
size=14,
|
|
110
|
+
color=green,
|
|
111
|
+
font_family="Consolas",
|
|
112
|
+
selectable=False,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
isotype = ft.Row(
|
|
116
|
+
controls=[dots, chevron],
|
|
117
|
+
spacing=1,
|
|
118
|
+
vertical_alignment=ft.CrossAxisAlignment.CENTER,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Texto principal
|
|
122
|
+
if self.mode == "own":
|
|
123
|
+
text_controls = [
|
|
124
|
+
ft.Text("G360", size=12, color=green, weight=ft.FontWeight.BOLD,
|
|
125
|
+
font_family="Consolas", selectable=False),
|
|
126
|
+
ft.Text(" by ccusi", size=12, color=gray,
|
|
127
|
+
font_family="Consolas", selectable=False),
|
|
128
|
+
]
|
|
129
|
+
else:
|
|
130
|
+
text_controls = [
|
|
131
|
+
ft.Text("powered by ", size=12, color=gray,
|
|
132
|
+
font_family="Consolas", selectable=False),
|
|
133
|
+
ft.Text("G360", size=12, color=green, weight=ft.FontWeight.BOLD,
|
|
134
|
+
font_family="Consolas", selectable=False),
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
if self.version:
|
|
138
|
+
text_controls.append(
|
|
139
|
+
ft.Text(f" v{self.version}", size=12, color=gray, opacity=0.7,
|
|
140
|
+
font_family="Consolas", selectable=False)
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
text_row = ft.Row(
|
|
144
|
+
controls=text_controls,
|
|
145
|
+
spacing=0,
|
|
146
|
+
vertical_alignment=ft.CrossAxisAlignment.CENTER,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
self.controls = [isotype, text_row]
|
|
150
|
+
|
|
151
|
+
# -- Propiedades --
|
|
152
|
+
|
|
153
|
+
def _is_mounted(self) -> bool:
|
|
154
|
+
"""Verifica si el widget ya esta montado en la pagina."""
|
|
155
|
+
try:
|
|
156
|
+
return self.page is not None
|
|
157
|
+
except RuntimeError:
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def mode(self) -> str:
|
|
162
|
+
return self._mode
|
|
163
|
+
|
|
164
|
+
@mode.setter
|
|
165
|
+
def mode(self, value: str):
|
|
166
|
+
if value not in ("own", "powered"):
|
|
167
|
+
raise ValueError(f"mode debe ser 'own' o 'powered', recibido: '{value}'")
|
|
168
|
+
self._mode = value
|
|
169
|
+
if self._is_mounted():
|
|
170
|
+
self._build()
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def version(self) -> str | None:
|
|
174
|
+
return self._version
|
|
175
|
+
|
|
176
|
+
@version.setter
|
|
177
|
+
def version(self, value: str | None):
|
|
178
|
+
self._version = value
|
|
179
|
+
if self._is_mounted():
|
|
180
|
+
self._build()
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
def grayscale(self) -> bool:
|
|
184
|
+
return self._grayscale
|
|
185
|
+
|
|
186
|
+
@grayscale.setter
|
|
187
|
+
def grayscale(self, value: bool):
|
|
188
|
+
self._grayscale = value
|
|
189
|
+
if self._is_mounted():
|
|
190
|
+
self._build()
|
|
191
|
+
|
|
192
|
+
def did_mount(self):
|
|
193
|
+
"""Se ejecuta cuando el widget se monta en la pagina."""
|
|
194
|
+
self._build()
|
|
195
|
+
self.update()
|
|
196
|
+
|
|
197
|
+
def update(self):
|
|
198
|
+
"""Actualiza colores segun tema y refresca."""
|
|
199
|
+
if self._is_mounted():
|
|
200
|
+
self._build()
|
|
201
|
+
super().update()
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# ============================================================
|
|
205
|
+
# Funciones de conveniencia
|
|
206
|
+
# ============================================================
|
|
207
|
+
|
|
208
|
+
def g360_own(version: str | None = None, **kwargs) -> G360Signature:
|
|
209
|
+
"""Isotipo modo propio: G360 by ccusi"""
|
|
210
|
+
return G360Signature(mode="own", version=version, **kwargs)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def g360_powered(version: str | None = None, **kwargs) -> G360Signature:
|
|
214
|
+
"""Isotipo modo powered: powered by G360"""
|
|
215
|
+
return G360Signature(mode="powered", version=version, **kwargs)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def g360_footer(version: str | None = None, **kwargs) -> ft.Container:
|
|
219
|
+
"""Footer completo con el isotipo G360."""
|
|
220
|
+
return ft.Container(
|
|
221
|
+
content=G360Signature(mode="powered", version=version, opacity=0.6, **kwargs),
|
|
222
|
+
padding=ft.padding.Padding(0, 0, 0, 16),
|
|
223
|
+
alignment=ft.alignment.Alignment(0, 1),
|
|
224
|
+
)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* g360-signature v1.1.0
|
|
3
|
+
* Web component para branding G360 - Sin Shadow DOM
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const G360_SIGNATURE_CSS = `
|
|
7
|
+
.g360-signature {
|
|
8
|
+
display: inline-flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
gap: 4px;
|
|
11
|
+
height: 18px;
|
|
12
|
+
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
|
13
|
+
font-size: 12px;
|
|
14
|
+
line-height: 1;
|
|
15
|
+
opacity: 0.4;
|
|
16
|
+
transition: opacity 0.3s ease;
|
|
17
|
+
cursor: default;
|
|
18
|
+
--g360-green: #00d084;
|
|
19
|
+
--g360-gray: #64748b;
|
|
20
|
+
}
|
|
21
|
+
.g360-signature:hover {
|
|
22
|
+
opacity: 1;
|
|
23
|
+
}
|
|
24
|
+
.g360-signature .g360-iw {
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
gap: 1px;
|
|
28
|
+
height: 100%;
|
|
29
|
+
}
|
|
30
|
+
.g360-signature .g360-iso {
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-direction: column;
|
|
33
|
+
justify-content: center;
|
|
34
|
+
gap: 2px;
|
|
35
|
+
height: 100%;
|
|
36
|
+
}
|
|
37
|
+
.g360-signature .g360-d {
|
|
38
|
+
width: 4px;
|
|
39
|
+
height: 4px;
|
|
40
|
+
border-radius: 50%;
|
|
41
|
+
}
|
|
42
|
+
.g360-signature .g360-dt { background: var(--g360-gray); }
|
|
43
|
+
.g360-signature .g360-dm { background: var(--g360-green); }
|
|
44
|
+
.g360-signature .g360-db { background: var(--g360-gray); }
|
|
45
|
+
.g360-signature .g360-ch {
|
|
46
|
+
color: var(--g360-green);
|
|
47
|
+
width: 20px;
|
|
48
|
+
height: 20px;
|
|
49
|
+
}
|
|
50
|
+
.g360-signature .g360-t {
|
|
51
|
+
color: var(--g360-gray);
|
|
52
|
+
letter-spacing: 0.5px;
|
|
53
|
+
}
|
|
54
|
+
.g360-signature .g360-v {
|
|
55
|
+
color: var(--g360-gray);
|
|
56
|
+
opacity: 0.7;
|
|
57
|
+
}
|
|
58
|
+
.g360-signature .g360-s {
|
|
59
|
+
color: var(--g360-gray);
|
|
60
|
+
opacity: 0.5;
|
|
61
|
+
}
|
|
62
|
+
@media (prefers-color-scheme: dark) {
|
|
63
|
+
.g360-signature { --g360-gray: #94a3b8; }
|
|
64
|
+
}
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
class G360Signature extends HTMLElement {
|
|
68
|
+
static get observedAttributes() {
|
|
69
|
+
return ['mode', 'version'];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
constructor() {
|
|
73
|
+
super();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
connectedCallback() {
|
|
77
|
+
this._injectStyles();
|
|
78
|
+
this.render();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
attributeChangedCallback() {
|
|
82
|
+
this.render();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
_injectStyles() {
|
|
86
|
+
if (document.getElementById('g360-signature-css')) return;
|
|
87
|
+
const style = document.createElement('style');
|
|
88
|
+
style.id = 'g360-signature-css';
|
|
89
|
+
style.textContent = G360_SIGNATURE_CSS;
|
|
90
|
+
document.head.appendChild(style);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
get mode() {
|
|
94
|
+
return this.getAttribute('mode') || 'own';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
get version() {
|
|
98
|
+
return this.getAttribute('version') || '';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
render() {
|
|
102
|
+
const isOwn = this.mode === 'own';
|
|
103
|
+
const mainText = isOwn ? 'G360 by ccusi' : 'powered by G360';
|
|
104
|
+
|
|
105
|
+
const versionHtml = this.version
|
|
106
|
+
? `<span class="g360-s">></span><span class="g360-v">${this.version}</span>`
|
|
107
|
+
: '';
|
|
108
|
+
|
|
109
|
+
this.innerHTML = `
|
|
110
|
+
<span class="g360-signature">
|
|
111
|
+
<span class="g360-iw">
|
|
112
|
+
<span class="g360-iso">
|
|
113
|
+
<span class="g360-d g360-dt"></span>
|
|
114
|
+
<span class="g360-d g360-dm"></span>
|
|
115
|
+
<span class="g360-d g360-db"></span>
|
|
116
|
+
</span>
|
|
117
|
+
<svg class="g360-ch" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
118
|
+
<polyline points="6 4 14 10 6 16"></polyline>
|
|
119
|
+
</svg>
|
|
120
|
+
</span>
|
|
121
|
+
<span class="g360-t">${mainText}</span>
|
|
122
|
+
${versionHtml}
|
|
123
|
+
</span>
|
|
124
|
+
`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!customElements.get('g360-signature')) {
|
|
129
|
+
customElements.define('g360-signature', G360Signature);
|
|
130
|
+
}
|
|
@@ -156,20 +156,27 @@ class G360Theme:
|
|
|
156
156
|
|
|
157
157
|
def footer_signature(self):
|
|
158
158
|
import flet as ft
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
159
|
+
try:
|
|
160
|
+
from core.components.g360_signature import g360_footer
|
|
161
|
+
mode = self.config.get("signature", {}).get("mode", "powered")
|
|
162
|
+
version = self.config.get("signature", {}).get("version", None)
|
|
163
|
+
return g360_footer(version=version)
|
|
164
|
+
except ImportError:
|
|
165
|
+
# Fallback si g360_signature no esta instalado
|
|
166
|
+
mode = self.config.get("signature", {}).get("mode", "powered")
|
|
167
|
+
text = self.config.get("signature", {}).get("text", "powered by G360")
|
|
168
|
+
return ft.Container(
|
|
169
|
+
content=ft.Row(
|
|
170
|
+
[
|
|
171
|
+
self.logo_component(height=16),
|
|
172
|
+
ft.Container(width=8),
|
|
173
|
+
ft.Text(text, size=10, color=self.muted),
|
|
174
|
+
],
|
|
175
|
+
alignment=ft.MainAxisAlignment.CENTER,
|
|
176
|
+
),
|
|
177
|
+
padding=8,
|
|
178
|
+
bgcolor=self.surface,
|
|
179
|
+
)
|
|
173
180
|
|
|
174
181
|
@property
|
|
175
182
|
def as_dict(self):
|
|
@@ -14,5 +14,8 @@
|
|
|
14
14
|
<body>
|
|
15
15
|
<div id="root"></div>
|
|
16
16
|
<script type="module" src="/src/main.jsx"></script>
|
|
17
|
+
<script src="https://unpkg.com/g360-signature@latest/index.js"></script>
|
|
18
|
+
<!-- Firma Oficial G360 -->
|
|
19
|
+
<g360-signature mode="powered" style="position: fixed; bottom: 16px; right: 16px; z-index: 99999;"></g360-signature>
|
|
17
20
|
</body>
|
|
18
21
|
</html>
|
package/src/cli.js
CHANGED
|
@@ -109,9 +109,14 @@ program
|
|
|
109
109
|
|
|
110
110
|
program
|
|
111
111
|
.command('signature')
|
|
112
|
-
.argument('<command>', 'Command to execute
|
|
112
|
+
.argument('<command>', 'Command to execute: install, positions')
|
|
113
|
+
.description('Install g360-signature branding component')
|
|
113
114
|
.option('-p, --path <path>', 'Target project path', '.')
|
|
114
115
|
.option('--force', 'Force reinstall if already exists')
|
|
116
|
+
.option('-m, --mode <mode>', 'Signature mode: own or powered', 'powered')
|
|
117
|
+
.option('-v, --version <version>', 'Version to display')
|
|
118
|
+
.option('--position <position>', 'Signature position: bottom-right, bottom-left, bottom-center, footer-right, footer-left', 'bottom-right')
|
|
119
|
+
.option('-i, --interactive', 'Interactive mode with guided suggestions')
|
|
115
120
|
.action(signature);
|
|
116
121
|
|
|
117
122
|
program.parse();
|
|
@@ -1,55 +1,252 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import fs from 'fs-extra';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
|
|
6
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
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
|
-
}
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const SIGNATURE_ASSETS = path.join(__dirname, '..', 'assets', 'signature');
|
|
8
|
+
|
|
9
|
+
const POSITIONS = {
|
|
10
|
+
'bottom-right': 'position: fixed; bottom: 16px; right: 16px; z-index: 99999;',
|
|
11
|
+
'bottom-left': 'position: fixed; bottom: 16px; left: 16px; z-index: 99999;',
|
|
12
|
+
'bottom-center': 'position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%); z-index: 99999;',
|
|
13
|
+
'footer-right': 'position: static; float: right;',
|
|
14
|
+
'footer-left': 'position: static; float: left;',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const FLET_POSITIONS = {
|
|
18
|
+
'sidebar': 'En el sidebar como footer',
|
|
19
|
+
'footer': 'En el footer de la pagina',
|
|
20
|
+
'fixed-bottom': 'Fijo abajo a la derecha',
|
|
21
|
+
'header': 'En el header/cabecera',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export async function signature(command, options) {
|
|
25
|
+
const {
|
|
26
|
+
path: targetPath = '.',
|
|
27
|
+
force = false,
|
|
28
|
+
mode = 'powered',
|
|
29
|
+
version = null,
|
|
30
|
+
position = 'bottom-right',
|
|
31
|
+
interactive = false,
|
|
32
|
+
} = options;
|
|
33
|
+
|
|
34
|
+
if (command === 'install') {
|
|
35
|
+
console.log(chalk.bold.cyan('\n🔖 Instalando g360-signature\n'));
|
|
36
|
+
|
|
37
|
+
const targetDir = path.resolve(process.cwd(), targetPath);
|
|
38
|
+
const projectType = detectProjectType(targetDir);
|
|
39
|
+
|
|
40
|
+
if (!projectType) {
|
|
41
|
+
console.error(chalk.red('❌ No se detecto un proyecto web o Flet en el directorio actual'));
|
|
42
|
+
console.log(chalk.gray('Proyectos soportados: web (HTML), Flet (Python)'));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Modo interactivo: guiar al usuario
|
|
47
|
+
if (interactive) {
|
|
48
|
+
const selectedPosition = await interactivePosition(projectType);
|
|
49
|
+
if (selectedPosition) {
|
|
50
|
+
options.position = selectedPosition;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (projectType === 'flet') {
|
|
55
|
+
await installFlet(targetDir, { force, mode, version, position: options.position });
|
|
56
|
+
} else if (projectType === 'web') {
|
|
57
|
+
await installWeb(targetDir, { force, mode, version, position: options.position });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(chalk.green('\n✅ g360-signature instalado exitosamente!'));
|
|
61
|
+
showUsageTips(projectType, options.position);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (command === 'positions') {
|
|
65
|
+
showPositions();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function detectProjectType(dir) {
|
|
70
|
+
const pyprojectPath = path.join(dir, 'pyproject.toml');
|
|
71
|
+
if (fs.existsSync(pyprojectPath)) {
|
|
72
|
+
const content = fs.readFileSync(pyprojectPath, 'utf8');
|
|
73
|
+
if (content.includes('flet')) return 'flet';
|
|
74
|
+
}
|
|
75
|
+
const mainPy = path.join(dir, 'src', 'main.py');
|
|
76
|
+
if (fs.existsSync(mainPy)) {
|
|
77
|
+
const content = fs.readFileSync(mainPy, 'utf8');
|
|
78
|
+
if (content.includes('flet')) return 'flet';
|
|
79
|
+
}
|
|
80
|
+
const indexHtml = path.join(dir, 'index.html');
|
|
81
|
+
if (fs.existsSync(indexHtml)) return 'web';
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function showPositions() {
|
|
86
|
+
console.log(chalk.bold.cyan('\n📍 Posiciones disponibles para Web:\n'));
|
|
87
|
+
Object.entries(POSITIONS).forEach(([key, value]) => {
|
|
88
|
+
console.log(chalk.white(` ${key.padEnd(18)} ${chalk.gray(value)}`));
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
console.log(chalk.bold.cyan('\n📍 Posiciones disponibles para Flet:\n'));
|
|
92
|
+
Object.entries(FLET_POSITIONS).forEach(([key, value]) => {
|
|
93
|
+
console.log(chalk.white(` ${key.padEnd(18)} ${chalk.gray(value)}`));
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
console.log(chalk.gray('\nEjemplo: g360 signature install --position bottom-left\n'));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function interactivePosition(projectType) {
|
|
100
|
+
console.log(chalk.bold.cyan('\n📍 Selecciona la posicion de la firma:\n'));
|
|
101
|
+
|
|
102
|
+
const options = projectType === 'flet'
|
|
103
|
+
? Object.entries(FLET_POSITIONS)
|
|
104
|
+
: Object.entries(POSITIONS);
|
|
105
|
+
|
|
106
|
+
options.forEach(([key, value], index) => {
|
|
107
|
+
console.log(chalk.white(` ${index + 1}. ${key.padEnd(18)} ${chalk.gray(value)}`));
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
console.log(chalk.gray('\n Presiona Enter para usar la posicion por defecto (bottom-right)'));
|
|
111
|
+
console.log(chalk.gray(' O escribe el nombre de la posicion\n'));
|
|
112
|
+
|
|
113
|
+
// En modo no-interactivo, retornar default
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function showUsageTips(projectType, position) {
|
|
118
|
+
console.log(chalk.bold.cyan('\n💡 Tips de uso:\n'));
|
|
119
|
+
|
|
120
|
+
if (projectType === 'web') {
|
|
121
|
+
console.log(chalk.white(' Web Component:'));
|
|
122
|
+
console.log(chalk.gray(' <g360-signature mode="powered"></g360-signature>'));
|
|
123
|
+
console.log(chalk.gray(' <g360-signature mode="own" version="1.0"></g360-signature>\n'));
|
|
124
|
+
} else {
|
|
125
|
+
console.log(chalk.white(' Flet Widget:'));
|
|
126
|
+
console.log(chalk.gray(' from core.components.g360_signature import G360Signature, g360_footer'));
|
|
127
|
+
console.log(chalk.gray(' page.add(g360_footer(version="1.0"))'));
|
|
128
|
+
console.log(chalk.gray(' page.add(G360Signature(mode="own"))\n'));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
console.log(chalk.white(' Cambiar posicion:'));
|
|
132
|
+
console.log(chalk.gray(' g360 signature install --position bottom-left\n'));
|
|
133
|
+
|
|
134
|
+
console.log(chalk.white(' Ver todas las posiciones:'));
|
|
135
|
+
console.log(chalk.gray(' g360 signature positions\n'));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function installWeb(targetDir, { force, mode, version, position }) {
|
|
139
|
+
console.log(chalk.gray('Detectado: Proyecto Web'));
|
|
140
|
+
|
|
141
|
+
const indexHtmlPath = path.join(targetDir, 'index.html');
|
|
142
|
+
let htmlContent = fs.readFileSync(indexHtmlPath, 'utf8');
|
|
143
|
+
|
|
144
|
+
const hasLocalIndex = fs.existsSync(path.join(targetDir, 'g360-signature', 'index.js'));
|
|
145
|
+
const scriptSrc = hasLocalIndex
|
|
146
|
+
? ' <script src="g360-signature/index.js"></script>'
|
|
147
|
+
: ' <script src="https://unpkg.com/g360-signature@latest/index.js"></script>';
|
|
148
|
+
|
|
149
|
+
const versionAttr = version ? ` version="${version}"` : '';
|
|
150
|
+
const positionStyle = POSITIONS[position] || POSITIONS['bottom-right'];
|
|
151
|
+
|
|
152
|
+
const signatureComponent = `
|
|
153
|
+
<!-- Firma Oficial G360 -->
|
|
154
|
+
<g360-signature mode="${mode}"${versionAttr} style="${positionStyle}"></g360-signature>
|
|
155
|
+
`;
|
|
156
|
+
|
|
157
|
+
if (force) {
|
|
158
|
+
htmlContent = htmlContent.replace(/\n.*g360-signature.*\n/g, '\n');
|
|
159
|
+
htmlContent = htmlContent.replace(/<script[^>]*unpkg\.com\/g360-signature[^>]*><\/script>/g, '');
|
|
160
|
+
htmlContent = htmlContent.replace(/<script[^>]*g360-signature\/index\.js[^>]*><\/script>/g, '');
|
|
161
|
+
htmlContent = htmlContent.replace(/\n\s*\n/g, '\n');
|
|
162
|
+
} else if (htmlContent.includes('g360-signature')) {
|
|
163
|
+
console.log(chalk.yellow('⚠️ g360-signature ya se encuentra instalado'));
|
|
164
|
+
console.log(chalk.gray('Usa --force para reinstalar'));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!hasLocalIndex) {
|
|
169
|
+
const sigDir = path.join(targetDir, 'g360-signature');
|
|
170
|
+
fs.copySync(SIGNATURE_ASSETS, sigDir);
|
|
171
|
+
console.log(chalk.gray(' Archivos copiados a g360-signature/'));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!htmlContent.includes('g360-signature/index.js') && !htmlContent.includes('unpkg.com/g360-signature')) {
|
|
175
|
+
htmlContent = htmlContent.replace('</body>', `${scriptSrc}\n </body>`);
|
|
176
|
+
}
|
|
177
|
+
if (!htmlContent.includes('<g360-signature')) {
|
|
178
|
+
htmlContent = htmlContent.replace('</body>', `${signatureComponent}\n </body>`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
fs.writeFileSync(indexHtmlPath, htmlContent, 'utf8');
|
|
182
|
+
console.log(chalk.gray(' index.html actualizado'));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function installFlet(targetDir, { force, mode, version, position }) {
|
|
186
|
+
console.log(chalk.gray('Detectado: Proyecto Flet'));
|
|
187
|
+
|
|
188
|
+
const fletDestDir = path.join(targetDir, 'src', 'core', 'components');
|
|
189
|
+
if (!fs.existsSync(fletDestDir)) {
|
|
190
|
+
fs.mkdirSync(fletDestDir, { recursive: true });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const widgetDest = path.join(fletDestDir, 'g360_signature.py');
|
|
194
|
+
|
|
195
|
+
if (fs.existsSync(widgetDest) && !force) {
|
|
196
|
+
console.log(chalk.yellow('⚠️ g360-signature ya se encuentra instalado'));
|
|
197
|
+
console.log(chalk.gray('Usa --force para reinstalar'));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const widgetSrc = path.join(SIGNATURE_ASSETS, 'g360_flet', 'g360_signature.py');
|
|
202
|
+
fs.copySync(widgetSrc, widgetDest);
|
|
203
|
+
console.log(chalk.gray(' Widget copiado a src/core/components/g360_signature.py'));
|
|
204
|
+
|
|
205
|
+
const initPath = path.join(fletDestDir, '__init__.py');
|
|
206
|
+
if (!fs.existsSync(initPath)) {
|
|
207
|
+
fs.writeFileSync(initPath, '', 'utf8');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Insertar en main.py
|
|
211
|
+
const mainPyPath = path.join(targetDir, 'src', 'main.py');
|
|
212
|
+
if (fs.existsSync(mainPyPath)) {
|
|
213
|
+
let mainContent = fs.readFileSync(mainPyPath, 'utf8');
|
|
214
|
+
|
|
215
|
+
const importLine = 'from core.components.g360_signature import G360Signature, g360_footer';
|
|
216
|
+
const versionAttr = version ? `, version="${version}"` : '';
|
|
217
|
+
|
|
218
|
+
// Codigo de insercion segun posicion
|
|
219
|
+
let footerCode;
|
|
220
|
+
switch (position) {
|
|
221
|
+
case 'sidebar':
|
|
222
|
+
footerCode = `\n # Firma G360 en sidebar\n sidebar.controls.append(g360_footer(${versionAttr.trim() ? versionAttr : ''}))\n`;
|
|
223
|
+
break;
|
|
224
|
+
case 'header':
|
|
225
|
+
footerCode = `\n # Firma G360 en header\n header.controls.append(G360Signature(mode="${mode}"${versionAttr}))\n`;
|
|
226
|
+
break;
|
|
227
|
+
case 'fixed-bottom':
|
|
228
|
+
footerCode = `\n # Firma G360 fija abajo\n page.overlay.append(G360Signature(mode="${mode}"${versionAttr}))\n`;
|
|
229
|
+
break;
|
|
230
|
+
default: // footer
|
|
231
|
+
footerCode = `\n # Firma G360\n page.add(g360_footer(${versionAttr.trim() ? versionAttr : ''}))\n`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!mainContent.includes('g360_signature')) {
|
|
235
|
+
mainContent = mainContent.replace(
|
|
236
|
+
/(from\s+core\.\w+.*\n)/,
|
|
237
|
+
`$1${importLine}\n`
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
if (mainContent.includes('page.add')) {
|
|
241
|
+
const lastAdd = mainContent.lastIndexOf('page.add');
|
|
242
|
+
const endOfLine = mainContent.indexOf('\n', lastAdd);
|
|
243
|
+
mainContent = mainContent.slice(0, endOfLine + 1) + footerCode + mainContent.slice(endOfLine + 1);
|
|
244
|
+
} else {
|
|
245
|
+
mainContent += footerCode;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
fs.writeFileSync(mainPyPath, mainContent, 'utf8');
|
|
249
|
+
console.log(chalk.gray(' main.py actualizado'));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|