limbo-component 1.0.1 → 1.5.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/CHANGELOG.md +56 -0
- package/README.md +61 -29
- package/dist/limbo.cjs.js +5 -5
- package/dist/limbo.cjs.map +1 -1
- package/dist/limbo.css +1 -1
- package/dist/limbo.es.js +2501 -788
- package/dist/limbo.es.map +1 -1
- package/dist/limbo.min.js +6 -6
- package/dist/limbo.min.js.map +1 -1
- package/dist/limbo.umd.js +8 -8
- package/dist/limbo.umd.js.map +1 -1
- package/dist/types/App.d.ts +2 -1
- package/dist/types/App.d.ts.map +1 -1
- package/dist/types/components/CropperView.d.ts +2 -3
- package/dist/types/components/CropperView.d.ts.map +1 -1
- package/dist/types/components/Gallery.d.ts +1 -0
- package/dist/types/components/Gallery.d.ts.map +1 -1
- package/dist/types/components/ImageCard.d.ts +1 -0
- package/dist/types/components/ImageCard.d.ts.map +1 -1
- package/dist/types/components/ImageVariantsModal.d.ts +17 -0
- package/dist/types/components/ImageVariantsModal.d.ts.map +1 -0
- package/dist/types/components/Pagination.d.ts +7 -0
- package/dist/types/components/Pagination.d.ts.map +1 -0
- package/dist/types/components/TabAI.d.ts +2 -2
- package/dist/types/components/TabAI.d.ts.map +1 -1
- package/dist/types/components/TabPortals.d.ts +2 -2
- package/dist/types/components/TabPortals.d.ts.map +1 -1
- package/dist/types/components/TabStock.d.ts +1 -2
- package/dist/types/components/TabStock.d.ts.map +1 -1
- package/dist/types/components/TokenExpiredModal.d.ts +9 -0
- package/dist/types/components/TokenExpiredModal.d.ts.map +1 -0
- package/dist/types/components/UploadForm.d.ts.map +1 -1
- package/dist/types/core/LimboCore.d.ts +4 -0
- package/dist/types/core/LimboCore.d.ts.map +1 -1
- package/dist/types/hooks/useAiServices.d.ts +1 -1
- package/dist/types/hooks/useAiServices.d.ts.map +1 -1
- package/dist/types/hooks/useCreateVariant.d.ts +13 -0
- package/dist/types/hooks/useCreateVariant.d.ts.map +1 -0
- package/dist/types/hooks/useExternalImages.d.ts +9 -1
- package/dist/types/hooks/useExternalImages.d.ts.map +1 -1
- package/dist/types/hooks/useImageParams.d.ts +1 -1
- package/dist/types/hooks/useImageParams.d.ts.map +1 -1
- package/dist/types/hooks/useImageVariants.d.ts +20 -0
- package/dist/types/hooks/useImageVariants.d.ts.map +1 -0
- package/dist/types/hooks/useImages.d.ts +1 -0
- package/dist/types/hooks/useImages.d.ts.map +1 -1
- package/dist/types/hooks/usePortalSources.d.ts +1 -1
- package/dist/types/hooks/usePortalSources.d.ts.map +1 -1
- package/dist/types/hooks/useStockServices.d.ts +1 -1
- package/dist/types/hooks/useStockServices.d.ts.map +1 -1
- package/dist/types/hooks/useTokenExpiration.d.ts +10 -0
- package/dist/types/hooks/useTokenExpiration.d.ts.map +1 -0
- package/dist/types/services/aiApi.d.ts +4 -2
- package/dist/types/services/aiApi.d.ts.map +1 -1
- package/dist/types/services/apiClient.d.ts +22 -12
- package/dist/types/services/apiClient.d.ts.map +1 -1
- package/dist/types/services/assetsApi.d.ts +13 -6
- package/dist/types/services/assetsApi.d.ts.map +1 -1
- package/dist/types/services/imageParamsApi.d.ts +1 -1
- package/dist/types/services/imageParamsApi.d.ts.map +1 -1
- package/dist/types/services/portalsApi.d.ts +2 -2
- package/dist/types/services/portalsApi.d.ts.map +1 -1
- package/dist/types/services/responseAdapters.d.ts +6 -6
- package/dist/types/services/responseAdapters.d.ts.map +1 -1
- package/dist/types/services/stockApi.d.ts +3 -3
- package/dist/types/services/stockApi.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/types/services/authService.d.ts +0 -65
- package/dist/types/services/authService.d.ts.map +0 -1
package/dist/limbo.es.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect,
|
|
1
|
+
import React, { useState, useEffect, useCallback, useRef, useMemo } from "react";
|
|
2
2
|
import require$$2 from "react-dom";
|
|
3
3
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
4
4
|
const DEFAULT_MESSAGES = {
|
|
@@ -30452,6 +30452,937 @@ function Tabs({ tabs, active, onChange }) {
|
|
|
30452
30452
|
}
|
|
30453
30453
|
);
|
|
30454
30454
|
}
|
|
30455
|
+
const API_URLS = {
|
|
30456
|
+
// DEV: "https://led-dev-limbo-dev.eu.els.local", // PREPRODUCCIÓN - Updated URL
|
|
30457
|
+
DEV: "http://localhost",
|
|
30458
|
+
// LOCAL - Para desarrollo local
|
|
30459
|
+
PROD: "https://limbo.lefebvre.com"
|
|
30460
|
+
};
|
|
30461
|
+
let globalConfig = {
|
|
30462
|
+
publicKey: null,
|
|
30463
|
+
token: null,
|
|
30464
|
+
// JWT token (opcional, generado automáticamente)
|
|
30465
|
+
authMode: null,
|
|
30466
|
+
// "session" | "manual"
|
|
30467
|
+
tokenEndpoint: null,
|
|
30468
|
+
// Endpoint para obtener token (configurable)
|
|
30469
|
+
prod: false
|
|
30470
|
+
};
|
|
30471
|
+
function configureApiClient(config) {
|
|
30472
|
+
globalConfig = {
|
|
30473
|
+
...globalConfig,
|
|
30474
|
+
...config
|
|
30475
|
+
};
|
|
30476
|
+
if (!globalConfig.authMode) {
|
|
30477
|
+
if (globalConfig.token) {
|
|
30478
|
+
globalConfig.authMode = "manual";
|
|
30479
|
+
} else {
|
|
30480
|
+
globalConfig.authMode = "session";
|
|
30481
|
+
}
|
|
30482
|
+
}
|
|
30483
|
+
}
|
|
30484
|
+
function getBaseUrl({ prod = false } = {}) {
|
|
30485
|
+
return prod ? API_URLS.PROD : API_URLS.DEV;
|
|
30486
|
+
}
|
|
30487
|
+
async function getHeaders({ isFormData = false, useJWT = true, customHeaders = {} } = {}) {
|
|
30488
|
+
const headers = {};
|
|
30489
|
+
if (!isFormData) {
|
|
30490
|
+
headers["Content-Type"] = "application/json";
|
|
30491
|
+
}
|
|
30492
|
+
if (useJWT) {
|
|
30493
|
+
let token = globalConfig.token;
|
|
30494
|
+
if (globalConfig.authMode === "session" && !token) {
|
|
30495
|
+
try {
|
|
30496
|
+
const baseUrl = getBaseUrl(globalConfig);
|
|
30497
|
+
const endpoint = globalConfig.tokenEndpoint || "/auth/token";
|
|
30498
|
+
const response = await fetch(`${baseUrl}${endpoint}`, {
|
|
30499
|
+
method: "POST",
|
|
30500
|
+
headers: { "Content-Type": "application/json" },
|
|
30501
|
+
body: JSON.stringify({ public_key: globalConfig.publicKey }),
|
|
30502
|
+
credentials: "include"
|
|
30503
|
+
// Incluir cookies de sesión
|
|
30504
|
+
});
|
|
30505
|
+
if (response.ok) {
|
|
30506
|
+
const data = await response.json();
|
|
30507
|
+
token = data.token;
|
|
30508
|
+
globalConfig.token = token;
|
|
30509
|
+
} else {
|
|
30510
|
+
throw new Error(`Session auth failed: ${response.status}`);
|
|
30511
|
+
}
|
|
30512
|
+
} catch (error) {
|
|
30513
|
+
console.error("❌ Session authentication failed:", error);
|
|
30514
|
+
throw new Error("Failed to authenticate with session. User must be logged in.");
|
|
30515
|
+
}
|
|
30516
|
+
}
|
|
30517
|
+
if (token) {
|
|
30518
|
+
headers.Authorization = `Bearer ${token}`;
|
|
30519
|
+
} else {
|
|
30520
|
+
console.warn("⚠️ No JWT token available:", {
|
|
30521
|
+
authMode: globalConfig.authMode,
|
|
30522
|
+
hasPublicKey: !!globalConfig.publicKey
|
|
30523
|
+
});
|
|
30524
|
+
}
|
|
30525
|
+
}
|
|
30526
|
+
if (isFormData) {
|
|
30527
|
+
delete headers["Content-Type"];
|
|
30528
|
+
}
|
|
30529
|
+
return { ...headers, ...customHeaders };
|
|
30530
|
+
}
|
|
30531
|
+
function setToken(token) {
|
|
30532
|
+
globalConfig.token = token;
|
|
30533
|
+
}
|
|
30534
|
+
function getConfig() {
|
|
30535
|
+
return { ...globalConfig };
|
|
30536
|
+
}
|
|
30537
|
+
async function callApi({
|
|
30538
|
+
endpoint,
|
|
30539
|
+
method = "GET",
|
|
30540
|
+
body = null,
|
|
30541
|
+
prod = false,
|
|
30542
|
+
basePath = "",
|
|
30543
|
+
customHeaders = {},
|
|
30544
|
+
isFormData = false,
|
|
30545
|
+
useJWT = true
|
|
30546
|
+
// New parameter to control JWT usage
|
|
30547
|
+
}) {
|
|
30548
|
+
try {
|
|
30549
|
+
const useProd = prod || globalConfig.prod;
|
|
30550
|
+
const headers = await getHeaders({
|
|
30551
|
+
isFormData,
|
|
30552
|
+
useJWT,
|
|
30553
|
+
customHeaders
|
|
30554
|
+
});
|
|
30555
|
+
if (method === "GET" && !body && headers["Content-Type"]) {
|
|
30556
|
+
delete headers["Content-Type"];
|
|
30557
|
+
}
|
|
30558
|
+
const res = await fetch(`${getBaseUrl({ prod: useProd })}${basePath}${endpoint}`, {
|
|
30559
|
+
method,
|
|
30560
|
+
headers,
|
|
30561
|
+
body: body ? isFormData ? body : JSON.stringify(body) : void 0,
|
|
30562
|
+
credentials: "include"
|
|
30563
|
+
// Include cookies for CORS
|
|
30564
|
+
});
|
|
30565
|
+
if (!res.ok) {
|
|
30566
|
+
let errorMessage = `HTTP ${res.status}: ${res.statusText}`;
|
|
30567
|
+
let errorData = null;
|
|
30568
|
+
try {
|
|
30569
|
+
const result = await res.json();
|
|
30570
|
+
errorData = result;
|
|
30571
|
+
if (result.error) {
|
|
30572
|
+
errorMessage = `${result.error.code}: ${result.error.message}`;
|
|
30573
|
+
} else if (result.message) {
|
|
30574
|
+
errorMessage = result.message;
|
|
30575
|
+
}
|
|
30576
|
+
} catch {
|
|
30577
|
+
}
|
|
30578
|
+
if (res.status === 401 && errorData) {
|
|
30579
|
+
if (errorData.error === "token_expired" || errorData.message && errorData.message.includes("expired") || errorData.message && errorData.message.includes("caducado") || errorData.error && errorData.error.includes("expired")) {
|
|
30580
|
+
window.dispatchEvent(new CustomEvent("tokenExpiredError", {
|
|
30581
|
+
detail: { error: { response: { status: 401, data: errorData } } }
|
|
30582
|
+
}));
|
|
30583
|
+
}
|
|
30584
|
+
}
|
|
30585
|
+
throw new Error(errorMessage);
|
|
30586
|
+
}
|
|
30587
|
+
return res.json();
|
|
30588
|
+
} catch (err) {
|
|
30589
|
+
throw new Error(`API Error: ${err.message}`);
|
|
30590
|
+
}
|
|
30591
|
+
}
|
|
30592
|
+
function getApiBaseUrl() {
|
|
30593
|
+
const config = getConfig();
|
|
30594
|
+
const prod = config?.prod || false;
|
|
30595
|
+
const API_URLS2 = {
|
|
30596
|
+
DEV: "http://localhost",
|
|
30597
|
+
PROD: "https://limbo.lefebvre.com"
|
|
30598
|
+
};
|
|
30599
|
+
return prod ? API_URLS2.PROD : API_URLS2.DEV;
|
|
30600
|
+
}
|
|
30601
|
+
function generateFallbackAssetUrl(asset) {
|
|
30602
|
+
if (!asset?.id) return null;
|
|
30603
|
+
const baseUrl = getApiBaseUrl();
|
|
30604
|
+
return `${baseUrl}/api/assets/${asset.id}/download`;
|
|
30605
|
+
}
|
|
30606
|
+
function makeAbsoluteUrl(url) {
|
|
30607
|
+
if (!url) return null;
|
|
30608
|
+
if (url.startsWith("http://") || url.startsWith("https://")) {
|
|
30609
|
+
return url;
|
|
30610
|
+
}
|
|
30611
|
+
if (url.startsWith("/")) {
|
|
30612
|
+
const baseUrl = getApiBaseUrl();
|
|
30613
|
+
return `${baseUrl}${url}`;
|
|
30614
|
+
}
|
|
30615
|
+
return url;
|
|
30616
|
+
}
|
|
30617
|
+
function adaptAssetsListFromV2(v2Response) {
|
|
30618
|
+
if (!v2Response?.success || !Array.isArray(v2Response?.data?.data)) {
|
|
30619
|
+
return { result: [] };
|
|
30620
|
+
}
|
|
30621
|
+
const legacyAssets = v2Response.data.data.map((asset) => {
|
|
30622
|
+
return {
|
|
30623
|
+
id: asset.id,
|
|
30624
|
+
filename: asset.filename || asset.original_filename,
|
|
30625
|
+
mime_type: asset.mime_type,
|
|
30626
|
+
file_size: asset.file_size,
|
|
30627
|
+
width: asset.width,
|
|
30628
|
+
height: asset.height,
|
|
30629
|
+
upload_date: asset.upload_date || asset.created_at,
|
|
30630
|
+
processing_status: asset.processing_status || asset.status,
|
|
30631
|
+
// URL principal - handle multiple URL formats from different backend responses
|
|
30632
|
+
url: makeAbsoluteUrl(
|
|
30633
|
+
asset.master_url || asset.master?.url_signed || asset.urls?.original || asset.url
|
|
30634
|
+
) || generateFallbackAssetUrl(asset),
|
|
30635
|
+
// Generate fallback URL if none provided
|
|
30636
|
+
webp_available: asset.webp_available || !!asset.webp_url,
|
|
30637
|
+
// Solo metadatos básicos en listado
|
|
30638
|
+
metadata: asset.metadata || {},
|
|
30639
|
+
variants_count: asset.variants_count || 0
|
|
30640
|
+
};
|
|
30641
|
+
});
|
|
30642
|
+
const response = { result: legacyAssets };
|
|
30643
|
+
if (v2Response.data.pagination) {
|
|
30644
|
+
response.pagination = {
|
|
30645
|
+
page: v2Response.data.pagination.page,
|
|
30646
|
+
limit: v2Response.data.pagination.limit,
|
|
30647
|
+
total: v2Response.data.pagination.total,
|
|
30648
|
+
pages: v2Response.data.pagination.pages
|
|
30649
|
+
};
|
|
30650
|
+
}
|
|
30651
|
+
return response;
|
|
30652
|
+
}
|
|
30653
|
+
function adaptVariantsListFromV2(v2Response) {
|
|
30654
|
+
if (!v2Response?.success || !Array.isArray(v2Response?.data?.variants)) {
|
|
30655
|
+
return { result: [] };
|
|
30656
|
+
}
|
|
30657
|
+
const legacyVariants = v2Response.data.variants.map((variant) => {
|
|
30658
|
+
return {
|
|
30659
|
+
id: variant.id,
|
|
30660
|
+
name: variant.filename || variant.name || `Variante ${variant.id}`,
|
|
30661
|
+
filename: variant.filename,
|
|
30662
|
+
mime_type: variant.mime_type,
|
|
30663
|
+
format: variant.output_format || variant.format || (variant.mime_type ? variant.mime_type.split("/")[1] : "jpg"),
|
|
30664
|
+
file_size: variant.file_size,
|
|
30665
|
+
width: variant.width,
|
|
30666
|
+
height: variant.height,
|
|
30667
|
+
upload_date: variant.upload_date || variant.created_at,
|
|
30668
|
+
processing_status: variant.processing_status || variant.status,
|
|
30669
|
+
// URL principal - handle multiple URL formats from different backend responses
|
|
30670
|
+
url: makeAbsoluteUrl(
|
|
30671
|
+
variant.master_url || variant.master?.url_signed || variant.urls?.original || variant.url
|
|
30672
|
+
) || generateFallbackAssetUrl(variant),
|
|
30673
|
+
webp_available: variant.webp_available || !!variant.webp_url,
|
|
30674
|
+
// Metadatos específicos de variante
|
|
30675
|
+
metadata: variant.metadata || {},
|
|
30676
|
+
crop_data: variant.crop_data || {},
|
|
30677
|
+
crop_params: variant.crop_params || variant.crop_data || {},
|
|
30678
|
+
parent_asset_id: variant.parent_asset_id || v2Response.data.asset_id,
|
|
30679
|
+
variant_type: variant.variant_type || "crop"
|
|
30680
|
+
};
|
|
30681
|
+
});
|
|
30682
|
+
return { result: legacyVariants };
|
|
30683
|
+
}
|
|
30684
|
+
function adaptUploadFromV2(v2Response) {
|
|
30685
|
+
if (!v2Response?.success || !v2Response?.data) {
|
|
30686
|
+
return { result: null };
|
|
30687
|
+
}
|
|
30688
|
+
const asset = v2Response.data;
|
|
30689
|
+
const legacyResponse = {
|
|
30690
|
+
id: asset.id,
|
|
30691
|
+
filename: asset.original_filename,
|
|
30692
|
+
mime_type: asset.mime_type,
|
|
30693
|
+
file_size: asset.file_size,
|
|
30694
|
+
width: asset.width,
|
|
30695
|
+
height: asset.height,
|
|
30696
|
+
status: asset.status,
|
|
30697
|
+
upload_date: asset.created_at,
|
|
30698
|
+
// URL del master
|
|
30699
|
+
url: makeAbsoluteUrl(asset.master?.url_signed),
|
|
30700
|
+
master_format: asset.master?.format,
|
|
30701
|
+
// Estado de procesamiento
|
|
30702
|
+
processing: asset.processing || {
|
|
30703
|
+
master_webp: asset.status === "processing" ? "queued" : "completed",
|
|
30704
|
+
variants: asset.status === "processing" ? "queued" : "completed"
|
|
30705
|
+
},
|
|
30706
|
+
// Información adicional
|
|
30707
|
+
checksum: asset.checksum,
|
|
30708
|
+
storage_path: asset.storage_path_base,
|
|
30709
|
+
metadata: asset.metadata || {}
|
|
30710
|
+
};
|
|
30711
|
+
return { result: legacyResponse };
|
|
30712
|
+
}
|
|
30713
|
+
function adaptVariantGenerationFromV2(v2Response) {
|
|
30714
|
+
if (!v2Response?.success || !v2Response?.data) {
|
|
30715
|
+
return { result: null };
|
|
30716
|
+
}
|
|
30717
|
+
const data = v2Response.data;
|
|
30718
|
+
const legacyResponse = {
|
|
30719
|
+
job_id: data.job_id,
|
|
30720
|
+
asset_id: data.asset_id,
|
|
30721
|
+
variants_requested: data.variants_requested || [],
|
|
30722
|
+
processing_mode: data.async ? "async" : "sync",
|
|
30723
|
+
estimated_completion: data.estimated_completion,
|
|
30724
|
+
// Estado de cada variante
|
|
30725
|
+
variant_statuses: (data.variant_statuses || []).map((status) => ({
|
|
30726
|
+
name: status.name,
|
|
30727
|
+
status: status.status,
|
|
30728
|
+
size: status.expected_size,
|
|
30729
|
+
format: status.format
|
|
30730
|
+
}))
|
|
30731
|
+
};
|
|
30732
|
+
return { result: legacyResponse };
|
|
30733
|
+
}
|
|
30734
|
+
function adaptErrorFromV2(error) {
|
|
30735
|
+
if (error.message && !error.message.includes("API Error:")) {
|
|
30736
|
+
return error;
|
|
30737
|
+
}
|
|
30738
|
+
let errorMessage = error.message || "Unknown API error";
|
|
30739
|
+
if (errorMessage.startsWith("API Error: ")) {
|
|
30740
|
+
errorMessage = errorMessage.substring(11);
|
|
30741
|
+
}
|
|
30742
|
+
return new Error(errorMessage);
|
|
30743
|
+
}
|
|
30744
|
+
const BASE_PATH$4 = "/api";
|
|
30745
|
+
async function listAssets(params = {}) {
|
|
30746
|
+
try {
|
|
30747
|
+
const queryString = new URLSearchParams(params).toString();
|
|
30748
|
+
const endpoint = queryString ? `/assets?${queryString}` : "/assets";
|
|
30749
|
+
const response = await callApi({
|
|
30750
|
+
endpoint,
|
|
30751
|
+
method: "GET",
|
|
30752
|
+
basePath: BASE_PATH$4,
|
|
30753
|
+
useJWT: true
|
|
30754
|
+
});
|
|
30755
|
+
return adaptAssetsListFromV2(response);
|
|
30756
|
+
} catch (error) {
|
|
30757
|
+
throw adaptErrorFromV2(error);
|
|
30758
|
+
}
|
|
30759
|
+
}
|
|
30760
|
+
async function uploadAsset(file, uploaded_by = null, store_original = false) {
|
|
30761
|
+
try {
|
|
30762
|
+
const formData = new FormData();
|
|
30763
|
+
formData.append("file", file);
|
|
30764
|
+
if (uploaded_by) formData.append("uploaded_by", uploaded_by);
|
|
30765
|
+
if (store_original) formData.append("store_original", store_original.toString());
|
|
30766
|
+
const response = await callApi({
|
|
30767
|
+
endpoint: "/assets",
|
|
30768
|
+
method: "POST",
|
|
30769
|
+
body: formData,
|
|
30770
|
+
basePath: BASE_PATH$4,
|
|
30771
|
+
isFormData: true,
|
|
30772
|
+
useJWT: true
|
|
30773
|
+
});
|
|
30774
|
+
return adaptUploadFromV2(response);
|
|
30775
|
+
} catch (error) {
|
|
30776
|
+
throw adaptErrorFromV2(error);
|
|
30777
|
+
}
|
|
30778
|
+
}
|
|
30779
|
+
async function deleteAsset(assetId) {
|
|
30780
|
+
try {
|
|
30781
|
+
const response = await callApi({
|
|
30782
|
+
endpoint: `/assets/${assetId}`,
|
|
30783
|
+
method: "DELETE",
|
|
30784
|
+
basePath: BASE_PATH$4,
|
|
30785
|
+
useJWT: true
|
|
30786
|
+
});
|
|
30787
|
+
return response;
|
|
30788
|
+
} catch (error) {
|
|
30789
|
+
throw adaptErrorFromV2(error);
|
|
30790
|
+
}
|
|
30791
|
+
}
|
|
30792
|
+
async function generateVariant(assetId, {
|
|
30793
|
+
variant_name,
|
|
30794
|
+
width,
|
|
30795
|
+
height,
|
|
30796
|
+
crop_params,
|
|
30797
|
+
preset_aspect = null,
|
|
30798
|
+
preset_size = null,
|
|
30799
|
+
output_format = "webp"
|
|
30800
|
+
}) {
|
|
30801
|
+
try {
|
|
30802
|
+
const variants = [{
|
|
30803
|
+
name: variant_name,
|
|
30804
|
+
width,
|
|
30805
|
+
height,
|
|
30806
|
+
output_format,
|
|
30807
|
+
crop_params
|
|
30808
|
+
}];
|
|
30809
|
+
if (preset_aspect) variants[0].preset_aspect = preset_aspect;
|
|
30810
|
+
if (preset_size) variants[0].preset_size = preset_size;
|
|
30811
|
+
const response = await callApi({
|
|
30812
|
+
endpoint: `/assets/${assetId}/variants`,
|
|
30813
|
+
method: "POST",
|
|
30814
|
+
body: {
|
|
30815
|
+
variants,
|
|
30816
|
+
async: false
|
|
30817
|
+
},
|
|
30818
|
+
basePath: BASE_PATH$4,
|
|
30819
|
+
useJWT: true
|
|
30820
|
+
});
|
|
30821
|
+
return adaptVariantGenerationFromV2(response);
|
|
30822
|
+
} catch (error) {
|
|
30823
|
+
throw adaptErrorFromV2(error);
|
|
30824
|
+
}
|
|
30825
|
+
}
|
|
30826
|
+
async function listVariants(assetId) {
|
|
30827
|
+
try {
|
|
30828
|
+
const response = await callApi({
|
|
30829
|
+
endpoint: `/assets/${assetId}/variants`,
|
|
30830
|
+
method: "GET",
|
|
30831
|
+
basePath: BASE_PATH$4,
|
|
30832
|
+
useJWT: true
|
|
30833
|
+
});
|
|
30834
|
+
const adaptedResponse = adaptVariantsListFromV2(response);
|
|
30835
|
+
return adaptedResponse;
|
|
30836
|
+
} catch (error) {
|
|
30837
|
+
throw adaptErrorFromV2(error);
|
|
30838
|
+
}
|
|
30839
|
+
}
|
|
30840
|
+
async function deleteVariant(assetId, variantId) {
|
|
30841
|
+
try {
|
|
30842
|
+
const response = await callApi({
|
|
30843
|
+
endpoint: `/assets/${assetId}/variants/${variantId}`,
|
|
30844
|
+
method: "DELETE",
|
|
30845
|
+
basePath: BASE_PATH$4,
|
|
30846
|
+
useJWT: true
|
|
30847
|
+
});
|
|
30848
|
+
return response;
|
|
30849
|
+
} catch (error) {
|
|
30850
|
+
throw adaptErrorFromV2(error);
|
|
30851
|
+
}
|
|
30852
|
+
}
|
|
30853
|
+
function useImageVariants() {
|
|
30854
|
+
const [variants, setVariants] = useState({});
|
|
30855
|
+
const [loading, setLoading] = useState({});
|
|
30856
|
+
const [errors, setErrors] = useState({});
|
|
30857
|
+
const loadVariants = useCallback(async (assetId) => {
|
|
30858
|
+
if (!assetId) return;
|
|
30859
|
+
if (loading[assetId]) return;
|
|
30860
|
+
if (variants[assetId]) return variants[assetId];
|
|
30861
|
+
setLoading((prev) => ({ ...prev, [assetId]: true }));
|
|
30862
|
+
setErrors((prev) => ({ ...prev, [assetId]: null }));
|
|
30863
|
+
try {
|
|
30864
|
+
const response = await listVariants(assetId);
|
|
30865
|
+
const variantsList = response?.result || [];
|
|
30866
|
+
setVariants((prev) => ({ ...prev, [assetId]: variantsList }));
|
|
30867
|
+
return variantsList;
|
|
30868
|
+
} catch (error) {
|
|
30869
|
+
console.error("Error loading variants:", error);
|
|
30870
|
+
setErrors((prev) => ({ ...prev, [assetId]: error.message }));
|
|
30871
|
+
return [];
|
|
30872
|
+
} finally {
|
|
30873
|
+
setLoading((prev) => ({ ...prev, [assetId]: false }));
|
|
30874
|
+
}
|
|
30875
|
+
}, [variants, loading]);
|
|
30876
|
+
const getVariants = useCallback((assetId) => {
|
|
30877
|
+
return variants[assetId] || [];
|
|
30878
|
+
}, [variants]);
|
|
30879
|
+
const isLoading = useCallback((assetId) => {
|
|
30880
|
+
return loading[assetId] || false;
|
|
30881
|
+
}, [loading]);
|
|
30882
|
+
const getError = useCallback((assetId) => {
|
|
30883
|
+
return errors[assetId] || null;
|
|
30884
|
+
}, [errors]);
|
|
30885
|
+
const clearVariants = useCallback((assetId) => {
|
|
30886
|
+
setVariants((prev) => {
|
|
30887
|
+
const newState = { ...prev };
|
|
30888
|
+
delete newState[assetId];
|
|
30889
|
+
return newState;
|
|
30890
|
+
});
|
|
30891
|
+
setLoading((prev) => {
|
|
30892
|
+
const newState = { ...prev };
|
|
30893
|
+
delete newState[assetId];
|
|
30894
|
+
return newState;
|
|
30895
|
+
});
|
|
30896
|
+
setErrors((prev) => {
|
|
30897
|
+
const newState = { ...prev };
|
|
30898
|
+
delete newState[assetId];
|
|
30899
|
+
return newState;
|
|
30900
|
+
});
|
|
30901
|
+
}, []);
|
|
30902
|
+
const refreshVariants = useCallback(async (assetId) => {
|
|
30903
|
+
clearVariants(assetId);
|
|
30904
|
+
return await loadVariants(assetId);
|
|
30905
|
+
}, [clearVariants, loadVariants]);
|
|
30906
|
+
const removeVariant = useCallback(async (assetId, variantId) => {
|
|
30907
|
+
try {
|
|
30908
|
+
await deleteVariant(assetId, variantId);
|
|
30909
|
+
setVariants((prev) => {
|
|
30910
|
+
const assetVariants = prev[assetId] || [];
|
|
30911
|
+
return {
|
|
30912
|
+
...prev,
|
|
30913
|
+
[assetId]: assetVariants.filter((v) => v.id !== variantId)
|
|
30914
|
+
};
|
|
30915
|
+
});
|
|
30916
|
+
return { success: true };
|
|
30917
|
+
} catch (error) {
|
|
30918
|
+
console.error("Error deleting variant:", error);
|
|
30919
|
+
return { success: false, error: error.message };
|
|
30920
|
+
}
|
|
30921
|
+
}, []);
|
|
30922
|
+
return {
|
|
30923
|
+
loadVariants,
|
|
30924
|
+
getVariants,
|
|
30925
|
+
isLoading,
|
|
30926
|
+
getError,
|
|
30927
|
+
clearVariants,
|
|
30928
|
+
refreshVariants,
|
|
30929
|
+
removeVariant
|
|
30930
|
+
};
|
|
30931
|
+
}
|
|
30932
|
+
function ImageVariantsModal({
|
|
30933
|
+
image,
|
|
30934
|
+
isOpen,
|
|
30935
|
+
onClose,
|
|
30936
|
+
onSelect,
|
|
30937
|
+
onDelete,
|
|
30938
|
+
onCrop,
|
|
30939
|
+
onVariantDeleted,
|
|
30940
|
+
// Nueva prop para notificar cuando se elimina una variante
|
|
30941
|
+
allowedActions = {
|
|
30942
|
+
select: true,
|
|
30943
|
+
download: true,
|
|
30944
|
+
copy: true,
|
|
30945
|
+
delete: true,
|
|
30946
|
+
crop: true
|
|
30947
|
+
}
|
|
30948
|
+
}) {
|
|
30949
|
+
const { loadVariants, getVariants, isLoading, getError, removeVariant } = useImageVariants();
|
|
30950
|
+
const accessibilityManager = window.limboCore?.accessibilityManager;
|
|
30951
|
+
const [deleteMessage, setDeleteMessage] = React.useState(null);
|
|
30952
|
+
const [deleteMessageType, setDeleteMessageType] = React.useState(null);
|
|
30953
|
+
const config = window.limboCore?.config?.getGlobal() || {};
|
|
30954
|
+
const interaction = config.interaction || {
|
|
30955
|
+
allowSelection: false,
|
|
30956
|
+
allowCropping: true
|
|
30957
|
+
};
|
|
30958
|
+
const shouldShowSelectButton = interaction.allowSelection && allowedActions.select && onSelect;
|
|
30959
|
+
const shouldShowCropButton = interaction.allowCropping && allowedActions.crop && onCrop;
|
|
30960
|
+
const shouldShowCopyButton = allowedActions.copy;
|
|
30961
|
+
const shouldShowDownloadButton = allowedActions.download;
|
|
30962
|
+
const shouldShowDeleteButton = allowedActions.delete;
|
|
30963
|
+
const [failedVariants, setFailedVariants] = React.useState(/* @__PURE__ */ new Set());
|
|
30964
|
+
const apiVariants = React.useMemo(
|
|
30965
|
+
() => image?.variants || [],
|
|
30966
|
+
[image?.variants]
|
|
30967
|
+
);
|
|
30968
|
+
const hookVariants = getVariants(image?.id);
|
|
30969
|
+
const variants = apiVariants.length > 0 ? apiVariants : hookVariants;
|
|
30970
|
+
const loading = apiVariants.length === 0 ? isLoading(image?.id) : false;
|
|
30971
|
+
const error = apiVariants.length === 0 ? getError(image?.id) : null;
|
|
30972
|
+
useEffect(() => {
|
|
30973
|
+
if (isOpen && image?.id && (!image?.variants || image.variants.length === 0)) {
|
|
30974
|
+
loadVariants(image.id);
|
|
30975
|
+
}
|
|
30976
|
+
}, [isOpen, image?.id, image?.variants, loadVariants]);
|
|
30977
|
+
React.useEffect(() => {
|
|
30978
|
+
if (isOpen) {
|
|
30979
|
+
setFailedVariants(/* @__PURE__ */ new Set());
|
|
30980
|
+
}
|
|
30981
|
+
}, [isOpen]);
|
|
30982
|
+
const handleVariantImageError = (variantId) => {
|
|
30983
|
+
setFailedVariants((prev) => /* @__PURE__ */ new Set([...prev, variantId]));
|
|
30984
|
+
};
|
|
30985
|
+
if (!isOpen) return null;
|
|
30986
|
+
const handleSelectVariant = (variant) => {
|
|
30987
|
+
const variantAsImage = {
|
|
30988
|
+
id: variant.id,
|
|
30989
|
+
filename: `${image.filename}_${variant.name}`,
|
|
30990
|
+
url: variant.url,
|
|
30991
|
+
width: variant.width,
|
|
30992
|
+
height: variant.height,
|
|
30993
|
+
mime_type: `image/${variant.format}`,
|
|
30994
|
+
file_size: variant.file_size,
|
|
30995
|
+
upload_date: variant.created_at,
|
|
30996
|
+
processing_status: "completed",
|
|
30997
|
+
is_variant: true,
|
|
30998
|
+
parent_asset_id: image.id,
|
|
30999
|
+
variant_info: variant
|
|
31000
|
+
};
|
|
31001
|
+
accessibilityManager?.announce(`Variante seleccionada: ${variant.name}`);
|
|
31002
|
+
onSelect?.(variantAsImage);
|
|
31003
|
+
onClose?.();
|
|
31004
|
+
};
|
|
31005
|
+
const handleCopyVariantUrl = async (variant) => {
|
|
31006
|
+
try {
|
|
31007
|
+
await navigator.clipboard.writeText(variant.url);
|
|
31008
|
+
accessibilityManager?.announce(`URL de variante ${variant.name} copiada`);
|
|
31009
|
+
} catch (err) {
|
|
31010
|
+
console.error("Error copying variant URL:", err);
|
|
31011
|
+
accessibilityManager?.announceError("Error al copiar URL de variante");
|
|
31012
|
+
}
|
|
31013
|
+
};
|
|
31014
|
+
const handleDownloadVariant = (variant) => {
|
|
31015
|
+
accessibilityManager?.announce(`Descargando variante ${variant.name}`);
|
|
31016
|
+
const a = document.createElement("a");
|
|
31017
|
+
a.href = variant.url;
|
|
31018
|
+
a.download = `${image.filename}_${variant.name}.${variant.format}`;
|
|
31019
|
+
document.body.appendChild(a);
|
|
31020
|
+
a.click();
|
|
31021
|
+
document.body.removeChild(a);
|
|
31022
|
+
};
|
|
31023
|
+
const handleViewVariant = (variant) => {
|
|
31024
|
+
accessibilityManager?.announce(
|
|
31025
|
+
`Abriendo variante ${variant.name} en nueva pestaña`
|
|
31026
|
+
);
|
|
31027
|
+
window.open(variant.url, "_blank");
|
|
31028
|
+
};
|
|
31029
|
+
const handleViewOriginal = () => {
|
|
31030
|
+
accessibilityManager?.announce(
|
|
31031
|
+
`Abriendo imagen original ${image.filename} en nueva pestaña`
|
|
31032
|
+
);
|
|
31033
|
+
window.open(image.url, "_blank");
|
|
31034
|
+
};
|
|
31035
|
+
const handleCopyOriginalUrl = async () => {
|
|
31036
|
+
try {
|
|
31037
|
+
await navigator.clipboard.writeText(image.url);
|
|
31038
|
+
accessibilityManager?.announce(`URL de imagen original copiada`);
|
|
31039
|
+
} catch (err) {
|
|
31040
|
+
console.error("Error copying original URL:", err);
|
|
31041
|
+
accessibilityManager?.announceError("Error al copiar URL");
|
|
31042
|
+
}
|
|
31043
|
+
};
|
|
31044
|
+
const handleDownloadOriginal = () => {
|
|
31045
|
+
accessibilityManager?.announce(
|
|
31046
|
+
`Descargando imagen original ${image.filename}`
|
|
31047
|
+
);
|
|
31048
|
+
const a = document.createElement("a");
|
|
31049
|
+
a.href = image.url;
|
|
31050
|
+
a.download = image.filename;
|
|
31051
|
+
document.body.appendChild(a);
|
|
31052
|
+
a.click();
|
|
31053
|
+
document.body.removeChild(a);
|
|
31054
|
+
};
|
|
31055
|
+
const handleDeleteOriginal = async () => {
|
|
31056
|
+
if (!onDelete) return;
|
|
31057
|
+
const confirmed = window.confirm(
|
|
31058
|
+
`¿Estás seguro de que deseas eliminar "${image.filename}"? Esta acción también eliminará todas sus variantes.`
|
|
31059
|
+
);
|
|
31060
|
+
if (confirmed) {
|
|
31061
|
+
accessibilityManager?.announce(`Eliminando imagen ${image.filename}`);
|
|
31062
|
+
await onDelete(image.id);
|
|
31063
|
+
onClose?.();
|
|
31064
|
+
}
|
|
31065
|
+
};
|
|
31066
|
+
const handleCropOriginal = () => {
|
|
31067
|
+
if (!onCrop) return;
|
|
31068
|
+
accessibilityManager?.announce(
|
|
31069
|
+
`Abriendo herramienta de recorte para ${image.filename}`
|
|
31070
|
+
);
|
|
31071
|
+
onCrop(image);
|
|
31072
|
+
onClose?.();
|
|
31073
|
+
};
|
|
31074
|
+
const handleDeleteVariant = async (variant) => {
|
|
31075
|
+
const confirmed = window.confirm(
|
|
31076
|
+
`¿Estás seguro de que deseas eliminar la variante "${variant.name || variant.filename}"?`
|
|
31077
|
+
);
|
|
31078
|
+
if (confirmed) {
|
|
31079
|
+
accessibilityManager?.announce(
|
|
31080
|
+
`Eliminando variante ${variant.name || variant.filename}`
|
|
31081
|
+
);
|
|
31082
|
+
const result = await removeVariant(image.id, variant.id);
|
|
31083
|
+
if (result.success) {
|
|
31084
|
+
setDeleteMessage("Variante eliminada correctamente");
|
|
31085
|
+
setDeleteMessageType("success");
|
|
31086
|
+
accessibilityManager?.announce(`Variante eliminada correctamente`);
|
|
31087
|
+
onVariantDeleted?.();
|
|
31088
|
+
setTimeout(() => {
|
|
31089
|
+
setDeleteMessage(null);
|
|
31090
|
+
setDeleteMessageType(null);
|
|
31091
|
+
}, 3e3);
|
|
31092
|
+
} else {
|
|
31093
|
+
setDeleteMessage(`Error al eliminar variante: ${result.error}`);
|
|
31094
|
+
setDeleteMessageType("error");
|
|
31095
|
+
accessibilityManager?.announceError(
|
|
31096
|
+
`Error al eliminar variante: ${result.error}`
|
|
31097
|
+
);
|
|
31098
|
+
setTimeout(() => {
|
|
31099
|
+
setDeleteMessage(null);
|
|
31100
|
+
setDeleteMessageType(null);
|
|
31101
|
+
}, 5e3);
|
|
31102
|
+
}
|
|
31103
|
+
}
|
|
31104
|
+
};
|
|
31105
|
+
return /* @__PURE__ */ jsx(
|
|
31106
|
+
"div",
|
|
31107
|
+
{
|
|
31108
|
+
className: "limbo-modal-overlay",
|
|
31109
|
+
onClick: onClose,
|
|
31110
|
+
role: "dialog",
|
|
31111
|
+
"aria-modal": "true",
|
|
31112
|
+
"aria-labelledby": "variants-modal-title",
|
|
31113
|
+
children: /* @__PURE__ */ jsxs(
|
|
31114
|
+
"div",
|
|
31115
|
+
{
|
|
31116
|
+
className: "limbo-modal-content limbo-variants-modal",
|
|
31117
|
+
onClick: (e) => e.stopPropagation(),
|
|
31118
|
+
children: [
|
|
31119
|
+
/* @__PURE__ */ jsxs("div", { className: "limbo-modal-header", children: [
|
|
31120
|
+
/* @__PURE__ */ jsxs("h2", { id: "variants-modal-title", children: [
|
|
31121
|
+
"Variantes de ",
|
|
31122
|
+
image?.filename
|
|
31123
|
+
] }),
|
|
31124
|
+
/* @__PURE__ */ jsx(
|
|
31125
|
+
"button",
|
|
31126
|
+
{
|
|
31127
|
+
className: "limbo-modal-close",
|
|
31128
|
+
onClick: onClose,
|
|
31129
|
+
"aria-label": "Cerrar modal de variantes",
|
|
31130
|
+
children: "✕"
|
|
31131
|
+
}
|
|
31132
|
+
)
|
|
31133
|
+
] }),
|
|
31134
|
+
deleteMessage && /* @__PURE__ */ jsx(
|
|
31135
|
+
"div",
|
|
31136
|
+
{
|
|
31137
|
+
className: `limbo-variants-message limbo-variants-message--${deleteMessageType} mt-1`,
|
|
31138
|
+
role: "alert",
|
|
31139
|
+
style: {
|
|
31140
|
+
padding: "12px 20px",
|
|
31141
|
+
margin: "0 20px 15px 20px",
|
|
31142
|
+
borderRadius: "4px",
|
|
31143
|
+
backgroundColor: deleteMessageType === "success" ? "#d4edda" : "#f8d7da",
|
|
31144
|
+
color: deleteMessageType === "success" ? "#155724" : "#721c24",
|
|
31145
|
+
border: `1px solid ${deleteMessageType === "success" ? "#c3e6cb" : "#f5c6cb"}`
|
|
31146
|
+
},
|
|
31147
|
+
children: deleteMessage
|
|
31148
|
+
}
|
|
31149
|
+
),
|
|
31150
|
+
/* @__PURE__ */ jsx("div", { className: "limbo-modal-body", children: loading ? /* @__PURE__ */ jsxs("div", { className: "limbo-variants-loading", children: [
|
|
31151
|
+
/* @__PURE__ */ jsx("div", { className: "limbo-loader" }),
|
|
31152
|
+
/* @__PURE__ */ jsx("p", { children: "Cargando variantes..." })
|
|
31153
|
+
] }) : error ? /* @__PURE__ */ jsxs("div", { className: "limbo-variants-error", children: [
|
|
31154
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
31155
|
+
"Error al cargar variantes: ",
|
|
31156
|
+
error
|
|
31157
|
+
] }),
|
|
31158
|
+
/* @__PURE__ */ jsx(
|
|
31159
|
+
"button",
|
|
31160
|
+
{
|
|
31161
|
+
onClick: () => loadVariants(image.id),
|
|
31162
|
+
className: "btn btn-primary",
|
|
31163
|
+
children: "Reintentar"
|
|
31164
|
+
}
|
|
31165
|
+
)
|
|
31166
|
+
] }) : variants.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "limbo-variants-empty", children: [
|
|
31167
|
+
/* @__PURE__ */ jsx("p", { children: "Esta imagen no tiene variantes aún." }),
|
|
31168
|
+
/* @__PURE__ */ jsx("small", { children: "Las variantes aparecerán aquí después de hacer recortes o redimensionados." })
|
|
31169
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
31170
|
+
/* @__PURE__ */ jsxs("div", { className: "limbo-variant-section", children: [
|
|
31171
|
+
/* @__PURE__ */ jsx("h3", { children: "Imagen original" }),
|
|
31172
|
+
/* @__PURE__ */ jsxs(
|
|
31173
|
+
"div",
|
|
31174
|
+
{
|
|
31175
|
+
onClick: handleViewOriginal,
|
|
31176
|
+
className: "limbo-variant-card limbo-variant-original cursor-pointer",
|
|
31177
|
+
children: [
|
|
31178
|
+
/* @__PURE__ */ jsx("div", { className: "limbo-variant-preview", children: /* @__PURE__ */ jsx("img", { src: image.url, alt: image.filename, loading: "lazy" }) }),
|
|
31179
|
+
/* @__PURE__ */ jsxs("div", { className: "limbo-variant-info", children: [
|
|
31180
|
+
/* @__PURE__ */ jsx("div", { className: "limbo-variant-name", children: image.filename }),
|
|
31181
|
+
/* @__PURE__ */ jsxs("div", { className: "limbo-variant-meta", children: [
|
|
31182
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
31183
|
+
image.width,
|
|
31184
|
+
"×",
|
|
31185
|
+
image.height
|
|
31186
|
+
] }),
|
|
31187
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
31188
|
+
Math.round(image.file_size / 1024),
|
|
31189
|
+
" KB"
|
|
31190
|
+
] })
|
|
31191
|
+
] })
|
|
31192
|
+
] }),
|
|
31193
|
+
/* @__PURE__ */ jsxs("div", { className: "limbo-variant-actions", children: [
|
|
31194
|
+
shouldShowSelectButton && /* @__PURE__ */ jsx(
|
|
31195
|
+
"button",
|
|
31196
|
+
{
|
|
31197
|
+
className: "btn btn-success btn-sm",
|
|
31198
|
+
onClick: (e) => {
|
|
31199
|
+
e.stopPropagation();
|
|
31200
|
+
onSelect?.(image);
|
|
31201
|
+
onClose?.();
|
|
31202
|
+
},
|
|
31203
|
+
title: "Seleccionar original",
|
|
31204
|
+
children: "Seleccionar"
|
|
31205
|
+
}
|
|
31206
|
+
),
|
|
31207
|
+
shouldShowCropButton && /* @__PURE__ */ jsx(
|
|
31208
|
+
"button",
|
|
31209
|
+
{
|
|
31210
|
+
className: "btn btn-crop btn-sm",
|
|
31211
|
+
onClick: (e) => {
|
|
31212
|
+
e.stopPropagation();
|
|
31213
|
+
handleCropOriginal();
|
|
31214
|
+
},
|
|
31215
|
+
title: "Recortar imagen",
|
|
31216
|
+
children: /* @__PURE__ */ jsx("span", { className: "icon icon-scissors-white icon--sm" })
|
|
31217
|
+
}
|
|
31218
|
+
),
|
|
31219
|
+
shouldShowCopyButton && /* @__PURE__ */ jsx(
|
|
31220
|
+
"button",
|
|
31221
|
+
{
|
|
31222
|
+
className: "btn btn-secondary btn-sm",
|
|
31223
|
+
onClick: (e) => {
|
|
31224
|
+
e.stopPropagation();
|
|
31225
|
+
handleCopyOriginalUrl();
|
|
31226
|
+
},
|
|
31227
|
+
title: "Copiar URL",
|
|
31228
|
+
children: /* @__PURE__ */ jsx("span", { className: "icon icon-copy-white icon--sm" })
|
|
31229
|
+
}
|
|
31230
|
+
),
|
|
31231
|
+
shouldShowDownloadButton && /* @__PURE__ */ jsx(
|
|
31232
|
+
"button",
|
|
31233
|
+
{
|
|
31234
|
+
className: "btn btn-primary btn-sm",
|
|
31235
|
+
onClick: (e) => {
|
|
31236
|
+
e.stopPropagation();
|
|
31237
|
+
handleDownloadOriginal();
|
|
31238
|
+
},
|
|
31239
|
+
title: "Descargar",
|
|
31240
|
+
children: /* @__PURE__ */ jsx("span", { className: "icon icon-download-white icon--sm" })
|
|
31241
|
+
}
|
|
31242
|
+
),
|
|
31243
|
+
shouldShowDeleteButton && onDelete && /* @__PURE__ */ jsx(
|
|
31244
|
+
"button",
|
|
31245
|
+
{
|
|
31246
|
+
className: "btn btn-danger btn-sm",
|
|
31247
|
+
onClick: (e) => {
|
|
31248
|
+
e.stopPropagation();
|
|
31249
|
+
handleDeleteOriginal();
|
|
31250
|
+
},
|
|
31251
|
+
title: "Eliminar imagen",
|
|
31252
|
+
children: /* @__PURE__ */ jsx("span", { className: "icon icon-close-small-white icon--sm m-[0!important]" })
|
|
31253
|
+
}
|
|
31254
|
+
)
|
|
31255
|
+
] })
|
|
31256
|
+
]
|
|
31257
|
+
}
|
|
31258
|
+
)
|
|
31259
|
+
] }),
|
|
31260
|
+
/* @__PURE__ */ jsxs("div", { className: "limbo-variant-section", children: [
|
|
31261
|
+
/* @__PURE__ */ jsxs("h3", { children: [
|
|
31262
|
+
"Variantes (",
|
|
31263
|
+
variants.length,
|
|
31264
|
+
")"
|
|
31265
|
+
] }),
|
|
31266
|
+
/* @__PURE__ */ jsx("div", { className: "limbo-variants-grid", children: variants.map((variant) => {
|
|
31267
|
+
const hasImageError = failedVariants.has(variant.id);
|
|
31268
|
+
return /* @__PURE__ */ jsxs(
|
|
31269
|
+
"div",
|
|
31270
|
+
{
|
|
31271
|
+
className: `limbo-variant-card ${!hasImageError ? "cursor-pointer" : ""}`,
|
|
31272
|
+
onClick: !hasImageError ? () => handleViewVariant(variant) : void 0,
|
|
31273
|
+
children: [
|
|
31274
|
+
/* @__PURE__ */ jsxs("div", { className: "limbo-variant-preview", children: [
|
|
31275
|
+
/* @__PURE__ */ jsx(
|
|
31276
|
+
"img",
|
|
31277
|
+
{
|
|
31278
|
+
src: variant.url,
|
|
31279
|
+
alt: variant.name || variant.filename || `Variante ${variant.id}`,
|
|
31280
|
+
loading: "lazy",
|
|
31281
|
+
onError: () => handleVariantImageError(variant.id),
|
|
31282
|
+
style: {
|
|
31283
|
+
display: hasImageError ? "none" : "block"
|
|
31284
|
+
}
|
|
31285
|
+
}
|
|
31286
|
+
),
|
|
31287
|
+
hasImageError && /* @__PURE__ */ jsx(
|
|
31288
|
+
"div",
|
|
31289
|
+
{
|
|
31290
|
+
className: "limbo-variant-error",
|
|
31291
|
+
style: {
|
|
31292
|
+
display: "flex",
|
|
31293
|
+
alignItems: "center",
|
|
31294
|
+
justifyContent: "center",
|
|
31295
|
+
height: "100px",
|
|
31296
|
+
backgroundColor: "#f5f5f5",
|
|
31297
|
+
color: "#666"
|
|
31298
|
+
},
|
|
31299
|
+
children: /* @__PURE__ */ jsx("span", { children: "Error al cargar preview" })
|
|
31300
|
+
}
|
|
31301
|
+
)
|
|
31302
|
+
] }),
|
|
31303
|
+
/* @__PURE__ */ jsxs("div", { className: "limbo-variant-info", children: [
|
|
31304
|
+
/* @__PURE__ */ jsx("div", { className: "limbo-variant-name", children: variant.name || variant.filename || `Variante ${variant.id}` }),
|
|
31305
|
+
/* @__PURE__ */ jsxs("div", { className: "limbo-variant-meta", children: [
|
|
31306
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
31307
|
+
variant.width,
|
|
31308
|
+
"×",
|
|
31309
|
+
variant.height
|
|
31310
|
+
] }),
|
|
31311
|
+
/* @__PURE__ */ jsx("span", { children: (variant.format || "jpg").toUpperCase() }),
|
|
31312
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
31313
|
+
Math.round((variant.file_size || 0) / 1024),
|
|
31314
|
+
" KB"
|
|
31315
|
+
] })
|
|
31316
|
+
] }),
|
|
31317
|
+
variant.crop_params && /* @__PURE__ */ jsxs("div", { className: "limbo-variant-crop-badge", children: [
|
|
31318
|
+
/* @__PURE__ */ jsx("span", { className: "icon icon-scissors icon--xs" }),
|
|
31319
|
+
" ",
|
|
31320
|
+
"Recortada"
|
|
31321
|
+
] })
|
|
31322
|
+
] }),
|
|
31323
|
+
/* @__PURE__ */ jsxs("div", { className: "limbo-variant-actions", children: [
|
|
31324
|
+
shouldShowSelectButton && /* @__PURE__ */ jsx(
|
|
31325
|
+
"button",
|
|
31326
|
+
{
|
|
31327
|
+
className: "btn btn-success btn-sm",
|
|
31328
|
+
onClick: (e) => {
|
|
31329
|
+
e.stopPropagation();
|
|
31330
|
+
handleSelectVariant(variant);
|
|
31331
|
+
},
|
|
31332
|
+
title: `Seleccionar variante ${variant.name || variant.filename || variant.id}`,
|
|
31333
|
+
children: "Seleccionar"
|
|
31334
|
+
}
|
|
31335
|
+
),
|
|
31336
|
+
shouldShowCopyButton && /* @__PURE__ */ jsx(
|
|
31337
|
+
"button",
|
|
31338
|
+
{
|
|
31339
|
+
className: "btn btn-secondary btn-sm",
|
|
31340
|
+
onClick: (e) => {
|
|
31341
|
+
e.stopPropagation();
|
|
31342
|
+
handleCopyVariantUrl(variant);
|
|
31343
|
+
},
|
|
31344
|
+
title: "Copiar URL",
|
|
31345
|
+
children: /* @__PURE__ */ jsx("span", { className: "icon icon-copy-white icon--sm" })
|
|
31346
|
+
}
|
|
31347
|
+
),
|
|
31348
|
+
shouldShowDownloadButton && /* @__PURE__ */ jsx(
|
|
31349
|
+
"button",
|
|
31350
|
+
{
|
|
31351
|
+
className: "btn btn-primary btn-sm",
|
|
31352
|
+
onClick: (e) => {
|
|
31353
|
+
e.stopPropagation();
|
|
31354
|
+
handleDownloadVariant(variant);
|
|
31355
|
+
},
|
|
31356
|
+
title: "Descargar",
|
|
31357
|
+
children: /* @__PURE__ */ jsx("span", { className: "icon icon-download-white icon--sm" })
|
|
31358
|
+
}
|
|
31359
|
+
),
|
|
31360
|
+
shouldShowDeleteButton && /* @__PURE__ */ jsx(
|
|
31361
|
+
"button",
|
|
31362
|
+
{
|
|
31363
|
+
className: "btn btn-danger btn-sm",
|
|
31364
|
+
onClick: (e) => {
|
|
31365
|
+
e.stopPropagation();
|
|
31366
|
+
handleDeleteVariant(variant);
|
|
31367
|
+
},
|
|
31368
|
+
title: "Eliminar variante",
|
|
31369
|
+
children: /* @__PURE__ */ jsx("span", { className: "icon icon-close-small-white icon--sm m-[0!important]" })
|
|
31370
|
+
}
|
|
31371
|
+
)
|
|
31372
|
+
] })
|
|
31373
|
+
]
|
|
31374
|
+
},
|
|
31375
|
+
variant.id
|
|
31376
|
+
);
|
|
31377
|
+
}) })
|
|
31378
|
+
] })
|
|
31379
|
+
] }) })
|
|
31380
|
+
]
|
|
31381
|
+
}
|
|
31382
|
+
)
|
|
31383
|
+
}
|
|
31384
|
+
);
|
|
31385
|
+
}
|
|
30455
31386
|
function ImageCard({
|
|
30456
31387
|
image,
|
|
30457
31388
|
onDelete,
|
|
@@ -30465,10 +31396,14 @@ function ImageCard({
|
|
|
30465
31396
|
download: true,
|
|
30466
31397
|
copy: true,
|
|
30467
31398
|
delete: true,
|
|
30468
|
-
crop: true
|
|
31399
|
+
crop: true,
|
|
31400
|
+
variants: true
|
|
31401
|
+
// Nueva acción para ver variantes
|
|
30469
31402
|
}
|
|
30470
31403
|
}) {
|
|
30471
31404
|
const [copied, setCopied] = useState(false);
|
|
31405
|
+
const [showVariants, setShowVariants] = useState(false);
|
|
31406
|
+
const [localVariantsCount, setLocalVariantsCount] = useState(image.variants_count || 0);
|
|
30472
31407
|
const { isMobile, isTouch } = useMobileDetection();
|
|
30473
31408
|
const accessibilityManager = window.limboCore?.accessibilityManager;
|
|
30474
31409
|
const config = window.limboCore?.config?.getGlobal() || {};
|
|
@@ -30484,21 +31419,41 @@ function ImageCard({
|
|
|
30484
31419
|
);
|
|
30485
31420
|
onDelete?.(image);
|
|
30486
31421
|
};
|
|
30487
|
-
const handleCopyUrl = (e) => {
|
|
31422
|
+
const handleCopyUrl = async (e) => {
|
|
30488
31423
|
e.stopPropagation();
|
|
30489
|
-
|
|
31424
|
+
let textToCopy = image.url || image.path;
|
|
31425
|
+
try {
|
|
31426
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
31427
|
+
await navigator.clipboard.writeText(textToCopy);
|
|
31428
|
+
} else {
|
|
31429
|
+
const textArea = document.createElement("textarea");
|
|
31430
|
+
textArea.value = textToCopy;
|
|
31431
|
+
textArea.style.position = "fixed";
|
|
31432
|
+
textArea.style.opacity = "0";
|
|
31433
|
+
document.body.appendChild(textArea);
|
|
31434
|
+
textArea.focus();
|
|
31435
|
+
textArea.select();
|
|
31436
|
+
document.execCommand("copy");
|
|
31437
|
+
document.body.removeChild(textArea);
|
|
31438
|
+
}
|
|
30490
31439
|
setCopied(true);
|
|
30491
31440
|
accessibilityManager?.announce(
|
|
30492
|
-
`URL de ${image.filename} copiada al portapapeles
|
|
31441
|
+
`URL de ${image.filename} copiada al portapapeles. Nota: La URL puede tener un tiempo de validez limitado.`
|
|
30493
31442
|
);
|
|
30494
31443
|
setTimeout(() => setCopied(false), 2e3);
|
|
30495
|
-
})
|
|
31444
|
+
} catch (err) {
|
|
31445
|
+
console.error("Error al copiar URL:", err);
|
|
31446
|
+
accessibilityManager?.announce(
|
|
31447
|
+
`Error al copiar URL de ${image.filename}. Inténtalo de nuevo.`
|
|
31448
|
+
);
|
|
31449
|
+
alert(`Error al copiar URL de ${image.filename}. Por favor, inténtalo de nuevo.`);
|
|
31450
|
+
}
|
|
30496
31451
|
};
|
|
30497
31452
|
const handleDownload = (e) => {
|
|
30498
31453
|
e.preventDefault();
|
|
30499
31454
|
e.stopPropagation();
|
|
30500
31455
|
accessibilityManager?.announce(`Descargando ${image.filename}`);
|
|
30501
|
-
fetch(image.path, { mode: "cors" }).then((resp) => resp.blob()).then((blob) => {
|
|
31456
|
+
fetch(image.url || image.path, { mode: "cors" }).then((resp) => resp.blob()).then((blob) => {
|
|
30502
31457
|
const url = window.URL.createObjectURL(blob);
|
|
30503
31458
|
const a = document.createElement("a");
|
|
30504
31459
|
a.href = url;
|
|
@@ -30513,9 +31468,22 @@ function ImageCard({
|
|
|
30513
31468
|
};
|
|
30514
31469
|
const handleCrop = (e) => {
|
|
30515
31470
|
e.stopPropagation();
|
|
30516
|
-
accessibilityManager?.announce(`
|
|
31471
|
+
accessibilityManager?.announce(`Editando imagen ${image.filename}`);
|
|
30517
31472
|
onCrop?.(image);
|
|
30518
31473
|
};
|
|
31474
|
+
const hasVariants = image.variants && image.variants.length > 0 || localVariantsCount > 0;
|
|
31475
|
+
const variantsCount = localVariantsCount;
|
|
31476
|
+
const handleVariantDeleted = React.useCallback(() => {
|
|
31477
|
+
setLocalVariantsCount((prev) => Math.max(0, prev - 1));
|
|
31478
|
+
}, []);
|
|
31479
|
+
React.useEffect(() => {
|
|
31480
|
+
setLocalVariantsCount(image.variants_count || 0);
|
|
31481
|
+
}, [image.variants_count]);
|
|
31482
|
+
const handleShowVariants = (e) => {
|
|
31483
|
+
e.stopPropagation();
|
|
31484
|
+
accessibilityManager?.announce(`Mostrando variantes de ${image.filename}`);
|
|
31485
|
+
setShowVariants(true);
|
|
31486
|
+
};
|
|
30519
31487
|
const handleSelect = (e) => {
|
|
30520
31488
|
e.stopPropagation();
|
|
30521
31489
|
accessibilityManager?.announce(
|
|
@@ -30525,6 +31493,7 @@ function ImageCard({
|
|
|
30525
31493
|
};
|
|
30526
31494
|
const shouldShowSelectButton = interaction.allowSelection && allowedActions.select && onSelect;
|
|
30527
31495
|
const shouldShowCropButton = interaction.allowCropping && allowedActions.crop && onCrop;
|
|
31496
|
+
const shouldShowVariantsButton = hasVariants && allowedActions.variants !== false;
|
|
30528
31497
|
const handleKeyDown = (e) => {
|
|
30529
31498
|
switch (e.key) {
|
|
30530
31499
|
case "Enter":
|
|
@@ -30560,194 +31529,274 @@ function ImageCard({
|
|
|
30560
31529
|
handleCrop(e);
|
|
30561
31530
|
}
|
|
30562
31531
|
break;
|
|
31532
|
+
case "v":
|
|
31533
|
+
case "V":
|
|
31534
|
+
if (allowedActions.variants) {
|
|
31535
|
+
e.preventDefault();
|
|
31536
|
+
handleShowVariants(e);
|
|
31537
|
+
}
|
|
31538
|
+
break;
|
|
30563
31539
|
}
|
|
30564
31540
|
};
|
|
30565
31541
|
const handleImageClick = () => {
|
|
30566
|
-
window.open(image.path, "_blank");
|
|
31542
|
+
window.open(image.url || image.path, "_blank");
|
|
30567
31543
|
};
|
|
30568
|
-
return /* @__PURE__ */ jsxs(
|
|
30569
|
-
|
|
30570
|
-
|
|
30571
|
-
|
|
30572
|
-
|
|
30573
|
-
|
|
30574
|
-
|
|
30575
|
-
|
|
30576
|
-
|
|
30577
|
-
|
|
30578
|
-
|
|
30579
|
-
|
|
30580
|
-
|
|
30581
|
-
|
|
30582
|
-
|
|
30583
|
-
|
|
30584
|
-
},
|
|
30585
|
-
children: [
|
|
30586
|
-
/* @__PURE__ */ jsxs(
|
|
30587
|
-
"div",
|
|
30588
|
-
{
|
|
30589
|
-
className: `limbo-image-actions ${isMobile ? "limbo-image-actions--mobile" : ""}`,
|
|
30590
|
-
children: [
|
|
30591
|
-
allowedActions.copy && /* @__PURE__ */ jsx(
|
|
30592
|
-
"button",
|
|
30593
|
-
{
|
|
30594
|
-
type: "button",
|
|
30595
|
-
title: copied ? "¡Copiado!" : "Copiar URL",
|
|
30596
|
-
className: `btn btn-copy border border-brand-blue-050/50 ${isMobile ? "btn--touch" : ""}`,
|
|
30597
|
-
onClick: handleCopyUrl,
|
|
30598
|
-
tabIndex: -1,
|
|
30599
|
-
style: {
|
|
30600
|
-
...isMobile && {
|
|
30601
|
-
width: "40px",
|
|
30602
|
-
height: "40px"
|
|
30603
|
-
}
|
|
30604
|
-
},
|
|
30605
|
-
children: /* @__PURE__ */ jsx(
|
|
30606
|
-
"span",
|
|
30607
|
-
{
|
|
30608
|
-
className: `icon ${copied ? "icon-copied-white" : "icon-copy-white"} icon--sm`,
|
|
30609
|
-
"aria-hidden": "true"
|
|
30610
|
-
}
|
|
30611
|
-
)
|
|
30612
|
-
}
|
|
30613
|
-
),
|
|
30614
|
-
shouldShowSelectButton && /* @__PURE__ */ jsx(
|
|
30615
|
-
"button",
|
|
30616
|
-
{
|
|
30617
|
-
type: "button",
|
|
30618
|
-
title: "Seleccionar imagen",
|
|
30619
|
-
className: `btn btn-success border border-brand-blue-050/50 ${isMobile ? "btn--touch" : ""}`,
|
|
30620
|
-
onClick: handleSelect,
|
|
30621
|
-
tabIndex: -1,
|
|
30622
|
-
style: {
|
|
30623
|
-
...isMobile && {
|
|
30624
|
-
width: "40px",
|
|
30625
|
-
height: "40px"
|
|
30626
|
-
}
|
|
30627
|
-
},
|
|
30628
|
-
children: /* @__PURE__ */ jsx(
|
|
30629
|
-
"span",
|
|
30630
|
-
{
|
|
30631
|
-
className: "icon icon-tick-white icon--sm",
|
|
30632
|
-
"aria-hidden": "true"
|
|
30633
|
-
}
|
|
30634
|
-
)
|
|
30635
|
-
}
|
|
30636
|
-
),
|
|
30637
|
-
shouldShowCropButton && /* @__PURE__ */ jsx(
|
|
30638
|
-
"button",
|
|
30639
|
-
{
|
|
30640
|
-
type: "button",
|
|
30641
|
-
title: "Editar imagen",
|
|
30642
|
-
className: `btn btn-crop border border-brand-blue-050/50 ${isMobile ? "btn--touch" : ""}`,
|
|
30643
|
-
onClick: handleCrop,
|
|
30644
|
-
tabIndex: -1,
|
|
30645
|
-
style: {
|
|
30646
|
-
...isMobile && {
|
|
30647
|
-
width: "40px",
|
|
30648
|
-
height: "40px"
|
|
30649
|
-
}
|
|
30650
|
-
},
|
|
30651
|
-
children: /* @__PURE__ */ jsx(
|
|
30652
|
-
"span",
|
|
30653
|
-
{
|
|
30654
|
-
className: "icon icon-scissors-white icon--sm",
|
|
30655
|
-
"aria-hidden": "true"
|
|
30656
|
-
}
|
|
30657
|
-
)
|
|
30658
|
-
}
|
|
30659
|
-
),
|
|
30660
|
-
allowedActions.download && /* @__PURE__ */ jsx(
|
|
30661
|
-
"button",
|
|
30662
|
-
{
|
|
30663
|
-
type: "button",
|
|
30664
|
-
title: "Descargar",
|
|
30665
|
-
className: `btn btn-download border border-brand-blue-050/50 ${isMobile ? "btn--touch" : ""}`,
|
|
30666
|
-
onClick: handleDownload,
|
|
30667
|
-
tabIndex: -1,
|
|
30668
|
-
style: {
|
|
30669
|
-
...isMobile && {
|
|
30670
|
-
width: "40px",
|
|
30671
|
-
height: "40px"
|
|
30672
|
-
}
|
|
30673
|
-
},
|
|
30674
|
-
children: /* @__PURE__ */ jsx(
|
|
30675
|
-
"span",
|
|
30676
|
-
{
|
|
30677
|
-
className: "icon icon-download-white icon--sm",
|
|
30678
|
-
"aria-hidden": "true"
|
|
30679
|
-
}
|
|
30680
|
-
)
|
|
30681
|
-
}
|
|
30682
|
-
),
|
|
30683
|
-
allowedActions.delete && onDelete && /* @__PURE__ */ jsx(
|
|
30684
|
-
"button",
|
|
30685
|
-
{
|
|
30686
|
-
onClick: handleDelete,
|
|
30687
|
-
disabled: isDeleting,
|
|
30688
|
-
className: `btn btn-delete border border-brand-blue-050/50 ${isMobile ? "btn--touch" : ""}`,
|
|
30689
|
-
title: "Eliminar imagen",
|
|
30690
|
-
tabIndex: -1,
|
|
30691
|
-
style: {
|
|
30692
|
-
...isMobile && {
|
|
30693
|
-
width: "40px",
|
|
30694
|
-
height: "40px"
|
|
30695
|
-
}
|
|
30696
|
-
},
|
|
30697
|
-
children: isDeleting ? "…" : /* @__PURE__ */ jsx(
|
|
30698
|
-
"span",
|
|
30699
|
-
{
|
|
30700
|
-
className: "icon icon-close-small-white icon--sm",
|
|
30701
|
-
"aria-hidden": "true"
|
|
30702
|
-
}
|
|
30703
|
-
)
|
|
30704
|
-
}
|
|
30705
|
-
)
|
|
30706
|
-
]
|
|
31544
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
31545
|
+
/* @__PURE__ */ jsxs(
|
|
31546
|
+
"div",
|
|
31547
|
+
{
|
|
31548
|
+
className: `limbo-image-card flex flex-col items-center cursor-pointer relative transition ${isDeleting ? "opacity-50" : ""} ${isMobile ? "limbo-image-card--mobile" : ""}`,
|
|
31549
|
+
onClick: handleImageClick,
|
|
31550
|
+
onKeyDown: handleKeyDown,
|
|
31551
|
+
title: isMobile ? "Toque para ver imagen" : "Haga click en la imagen para ver preview en nueva pestaña (Enter/Space para abrir, D descargar, C copiar, Delete eliminar, X recortar)",
|
|
31552
|
+
role: "button",
|
|
31553
|
+
tabIndex: 0,
|
|
31554
|
+
"aria-label": `Imagen ${image.filename}. ${image.width}x${image.height} px`,
|
|
31555
|
+
style: {
|
|
31556
|
+
// Optimización para touch
|
|
31557
|
+
...isTouch && {
|
|
31558
|
+
touchAction: "manipulation",
|
|
31559
|
+
WebkitTapHighlightColor: "transparent"
|
|
30707
31560
|
}
|
|
30708
|
-
|
|
30709
|
-
|
|
30710
|
-
|
|
30711
|
-
|
|
30712
|
-
|
|
30713
|
-
|
|
30714
|
-
|
|
30715
|
-
|
|
30716
|
-
|
|
30717
|
-
|
|
30718
|
-
|
|
30719
|
-
|
|
30720
|
-
|
|
30721
|
-
|
|
30722
|
-
|
|
30723
|
-
|
|
30724
|
-
|
|
31561
|
+
},
|
|
31562
|
+
children: [
|
|
31563
|
+
/* @__PURE__ */ jsxs(
|
|
31564
|
+
"div",
|
|
31565
|
+
{
|
|
31566
|
+
className: `limbo-image-actions ${isMobile ? "limbo-image-actions--mobile" : ""}`,
|
|
31567
|
+
children: [
|
|
31568
|
+
allowedActions.copy && /* @__PURE__ */ jsx(
|
|
31569
|
+
"button",
|
|
31570
|
+
{
|
|
31571
|
+
type: "button",
|
|
31572
|
+
title: copied ? "¡Copiado!" : "Copiar URL",
|
|
31573
|
+
className: `btn btn-copy border border-brand-blue-050/50 ${isMobile ? "btn--touch" : ""}`,
|
|
31574
|
+
onClick: handleCopyUrl,
|
|
31575
|
+
tabIndex: -1,
|
|
31576
|
+
style: {
|
|
31577
|
+
...isMobile && {
|
|
31578
|
+
width: "40px",
|
|
31579
|
+
height: "40px"
|
|
31580
|
+
}
|
|
31581
|
+
},
|
|
31582
|
+
children: /* @__PURE__ */ jsx(
|
|
31583
|
+
"span",
|
|
31584
|
+
{
|
|
31585
|
+
className: `icon ${copied ? "icon-copied-white" : "icon-copy-white"} icon--sm`,
|
|
31586
|
+
"aria-hidden": "true"
|
|
31587
|
+
}
|
|
31588
|
+
)
|
|
31589
|
+
}
|
|
31590
|
+
),
|
|
31591
|
+
shouldShowSelectButton && /* @__PURE__ */ jsx(
|
|
31592
|
+
"button",
|
|
31593
|
+
{
|
|
31594
|
+
type: "button",
|
|
31595
|
+
title: "Seleccionar imagen",
|
|
31596
|
+
className: `btn btn-success border border-brand-blue-050/50 ${isMobile ? "btn--touch" : ""}`,
|
|
31597
|
+
onClick: handleSelect,
|
|
31598
|
+
tabIndex: -1,
|
|
31599
|
+
style: {
|
|
31600
|
+
...isMobile && {
|
|
31601
|
+
width: "40px",
|
|
31602
|
+
height: "40px"
|
|
31603
|
+
}
|
|
31604
|
+
},
|
|
31605
|
+
children: /* @__PURE__ */ jsx(
|
|
31606
|
+
"span",
|
|
31607
|
+
{
|
|
31608
|
+
className: "icon icon-tick-white icon--sm",
|
|
31609
|
+
"aria-hidden": "true"
|
|
31610
|
+
}
|
|
31611
|
+
)
|
|
31612
|
+
}
|
|
31613
|
+
),
|
|
31614
|
+
shouldShowCropButton && /* @__PURE__ */ jsx(
|
|
31615
|
+
"button",
|
|
31616
|
+
{
|
|
31617
|
+
type: "button",
|
|
31618
|
+
title: "Editar imagen",
|
|
31619
|
+
className: `btn btn-crop border border-brand-blue-050/50 ${isMobile ? "btn--touch" : ""}`,
|
|
31620
|
+
onClick: handleCrop,
|
|
31621
|
+
tabIndex: -1,
|
|
31622
|
+
style: {
|
|
31623
|
+
...isMobile && {
|
|
31624
|
+
width: "40px",
|
|
31625
|
+
height: "40px"
|
|
31626
|
+
}
|
|
31627
|
+
},
|
|
31628
|
+
children: /* @__PURE__ */ jsx(
|
|
31629
|
+
"span",
|
|
31630
|
+
{
|
|
31631
|
+
className: "icon icon-scissors-white icon--sm",
|
|
31632
|
+
"aria-hidden": "true"
|
|
31633
|
+
}
|
|
31634
|
+
)
|
|
31635
|
+
}
|
|
31636
|
+
),
|
|
31637
|
+
shouldShowVariantsButton && /* @__PURE__ */ jsxs(
|
|
31638
|
+
"button",
|
|
31639
|
+
{
|
|
31640
|
+
type: "button",
|
|
31641
|
+
title: `Ver ${variantsCount} variante${variantsCount !== 1 ? "s" : ""}`,
|
|
31642
|
+
className: `btn btn-variants border border-brand-blue-050/50 ${isMobile ? "btn--touch" : ""}`,
|
|
31643
|
+
onClick: handleShowVariants,
|
|
31644
|
+
tabIndex: -1,
|
|
31645
|
+
style: {
|
|
31646
|
+
...isMobile && {
|
|
31647
|
+
width: "40px",
|
|
31648
|
+
height: "40px"
|
|
31649
|
+
},
|
|
31650
|
+
position: "relative"
|
|
31651
|
+
},
|
|
31652
|
+
children: [
|
|
31653
|
+
/* @__PURE__ */ jsx(
|
|
31654
|
+
"span",
|
|
31655
|
+
{
|
|
31656
|
+
className: "icon icon-album-menu-white icon--sm",
|
|
31657
|
+
"aria-hidden": "true"
|
|
31658
|
+
}
|
|
31659
|
+
),
|
|
31660
|
+
variantsCount > 0 && /* @__PURE__ */ jsx(
|
|
31661
|
+
"span",
|
|
31662
|
+
{
|
|
31663
|
+
className: "variants-count-badge",
|
|
31664
|
+
style: {
|
|
31665
|
+
position: "absolute",
|
|
31666
|
+
top: "-8px",
|
|
31667
|
+
right: "-8px",
|
|
31668
|
+
background: "#dc2626",
|
|
31669
|
+
color: "white",
|
|
31670
|
+
borderRadius: "50%",
|
|
31671
|
+
minWidth: "20px",
|
|
31672
|
+
height: "20px",
|
|
31673
|
+
fontSize: "12px",
|
|
31674
|
+
fontWeight: "700",
|
|
31675
|
+
display: "flex",
|
|
31676
|
+
alignItems: "center",
|
|
31677
|
+
justifyContent: "center",
|
|
31678
|
+
lineHeight: "1",
|
|
31679
|
+
border: "2px solid white",
|
|
31680
|
+
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.4)",
|
|
31681
|
+
zIndex: "999"
|
|
31682
|
+
},
|
|
31683
|
+
children: variantsCount
|
|
31684
|
+
}
|
|
31685
|
+
)
|
|
31686
|
+
]
|
|
31687
|
+
}
|
|
31688
|
+
),
|
|
31689
|
+
allowedActions.download && /* @__PURE__ */ jsx(
|
|
31690
|
+
"button",
|
|
31691
|
+
{
|
|
31692
|
+
type: "button",
|
|
31693
|
+
title: "Descargar",
|
|
31694
|
+
className: `btn btn-download border border-brand-blue-050/50 ${isMobile ? "btn--touch" : ""}`,
|
|
31695
|
+
onClick: handleDownload,
|
|
31696
|
+
tabIndex: -1,
|
|
31697
|
+
style: {
|
|
31698
|
+
...isMobile && {
|
|
31699
|
+
width: "40px",
|
|
31700
|
+
height: "40px"
|
|
31701
|
+
}
|
|
31702
|
+
},
|
|
31703
|
+
children: /* @__PURE__ */ jsx(
|
|
31704
|
+
"span",
|
|
31705
|
+
{
|
|
31706
|
+
className: "icon icon-download-white icon--sm",
|
|
31707
|
+
"aria-hidden": "true"
|
|
31708
|
+
}
|
|
31709
|
+
)
|
|
31710
|
+
}
|
|
31711
|
+
),
|
|
31712
|
+
allowedActions.delete && onDelete && /* @__PURE__ */ jsx(
|
|
31713
|
+
"button",
|
|
31714
|
+
{
|
|
31715
|
+
onClick: handleDelete,
|
|
31716
|
+
disabled: isDeleting,
|
|
31717
|
+
className: `btn btn-delete border border-brand-blue-050/50 ${isMobile ? "btn--touch" : ""}`,
|
|
31718
|
+
title: "Eliminar imagen",
|
|
31719
|
+
tabIndex: -1,
|
|
31720
|
+
style: {
|
|
31721
|
+
...isMobile && {
|
|
31722
|
+
width: "40px",
|
|
31723
|
+
height: "40px"
|
|
31724
|
+
}
|
|
31725
|
+
},
|
|
31726
|
+
children: isDeleting ? "…" : /* @__PURE__ */ jsx(
|
|
31727
|
+
"span",
|
|
31728
|
+
{
|
|
31729
|
+
className: "icon icon-close-small-white icon--sm",
|
|
31730
|
+
"aria-hidden": "true"
|
|
31731
|
+
}
|
|
31732
|
+
)
|
|
31733
|
+
}
|
|
31734
|
+
)
|
|
31735
|
+
]
|
|
30725
31736
|
}
|
|
30726
|
-
|
|
30727
|
-
|
|
30728
|
-
|
|
30729
|
-
|
|
30730
|
-
|
|
30731
|
-
|
|
30732
|
-
|
|
30733
|
-
|
|
30734
|
-
|
|
30735
|
-
|
|
30736
|
-
|
|
30737
|
-
|
|
30738
|
-
|
|
30739
|
-
|
|
30740
|
-
|
|
30741
|
-
|
|
30742
|
-
|
|
31737
|
+
),
|
|
31738
|
+
/* @__PURE__ */ jsx(
|
|
31739
|
+
"img",
|
|
31740
|
+
{
|
|
31741
|
+
src: image.url || image.path,
|
|
31742
|
+
alt: image.filename,
|
|
31743
|
+
className: "w-full object-cover rounded aspect-square",
|
|
31744
|
+
sizes: `height: ${thumbnailSize * 6}px,width: ${thumbnailSize * 6}px`,
|
|
31745
|
+
draggable: false,
|
|
31746
|
+
style: {
|
|
31747
|
+
// Better responsive behavior for images
|
|
31748
|
+
...isMobile && {
|
|
31749
|
+
minHeight: `${Math.max(thumbnailSize * 3, 80)}px`,
|
|
31750
|
+
height: "auto",
|
|
31751
|
+
aspectRatio: "1 / 1",
|
|
31752
|
+
objectFit: "cover"
|
|
31753
|
+
}
|
|
30743
31754
|
}
|
|
30744
|
-
}
|
|
30745
|
-
|
|
30746
|
-
|
|
30747
|
-
|
|
30748
|
-
|
|
30749
|
-
|
|
30750
|
-
|
|
31755
|
+
}
|
|
31756
|
+
),
|
|
31757
|
+
/* @__PURE__ */ jsx(
|
|
31758
|
+
"span",
|
|
31759
|
+
{
|
|
31760
|
+
className: `text-xs mt-1 truncate w-full text-center limbo-image-card-name ${isMobile ? "limbo-image-card-name--mobile" : ""}`,
|
|
31761
|
+
style: {
|
|
31762
|
+
// Better text visibility on mobile
|
|
31763
|
+
...isMobile && {
|
|
31764
|
+
fontSize: "0.75rem",
|
|
31765
|
+
lineHeight: "1.2",
|
|
31766
|
+
maxWidth: "100%",
|
|
31767
|
+
padding: "2px 4px",
|
|
31768
|
+
backgroundColor: "rgba(255, 255, 255, 0.9)",
|
|
31769
|
+
borderRadius: "4px",
|
|
31770
|
+
color: "#333",
|
|
31771
|
+
fontWeight: "500"
|
|
31772
|
+
}
|
|
31773
|
+
},
|
|
31774
|
+
children: image.filename
|
|
31775
|
+
}
|
|
31776
|
+
)
|
|
31777
|
+
]
|
|
31778
|
+
}
|
|
31779
|
+
),
|
|
31780
|
+
/* @__PURE__ */ jsx(
|
|
31781
|
+
ImageVariantsModal,
|
|
31782
|
+
{
|
|
31783
|
+
image,
|
|
31784
|
+
isOpen: showVariants,
|
|
31785
|
+
onClose: () => setShowVariants(false),
|
|
31786
|
+
onSelect,
|
|
31787
|
+
onDelete,
|
|
31788
|
+
onCrop,
|
|
31789
|
+
onVariantDeleted: handleVariantDeleted,
|
|
31790
|
+
allowedActions: {
|
|
31791
|
+
select: allowedActions.select,
|
|
31792
|
+
download: allowedActions.download,
|
|
31793
|
+
copy: allowedActions.copy,
|
|
31794
|
+
delete: allowedActions.delete,
|
|
31795
|
+
crop: allowedActions.crop
|
|
31796
|
+
}
|
|
31797
|
+
}
|
|
31798
|
+
)
|
|
31799
|
+
] });
|
|
30751
31800
|
}
|
|
30752
31801
|
function Loader({ text = "Cargando..." }) {
|
|
30753
31802
|
return /* @__PURE__ */ jsxs(
|
|
@@ -30851,7 +31900,9 @@ function Gallery({
|
|
|
30851
31900
|
download: true,
|
|
30852
31901
|
copy: true,
|
|
30853
31902
|
delete: true,
|
|
30854
|
-
crop: true
|
|
31903
|
+
crop: true,
|
|
31904
|
+
variants: true
|
|
31905
|
+
// Nueva acción para ver variantes
|
|
30855
31906
|
}
|
|
30856
31907
|
}) {
|
|
30857
31908
|
const [filters, setFilters] = useState({
|
|
@@ -31110,240 +32161,21 @@ function TabUpload({ file, setFile, previewUrl, setPreviewUrl, fileInputRef, onU
|
|
|
31110
32161
|
}
|
|
31111
32162
|
);
|
|
31112
32163
|
}
|
|
31113
|
-
|
|
31114
|
-
|
|
31115
|
-
|
|
31116
|
-
this.tokenExpiry = null;
|
|
31117
|
-
this.refreshPromise = null;
|
|
31118
|
-
}
|
|
31119
|
-
/**
|
|
31120
|
-
* Authenticate and get JWT token
|
|
31121
|
-
* Maps to: POST /auth/token
|
|
31122
|
-
*/
|
|
31123
|
-
async authenticate(apiKey, publicKey, prod = false) {
|
|
31124
|
-
try {
|
|
31125
|
-
const response = await callApi({
|
|
31126
|
-
apiKey: "temp",
|
|
31127
|
-
// We don't use this for auth endpoint
|
|
31128
|
-
endpoint: "/token",
|
|
31129
|
-
method: "POST",
|
|
31130
|
-
body: {
|
|
31131
|
-
api_key: apiKey,
|
|
31132
|
-
public_key: publicKey
|
|
31133
|
-
},
|
|
31134
|
-
prod,
|
|
31135
|
-
basePath: "/auth",
|
|
31136
|
-
customHeaders: {
|
|
31137
|
-
// Override Authorization for auth endpoint
|
|
31138
|
-
Authorization: void 0
|
|
31139
|
-
}
|
|
31140
|
-
});
|
|
31141
|
-
if (response && response.token) {
|
|
31142
|
-
this.currentToken = response.token;
|
|
31143
|
-
this.tokenExpiry = Date.now() + response.expires_in * 1e3 - 3e4;
|
|
31144
|
-
return {
|
|
31145
|
-
success: true,
|
|
31146
|
-
token: response.token,
|
|
31147
|
-
expires_in: response.expires_in,
|
|
31148
|
-
portal: response.portal,
|
|
31149
|
-
policies: response.policies
|
|
31150
|
-
};
|
|
31151
|
-
}
|
|
31152
|
-
throw new Error("Invalid authentication response");
|
|
31153
|
-
} catch (error) {
|
|
31154
|
-
console.error("Authentication failed:", error);
|
|
31155
|
-
return {
|
|
31156
|
-
success: false,
|
|
31157
|
-
error: error.message
|
|
31158
|
-
};
|
|
31159
|
-
}
|
|
31160
|
-
}
|
|
31161
|
-
/**
|
|
31162
|
-
* Get current valid token (auto-refresh if needed)
|
|
31163
|
-
*/
|
|
31164
|
-
async getValidToken(apiKey, publicKey, prod = false) {
|
|
31165
|
-
if (!this.currentToken || Date.now() > this.tokenExpiry) {
|
|
31166
|
-
if (!this.refreshPromise) {
|
|
31167
|
-
this.refreshPromise = this.authenticate(apiKey, publicKey, prod);
|
|
31168
|
-
}
|
|
31169
|
-
const result = await this.refreshPromise;
|
|
31170
|
-
this.refreshPromise = null;
|
|
31171
|
-
if (!result.success) {
|
|
31172
|
-
throw new Error(`Authentication failed: ${result.error}`);
|
|
31173
|
-
}
|
|
31174
|
-
}
|
|
31175
|
-
return this.currentToken;
|
|
31176
|
-
}
|
|
31177
|
-
/**
|
|
31178
|
-
* Check if current token is valid
|
|
31179
|
-
*/
|
|
31180
|
-
isTokenValid() {
|
|
31181
|
-
return this.currentToken && Date.now() < this.tokenExpiry;
|
|
31182
|
-
}
|
|
31183
|
-
/**
|
|
31184
|
-
* Clear current token (logout)
|
|
31185
|
-
*/
|
|
31186
|
-
clearToken() {
|
|
31187
|
-
this.currentToken = null;
|
|
31188
|
-
this.tokenExpiry = null;
|
|
31189
|
-
this.refreshPromise = null;
|
|
31190
|
-
}
|
|
31191
|
-
/**
|
|
31192
|
-
* Get token info
|
|
31193
|
-
*/
|
|
31194
|
-
getTokenInfo() {
|
|
31195
|
-
if (!this.currentToken) return null;
|
|
31196
|
-
return {
|
|
31197
|
-
hasToken: !!this.currentToken,
|
|
31198
|
-
isValid: this.isTokenValid(),
|
|
31199
|
-
expiresAt: new Date(this.tokenExpiry),
|
|
31200
|
-
expiresIn: Math.max(0, this.tokenExpiry - Date.now())
|
|
31201
|
-
};
|
|
31202
|
-
}
|
|
31203
|
-
}
|
|
31204
|
-
const API_URLS = {
|
|
31205
|
-
DEV: "https://led-dev-limbo-dev.eu.els.local",
|
|
31206
|
-
// PREPRODUCCIÓN - Updated URL
|
|
31207
|
-
// DEV: "http://localhost", // LOCAL - Para desarrollo local
|
|
31208
|
-
PROD: "https://limbo.lefebvre.com"
|
|
31209
|
-
};
|
|
31210
|
-
let globalConfig = {
|
|
31211
|
-
// Legacy configuration
|
|
31212
|
-
apiKey: null,
|
|
31213
|
-
publicKey: null,
|
|
31214
|
-
prod: false,
|
|
31215
|
-
// New JWT v2 configuration
|
|
31216
|
-
auth: {
|
|
31217
|
-
apiKey: null,
|
|
31218
|
-
publicKey: null,
|
|
31219
|
-
authMode: "jwt",
|
|
31220
|
-
// "jwt" | "legacy"
|
|
31221
|
-
portal: null,
|
|
31222
|
-
tokenStorage: "memory"
|
|
31223
|
-
}
|
|
31224
|
-
};
|
|
31225
|
-
let authServiceInstance = null;
|
|
31226
|
-
function configureApiClient(config) {
|
|
31227
|
-
if (config.auth) {
|
|
31228
|
-
globalConfig.auth = { ...globalConfig.auth, ...config.auth };
|
|
31229
|
-
globalConfig.prod = config.prod || globalConfig.prod;
|
|
31230
|
-
if (globalConfig.auth.authMode === "jwt" && globalConfig.auth.apiKey) {
|
|
31231
|
-
if (!authServiceInstance) {
|
|
31232
|
-
authServiceInstance = new AuthService();
|
|
31233
|
-
}
|
|
31234
|
-
}
|
|
31235
|
-
} else {
|
|
31236
|
-
globalConfig = {
|
|
31237
|
-
...globalConfig,
|
|
31238
|
-
...config,
|
|
31239
|
-
auth: {
|
|
31240
|
-
...globalConfig.auth,
|
|
31241
|
-
authMode: config.apiKey ? "legacy" : "jwt"
|
|
31242
|
-
}
|
|
31243
|
-
};
|
|
31244
|
-
}
|
|
31245
|
-
}
|
|
31246
|
-
function getBaseUrl({ prod = false } = {}) {
|
|
31247
|
-
return prod ? API_URLS.PROD : API_URLS.DEV;
|
|
31248
|
-
}
|
|
31249
|
-
async function getHeaders({ isFormData = false, useJWT = true, customHeaders = {} } = {}) {
|
|
31250
|
-
const headers = {
|
|
31251
|
-
"Content-Type": "application/json"
|
|
31252
|
-
};
|
|
31253
|
-
if (useJWT) {
|
|
31254
|
-
const authConfig = globalConfig.auth;
|
|
31255
|
-
if (authConfig.authMode === "jwt" && authConfig.apiKey) {
|
|
31256
|
-
try {
|
|
31257
|
-
if (!authServiceInstance) {
|
|
31258
|
-
authServiceInstance = new AuthService();
|
|
31259
|
-
}
|
|
31260
|
-
const token = await authServiceInstance.getValidToken(
|
|
31261
|
-
authConfig.apiKey,
|
|
31262
|
-
authConfig.publicKey || globalConfig.publicKey,
|
|
31263
|
-
globalConfig.prod
|
|
31264
|
-
);
|
|
31265
|
-
headers.Authorization = `Bearer ${token}`;
|
|
31266
|
-
} catch (error) {
|
|
31267
|
-
console.error("JWT authentication failed:", error);
|
|
31268
|
-
throw new Error("Authentication required");
|
|
31269
|
-
}
|
|
31270
|
-
} else if (authConfig.authMode === "legacy" && globalConfig.apiKey) {
|
|
31271
|
-
headers["X-API-Key"] = globalConfig.apiKey;
|
|
31272
|
-
}
|
|
31273
|
-
}
|
|
31274
|
-
if (isFormData) {
|
|
31275
|
-
delete headers["Content-Type"];
|
|
31276
|
-
}
|
|
31277
|
-
return { ...headers, ...customHeaders };
|
|
31278
|
-
}
|
|
31279
|
-
function configureJWTFromConfigManager(configManager) {
|
|
31280
|
-
const authConfig = configManager.getAuthConfig();
|
|
31281
|
-
configureApiClient({
|
|
31282
|
-
auth: authConfig,
|
|
31283
|
-
prod: configManager.get("prod") || false
|
|
31284
|
-
});
|
|
31285
|
-
}
|
|
31286
|
-
async function callApi({
|
|
31287
|
-
endpoint,
|
|
31288
|
-
method = "GET",
|
|
31289
|
-
body = null,
|
|
31290
|
-
prod = false,
|
|
31291
|
-
basePath = "",
|
|
31292
|
-
customHeaders = {},
|
|
31293
|
-
isFormData = false,
|
|
31294
|
-
useJWT = true
|
|
31295
|
-
// New parameter to control JWT usage
|
|
31296
|
-
}) {
|
|
31297
|
-
try {
|
|
31298
|
-
const useProd = prod || globalConfig.prod;
|
|
31299
|
-
const headers = await getHeaders({
|
|
31300
|
-
isFormData,
|
|
31301
|
-
useJWT,
|
|
31302
|
-
customHeaders
|
|
31303
|
-
});
|
|
31304
|
-
const res = await fetch(`${getBaseUrl({ prod: useProd })}${basePath}${endpoint}`, {
|
|
31305
|
-
method,
|
|
31306
|
-
headers,
|
|
31307
|
-
body: body ? isFormData ? body : JSON.stringify(body) : void 0
|
|
31308
|
-
});
|
|
31309
|
-
if (!res.ok) {
|
|
31310
|
-
let errorMessage = `HTTP ${res.status}: ${res.statusText}`;
|
|
31311
|
-
try {
|
|
31312
|
-
const result = await res.json();
|
|
31313
|
-
if (result.error) {
|
|
31314
|
-
errorMessage = `${result.error.code}: ${result.error.message}`;
|
|
31315
|
-
} else if (result.message) {
|
|
31316
|
-
errorMessage = result.message;
|
|
31317
|
-
}
|
|
31318
|
-
} catch {
|
|
31319
|
-
}
|
|
31320
|
-
throw new Error(errorMessage);
|
|
31321
|
-
}
|
|
31322
|
-
return res.json();
|
|
31323
|
-
} catch (err) {
|
|
31324
|
-
throw new Error(`API Error: ${err.message}`);
|
|
31325
|
-
}
|
|
32164
|
+
const BASE_PATH$3 = "/api";
|
|
32165
|
+
function getAiServices(prod = false) {
|
|
32166
|
+
return callApi({ endpoint: "/ai/services", prod, basePath: BASE_PATH$3 });
|
|
31326
32167
|
}
|
|
31327
|
-
|
|
31328
|
-
|
|
31329
|
-
return callApi({ endpoint: "/ai/services", prod, basePath: BASE_PATH$4 });
|
|
31330
|
-
}
|
|
31331
|
-
function generateAiImage(apiKey, params, prod = false) {
|
|
31332
|
-
return callApi({ endpoint: "/ai/generate", method: "POST", body: params, prod, basePath: BASE_PATH$4 });
|
|
32168
|
+
function generateAiImage(params, prod = false) {
|
|
32169
|
+
return callApi({ endpoint: "/ai/generate", method: "POST", body: params, prod, basePath: BASE_PATH$3 });
|
|
31333
32170
|
}
|
|
31334
32171
|
const CACHE_TTL$4 = 24 * 60 * 60 * 1e3;
|
|
31335
32172
|
const cache$4 = /* @__PURE__ */ new Map();
|
|
31336
|
-
function useAiServices(
|
|
32173
|
+
function useAiServices(prod = false) {
|
|
31337
32174
|
const [services, setServices] = useState([]);
|
|
31338
32175
|
const [loading, setLoading] = useState(true);
|
|
31339
32176
|
const [error, setError] = useState(null);
|
|
31340
32177
|
useEffect(() => {
|
|
31341
|
-
|
|
31342
|
-
setError("API Key no proporcionada");
|
|
31343
|
-
setLoading(false);
|
|
31344
|
-
return;
|
|
31345
|
-
}
|
|
31346
|
-
const cacheKey = `${apiKey}-${prod}`;
|
|
32178
|
+
const cacheKey = `ai-services-${prod}`;
|
|
31347
32179
|
const cached = cache$4.get(cacheKey);
|
|
31348
32180
|
const now = Date.now();
|
|
31349
32181
|
if (cached && now - cached.timestamp < CACHE_TTL$4) {
|
|
@@ -31354,9 +32186,9 @@ function useAiServices(apiKey, prod = false) {
|
|
|
31354
32186
|
let isMounted = true;
|
|
31355
32187
|
const fetchServices = async () => {
|
|
31356
32188
|
try {
|
|
31357
|
-
const data = await getAiServices(
|
|
32189
|
+
const data = await getAiServices(prod);
|
|
31358
32190
|
if (!isMounted) return;
|
|
31359
|
-
const result = data
|
|
32191
|
+
const result = data?.data?.images || [];
|
|
31360
32192
|
setServices(result);
|
|
31361
32193
|
cache$4.set(cacheKey, { data: result, timestamp: now });
|
|
31362
32194
|
} catch (err) {
|
|
@@ -31369,14 +32201,14 @@ function useAiServices(apiKey, prod = false) {
|
|
|
31369
32201
|
return () => {
|
|
31370
32202
|
isMounted = false;
|
|
31371
32203
|
};
|
|
31372
|
-
}, [
|
|
32204
|
+
}, [prod]);
|
|
31373
32205
|
const invalidateCache = () => {
|
|
31374
|
-
cache$4.delete(
|
|
32206
|
+
cache$4.delete(`ai-services-${prod}`);
|
|
31375
32207
|
};
|
|
31376
32208
|
return { services, loading, error, invalidateCache };
|
|
31377
32209
|
}
|
|
31378
|
-
const BASE_PATH$
|
|
31379
|
-
function getImageParams(
|
|
32210
|
+
const BASE_PATH$2 = "/api/atenea";
|
|
32211
|
+
function getImageParams({ services = null, form = false } = {}, prod = false) {
|
|
31380
32212
|
const params = {};
|
|
31381
32213
|
if (services) params.services = services;
|
|
31382
32214
|
if (form !== false) params.form = form;
|
|
@@ -31385,19 +32217,19 @@ function getImageParams(apiKey, { services = null, form = false } = {}, prod = f
|
|
|
31385
32217
|
endpoint: `/ai/params${queryString}`,
|
|
31386
32218
|
method: "GET",
|
|
31387
32219
|
prod,
|
|
31388
|
-
basePath: BASE_PATH$
|
|
32220
|
+
basePath: BASE_PATH$2
|
|
31389
32221
|
});
|
|
31390
32222
|
}
|
|
31391
32223
|
const CACHE_TTL$3 = 24 * 60 * 60 * 1e3;
|
|
31392
32224
|
const cache$3 = /* @__PURE__ */ new Map();
|
|
31393
|
-
function useImageParams(
|
|
32225
|
+
function useImageParams(prod = false) {
|
|
31394
32226
|
const [params, setParams] = useState(null);
|
|
31395
32227
|
const [loading, setLoading] = useState(false);
|
|
31396
32228
|
const [error, setError] = useState(null);
|
|
31397
32229
|
const fetchParams = async (service) => {
|
|
31398
32230
|
setLoading(true);
|
|
31399
32231
|
setError(null);
|
|
31400
|
-
const cacheKey = `${
|
|
32232
|
+
const cacheKey = `${prod}-${service}`;
|
|
31401
32233
|
const now = Date.now();
|
|
31402
32234
|
const cached = cache$3.get(cacheKey);
|
|
31403
32235
|
if (cached && now - cached.timestamp < CACHE_TTL$3) {
|
|
@@ -31407,13 +32239,13 @@ function useImageParams(apiKey, prod = false) {
|
|
|
31407
32239
|
}
|
|
31408
32240
|
try {
|
|
31409
32241
|
const data = await getImageParams(
|
|
31410
|
-
apiKey,
|
|
31411
32242
|
{ services: service, form: false },
|
|
31412
32243
|
prod
|
|
31413
32244
|
);
|
|
31414
|
-
|
|
31415
|
-
|
|
31416
|
-
|
|
32245
|
+
const result = data?.result?.data || data?.data || data?.result || data;
|
|
32246
|
+
setParams(result);
|
|
32247
|
+
cache$3.set(cacheKey, { data: result, timestamp: now });
|
|
32248
|
+
return result;
|
|
31417
32249
|
} catch (err) {
|
|
31418
32250
|
setError(err.message);
|
|
31419
32251
|
setParams(null);
|
|
@@ -31428,17 +32260,34 @@ function useImageParams(apiKey, prod = false) {
|
|
|
31428
32260
|
};
|
|
31429
32261
|
return { params, loading, error, fetchParams, reset };
|
|
31430
32262
|
}
|
|
31431
|
-
function TabAI({
|
|
31432
|
-
const aiServicesHook = useAiServices(
|
|
31433
|
-
const imageParamsHook = useImageParams(
|
|
32263
|
+
function TabAI({ prod, disabled, onSelected, onGenerated }) {
|
|
32264
|
+
const aiServicesHook = useAiServices(prod);
|
|
32265
|
+
const imageParamsHook = useImageParams(prod);
|
|
31434
32266
|
const [selectedService, setSelectedService] = useState("");
|
|
31435
32267
|
const [dynamicForm, setDynamicForm] = useState({});
|
|
31436
32268
|
const [aiLoading, setAiLoading] = useState(false);
|
|
31437
32269
|
const [aiError, setAiError] = useState(null);
|
|
31438
32270
|
const [aiImage, setAiImage] = useState(null);
|
|
32271
|
+
const serviceLabels = {
|
|
32272
|
+
// IA Services
|
|
32273
|
+
"dall-e-2": "Dalle2",
|
|
32274
|
+
"dall-e-3": "Dalle3",
|
|
32275
|
+
"freepik-classic": "Freepik Classic",
|
|
32276
|
+
"freepik-mystic": "Freepik Mystic",
|
|
32277
|
+
"freepik-google": "Freepik Google",
|
|
32278
|
+
"freepik-flux": "Freepik Flux",
|
|
32279
|
+
// Stock Services
|
|
32280
|
+
"shutterstock": "Shutterstock",
|
|
32281
|
+
"freepikstock": "Freepik"
|
|
32282
|
+
};
|
|
32283
|
+
const getServiceLabel = (service) => {
|
|
32284
|
+
return serviceLabels[service] || service;
|
|
32285
|
+
};
|
|
31439
32286
|
React.useEffect(() => {
|
|
31440
32287
|
if (!aiServicesHook.loading && aiServicesHook.services.length === 1) {
|
|
31441
|
-
|
|
32288
|
+
const service = aiServicesHook.services[0];
|
|
32289
|
+
setSelectedService(service);
|
|
32290
|
+
imageParamsHook.fetchParams(service, true);
|
|
31442
32291
|
}
|
|
31443
32292
|
}, [aiServicesHook.loading, aiServicesHook.services]);
|
|
31444
32293
|
const handleServiceChange = async (e) => {
|
|
@@ -31471,22 +32320,49 @@ function TabAI({ apiKey, prod, disabled, onGenerated }) {
|
|
|
31471
32320
|
setAiError(null);
|
|
31472
32321
|
setAiImage(null);
|
|
31473
32322
|
try {
|
|
31474
|
-
const
|
|
31475
|
-
|
|
31476
|
-
if (
|
|
31477
|
-
|
|
31478
|
-
|
|
31479
|
-
|
|
31480
|
-
|
|
31481
|
-
|
|
31482
|
-
|
|
31483
|
-
|
|
31484
|
-
|
|
31485
|
-
|
|
31486
|
-
|
|
31487
|
-
|
|
32323
|
+
const response = await generateAiImage(dynamicForm, prod);
|
|
32324
|
+
let imageData = null;
|
|
32325
|
+
if (response?.data?.images && Array.isArray(response.data.images) && response.data.images.length > 0) {
|
|
32326
|
+
imageData = response.data.images[0];
|
|
32327
|
+
} else if (response?.result?.images && Array.isArray(response.result.images) && response.result.images.length > 0) {
|
|
32328
|
+
imageData = response.result.images[0];
|
|
32329
|
+
} else if (Array.isArray(response) && response.length > 0) {
|
|
32330
|
+
imageData = response[0];
|
|
32331
|
+
} else if (typeof response === "string") {
|
|
32332
|
+
imageData = response;
|
|
32333
|
+
}
|
|
32334
|
+
if (imageData && onGenerated) {
|
|
32335
|
+
if (imageData.startsWith("http")) {
|
|
32336
|
+
try {
|
|
32337
|
+
const imageResponse = await fetch(imageData);
|
|
32338
|
+
if (!imageResponse.ok) {
|
|
32339
|
+
throw new Error(`Failed to download image: ${imageResponse.status}`);
|
|
32340
|
+
}
|
|
32341
|
+
const blob = await imageResponse.blob();
|
|
32342
|
+
const file = new File([blob], "ai-image.png", {
|
|
32343
|
+
type: blob.type || "image/png"
|
|
32344
|
+
});
|
|
32345
|
+
onGenerated(file);
|
|
32346
|
+
} catch (downloadError) {
|
|
32347
|
+
throw new Error(`Error downloading image: ${downloadError.message}`);
|
|
32348
|
+
}
|
|
32349
|
+
} else {
|
|
32350
|
+
const cleanBase64 = imageData.replace(/^data:image\/\w+;base64,/, "");
|
|
32351
|
+
const byteCharacters = atob(cleanBase64);
|
|
32352
|
+
const byteNumbers = new Array(byteCharacters.length);
|
|
32353
|
+
for (let i = 0; i < byteCharacters.length; i++) {
|
|
32354
|
+
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
|
32355
|
+
}
|
|
32356
|
+
const byteArray = new Uint8Array(byteNumbers);
|
|
32357
|
+
const file = new File([byteArray], "ai-image.webp", {
|
|
32358
|
+
type: "image/webp"
|
|
32359
|
+
});
|
|
32360
|
+
onGenerated(file);
|
|
32361
|
+
}
|
|
32362
|
+
} else {
|
|
32363
|
+
throw new Error("No se pudo extraer la imagen de la respuesta");
|
|
31488
32364
|
}
|
|
31489
|
-
setAiImage(result || null);
|
|
32365
|
+
setAiImage(response?.data?.images || response?.result?.images || null);
|
|
31490
32366
|
} catch (err) {
|
|
31491
32367
|
setAiError(err.message || "Error al generar la imagen");
|
|
31492
32368
|
} finally {
|
|
@@ -31496,25 +32372,39 @@ function TabAI({ apiKey, prod, disabled, onGenerated }) {
|
|
|
31496
32372
|
const renderDynamicForm = () => {
|
|
31497
32373
|
const params = imageParamsHook.params?.[selectedService]?.parameters;
|
|
31498
32374
|
if (!params) return null;
|
|
32375
|
+
const visibleFields = Object.entries(params).filter(([_, config]) => !config.disabled);
|
|
32376
|
+
const useGridLayout = visibleFields.length > 3;
|
|
31499
32377
|
return /* @__PURE__ */ jsxs(
|
|
31500
32378
|
"form",
|
|
31501
32379
|
{
|
|
31502
32380
|
onSubmit: handleDynamicFormSubmit,
|
|
31503
32381
|
"data-type": "ai",
|
|
31504
|
-
className: "flex flex-col gap-
|
|
32382
|
+
className: "flex flex-col gap-3 mt-4 border-t-1 pt-4 border-brand-blue-200",
|
|
31505
32383
|
"aria-label": "Formulario generación IA",
|
|
31506
32384
|
children: [
|
|
31507
|
-
|
|
31508
|
-
|
|
31509
|
-
|
|
32385
|
+
/* @__PURE__ */ jsx("div", { className: useGridLayout ? "grid grid-cols-1 md:grid-cols-2 gap-3" : "flex flex-col gap-3", children: visibleFields.map(([key, config]) => {
|
|
32386
|
+
let placeholder = config.placeholder || "";
|
|
32387
|
+
if (!placeholder) {
|
|
32388
|
+
if (config.type === "integer") {
|
|
32389
|
+
placeholder = config.minValue && config.maxValue ? `Entre ${config.minValue} y ${config.maxValue}` : config.minValue ? `Mínimo ${config.minValue}` : config.maxValue ? `Máximo ${config.maxValue}` : "Introduce un número";
|
|
32390
|
+
} else if (key.toLowerCase().includes("prompt")) {
|
|
32391
|
+
placeholder = "Describe la imagen que deseas generar...";
|
|
32392
|
+
} else if (key.toLowerCase().includes("negative")) {
|
|
32393
|
+
placeholder = "Elementos a evitar en la imagen...";
|
|
32394
|
+
} else {
|
|
32395
|
+
placeholder = `Introduce ${(config.label || key).toLowerCase()}`;
|
|
32396
|
+
}
|
|
31510
32397
|
}
|
|
31511
32398
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
31512
|
-
/* @__PURE__ */
|
|
32399
|
+
/* @__PURE__ */ jsxs(
|
|
31513
32400
|
"label",
|
|
31514
32401
|
{
|
|
31515
32402
|
htmlFor: `ai-${key}`,
|
|
31516
|
-
className: "text-
|
|
31517
|
-
children:
|
|
32403
|
+
className: "text-xs font-medium text-brand-blue-900",
|
|
32404
|
+
children: [
|
|
32405
|
+
config.label || key,
|
|
32406
|
+
config.required && /* @__PURE__ */ jsx("span", { className: "text-red-600 ml-1", children: "*" })
|
|
32407
|
+
]
|
|
31518
32408
|
}
|
|
31519
32409
|
),
|
|
31520
32410
|
config.options ? /* @__PURE__ */ jsx(
|
|
@@ -31526,6 +32416,7 @@ function TabAI({ apiKey, prod, disabled, onGenerated }) {
|
|
|
31526
32416
|
onChange: handleDynamicFormChange,
|
|
31527
32417
|
className: "limbo-input",
|
|
31528
32418
|
disabled,
|
|
32419
|
+
title: config.label || key,
|
|
31529
32420
|
children: config.options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt, children: opt }, opt))
|
|
31530
32421
|
}
|
|
31531
32422
|
) : /* @__PURE__ */ jsx(
|
|
@@ -31540,11 +32431,13 @@ function TabAI({ apiKey, prod, disabled, onGenerated }) {
|
|
|
31540
32431
|
disabled,
|
|
31541
32432
|
required: config.required,
|
|
31542
32433
|
min: config.minValue,
|
|
31543
|
-
max: config.maxValue
|
|
32434
|
+
max: config.maxValue,
|
|
32435
|
+
placeholder,
|
|
32436
|
+
title: config.label || key
|
|
31544
32437
|
}
|
|
31545
32438
|
)
|
|
31546
32439
|
] }, key);
|
|
31547
|
-
}),
|
|
32440
|
+
}) }),
|
|
31548
32441
|
/* @__PURE__ */ jsx(
|
|
31549
32442
|
"button",
|
|
31550
32443
|
{
|
|
@@ -31575,7 +32468,7 @@ function TabAI({ apiKey, prod, disabled, onGenerated }) {
|
|
|
31575
32468
|
{
|
|
31576
32469
|
id: "aiSelectDescription",
|
|
31577
32470
|
className: "text-lg font-semibold text-brand-blue-1000",
|
|
31578
|
-
children: "Selecciona el modelo IA"
|
|
32471
|
+
children: aiServicesHook.services.length === 1 ? `Generación con ${getServiceLabel(selectedService)}` : "Selecciona el modelo IA"
|
|
31579
32472
|
}
|
|
31580
32473
|
),
|
|
31581
32474
|
aiServicesHook.services.length > 1 && /* @__PURE__ */ jsxs(
|
|
@@ -31590,7 +32483,7 @@ function TabAI({ apiKey, prod, disabled, onGenerated }) {
|
|
|
31590
32483
|
className: "limbo-input",
|
|
31591
32484
|
children: [
|
|
31592
32485
|
/* @__PURE__ */ jsx("option", { value: "", children: "-- Selecciona --" }),
|
|
31593
|
-
aiServicesHook.services.map((service) => /* @__PURE__ */ jsx("option", { value: service, children: service }, service))
|
|
32486
|
+
aiServicesHook.services.map((service) => /* @__PURE__ */ jsx("option", { value: service, children: getServiceLabel(service) }, service))
|
|
31594
32487
|
]
|
|
31595
32488
|
}
|
|
31596
32489
|
),
|
|
@@ -31612,26 +32505,24 @@ function TabAI({ apiKey, prod, disabled, onGenerated }) {
|
|
|
31612
32505
|
] }, "img-" + index))
|
|
31613
32506
|
] });
|
|
31614
32507
|
}
|
|
31615
|
-
const BASE_PATH$
|
|
31616
|
-
function getStockServices(
|
|
31617
|
-
return callApi({ endpoint: "/stock/services", prod, basePath: BASE_PATH$
|
|
32508
|
+
const BASE_PATH$1 = "/api";
|
|
32509
|
+
function getStockServices(prod = false) {
|
|
32510
|
+
return callApi({ endpoint: "/stock/services", prod, basePath: BASE_PATH$1 });
|
|
31618
32511
|
}
|
|
31619
|
-
function searchStockImages(
|
|
31620
|
-
return callApi({ endpoint: "/stock/search", method: "POST", body: params, prod, basePath: BASE_PATH$
|
|
32512
|
+
function searchStockImages(params, prod = false) {
|
|
32513
|
+
return callApi({ endpoint: "/stock/search", method: "POST", body: params, prod, basePath: BASE_PATH$1 });
|
|
32514
|
+
}
|
|
32515
|
+
function downloadStockImage(params, prod = false) {
|
|
32516
|
+
return callApi({ endpoint: "/stock/download", method: "POST", body: params, prod, basePath: BASE_PATH$1 });
|
|
31621
32517
|
}
|
|
31622
32518
|
const CACHE_TTL$2 = 24 * 60 * 60 * 1e3;
|
|
31623
32519
|
const cache$2 = /* @__PURE__ */ new Map();
|
|
31624
|
-
function useStockServices(
|
|
32520
|
+
function useStockServices(prod = false) {
|
|
31625
32521
|
const [services, setServices] = useState([]);
|
|
31626
32522
|
const [loading, setLoading] = useState(true);
|
|
31627
32523
|
const [error, setError] = useState(null);
|
|
31628
32524
|
useEffect(() => {
|
|
31629
|
-
|
|
31630
|
-
setError("API Key no proporcionada");
|
|
31631
|
-
setLoading(false);
|
|
31632
|
-
return;
|
|
31633
|
-
}
|
|
31634
|
-
const cacheKey = `${apiKey}-${prod}`;
|
|
32525
|
+
const cacheKey = `stock-services-${prod}`;
|
|
31635
32526
|
const cached = cache$2.get(cacheKey);
|
|
31636
32527
|
const now = Date.now();
|
|
31637
32528
|
if (cached && now - cached.timestamp < CACHE_TTL$2) {
|
|
@@ -31642,9 +32533,9 @@ function useStockServices(apiKey, prod = false) {
|
|
|
31642
32533
|
let isMounted = true;
|
|
31643
32534
|
const fetchServices = async () => {
|
|
31644
32535
|
try {
|
|
31645
|
-
const data = await getStockServices(
|
|
32536
|
+
const data = await getStockServices(prod);
|
|
31646
32537
|
if (!isMounted) return;
|
|
31647
|
-
const result = data
|
|
32538
|
+
const result = data?.data?.images || [];
|
|
31648
32539
|
setServices(result);
|
|
31649
32540
|
cache$2.set(cacheKey, { data: result, timestamp: now });
|
|
31650
32541
|
} catch (err) {
|
|
@@ -31657,23 +32548,34 @@ function useStockServices(apiKey, prod = false) {
|
|
|
31657
32548
|
return () => {
|
|
31658
32549
|
isMounted = false;
|
|
31659
32550
|
};
|
|
31660
|
-
}, [
|
|
32551
|
+
}, [prod]);
|
|
31661
32552
|
const invalidateCache = () => {
|
|
31662
|
-
cache$2.delete(
|
|
32553
|
+
cache$2.delete(`stock-services-${prod}`);
|
|
31663
32554
|
};
|
|
31664
32555
|
return { services, loading, error, invalidateCache };
|
|
31665
32556
|
}
|
|
31666
|
-
function TabStock({
|
|
31667
|
-
const stockServicesHook = useStockServices(
|
|
31668
|
-
const imageParamsHook = useImageParams(
|
|
32557
|
+
function TabStock({ prod, disabled, onSelected }) {
|
|
32558
|
+
const stockServicesHook = useStockServices(prod);
|
|
32559
|
+
const imageParamsHook = useImageParams(prod);
|
|
31669
32560
|
const [selectedService, setSelectedService] = useState("");
|
|
31670
32561
|
const [dynamicForm, setDynamicForm] = useState({});
|
|
31671
32562
|
const [stockLoading, setStockLoading] = useState(false);
|
|
31672
32563
|
const [stockError, setStockError] = useState(null);
|
|
31673
32564
|
const [stockImages, setStockImages] = useState([]);
|
|
32565
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
32566
|
+
const [downloadingId, setDownloadingId] = useState(null);
|
|
32567
|
+
const serviceLabels = {
|
|
32568
|
+
shutterstock: "Shutterstock",
|
|
32569
|
+
freepikstock: "Freepik"
|
|
32570
|
+
};
|
|
32571
|
+
const getServiceLabel = (service) => {
|
|
32572
|
+
return serviceLabels[service] || service;
|
|
32573
|
+
};
|
|
31674
32574
|
React.useEffect(() => {
|
|
31675
32575
|
if (!stockServicesHook.loading && stockServicesHook.services.length === 1) {
|
|
31676
|
-
|
|
32576
|
+
const service = stockServicesHook.services[0];
|
|
32577
|
+
setSelectedService(service);
|
|
32578
|
+
imageParamsHook.fetchParams(service, true);
|
|
31677
32579
|
}
|
|
31678
32580
|
}, [stockServicesHook.loading, stockServicesHook.services]);
|
|
31679
32581
|
const handleServiceChange = async (e) => {
|
|
@@ -31681,6 +32583,7 @@ function TabStock({ apiKey, prod, disabled, onSelected }) {
|
|
|
31681
32583
|
setSelectedService(service);
|
|
31682
32584
|
setDynamicForm({});
|
|
31683
32585
|
setStockImages([]);
|
|
32586
|
+
setCurrentPage(1);
|
|
31684
32587
|
if (service) {
|
|
31685
32588
|
await imageParamsHook.fetchParams(service, true);
|
|
31686
32589
|
}
|
|
@@ -31700,11 +32603,10 @@ function TabStock({ apiKey, prod, disabled, onSelected }) {
|
|
|
31700
32603
|
const { name, value } = e.target;
|
|
31701
32604
|
setDynamicForm((prev) => ({ ...prev, [name]: value }));
|
|
31702
32605
|
};
|
|
31703
|
-
const
|
|
31704
|
-
e.preventDefault();
|
|
32606
|
+
const performSearch = async (page = 1) => {
|
|
31705
32607
|
setStockLoading(true);
|
|
31706
32608
|
setStockError(null);
|
|
31707
|
-
setStockImages([]);
|
|
32609
|
+
if (page === 1) setStockImages([]);
|
|
31708
32610
|
try {
|
|
31709
32611
|
const params = imageParamsHook.params?.[selectedService]?.parameters;
|
|
31710
32612
|
const formData = { ...dynamicForm };
|
|
@@ -31712,52 +32614,93 @@ function TabStock({ apiKey, prod, disabled, onSelected }) {
|
|
|
31712
32614
|
if (!(key in formData)) formData[key] = cfg.default ?? "";
|
|
31713
32615
|
});
|
|
31714
32616
|
formData.service = selectedService;
|
|
31715
|
-
|
|
31716
|
-
const
|
|
32617
|
+
formData.page = page;
|
|
32618
|
+
const data = await searchStockImages(formData, prod);
|
|
32619
|
+
const result = data?.result?.data || [];
|
|
31717
32620
|
setStockImages(Array.isArray(result) ? result : []);
|
|
32621
|
+
setCurrentPage(page);
|
|
31718
32622
|
} catch (err) {
|
|
31719
32623
|
setStockError(err.message || "Error al buscar imágenes");
|
|
31720
32624
|
} finally {
|
|
31721
32625
|
setStockLoading(false);
|
|
31722
32626
|
}
|
|
31723
32627
|
};
|
|
32628
|
+
const handleDynamicFormSubmit = async (e) => {
|
|
32629
|
+
e.preventDefault();
|
|
32630
|
+
const query = dynamicForm.query || dynamicForm.search || dynamicForm.term || "";
|
|
32631
|
+
if (query.trim().length < 5) {
|
|
32632
|
+
setStockError("La búsqueda debe tener al menos 5 caracteres");
|
|
32633
|
+
return;
|
|
32634
|
+
}
|
|
32635
|
+
await performSearch(1);
|
|
32636
|
+
};
|
|
32637
|
+
const handlePageChange = (newPage) => {
|
|
32638
|
+
if (newPage < 1) return;
|
|
32639
|
+
performSearch(newPage);
|
|
32640
|
+
};
|
|
31724
32641
|
const handleImageSelect = async (img) => {
|
|
31725
|
-
|
|
32642
|
+
setDownloadingId(img.id);
|
|
31726
32643
|
setStockError(null);
|
|
31727
32644
|
try {
|
|
31728
|
-
|
|
31729
|
-
|
|
31730
|
-
|
|
31731
|
-
|
|
31732
|
-
|
|
32645
|
+
const downloadParams = {
|
|
32646
|
+
service: selectedService,
|
|
32647
|
+
image_id: img.id
|
|
32648
|
+
};
|
|
32649
|
+
const downloadData = await downloadStockImage(downloadParams, prod);
|
|
32650
|
+
if (downloadData?.result?.url || downloadData?.result?.download_url) {
|
|
32651
|
+
const imageUrl = downloadData.result.url || downloadData.result.download_url;
|
|
32652
|
+
const response = await fetch(imageUrl);
|
|
32653
|
+
const blob = await response.blob();
|
|
32654
|
+
const filename = downloadData.result.filename || `stock-${img.id}.jpg`;
|
|
32655
|
+
const file = new File([blob], filename, {
|
|
32656
|
+
type: blob.type || "image/jpeg"
|
|
32657
|
+
});
|
|
32658
|
+
if (onSelected) onSelected(file);
|
|
32659
|
+
} else {
|
|
32660
|
+
throw new Error("No se pudo obtener la URL de descarga");
|
|
32661
|
+
}
|
|
31733
32662
|
} catch (err) {
|
|
31734
32663
|
setStockError(
|
|
31735
32664
|
err.message || "No se pudo recuperar la imagen seleccionada"
|
|
31736
32665
|
);
|
|
31737
32666
|
} finally {
|
|
31738
|
-
|
|
32667
|
+
setDownloadingId(null);
|
|
31739
32668
|
}
|
|
31740
32669
|
};
|
|
31741
32670
|
const renderDynamicForm = () => {
|
|
31742
32671
|
const params = imageParamsHook.params?.[selectedService]?.parameters;
|
|
31743
32672
|
if (!params) return null;
|
|
32673
|
+
const visibleFields = Object.entries(params).filter(([_, config]) => !config.disabled);
|
|
32674
|
+
const useGridLayout = visibleFields.length > 3;
|
|
31744
32675
|
return /* @__PURE__ */ jsxs(
|
|
31745
32676
|
"form",
|
|
31746
32677
|
{
|
|
31747
32678
|
onSubmit: handleDynamicFormSubmit,
|
|
31748
32679
|
"data-type": "stock",
|
|
31749
|
-
className: "flex flex-col gap-
|
|
32680
|
+
className: "flex flex-col gap-3 mt-4 border-t-1 pt-4 border-brand-blue-200",
|
|
31750
32681
|
"aria-label": "Formulario búsqueda de imágenes de Stock",
|
|
31751
32682
|
children: [
|
|
31752
|
-
|
|
31753
|
-
|
|
32683
|
+
/* @__PURE__ */ jsx("div", { className: useGridLayout ? "grid grid-cols-1 md:grid-cols-2 gap-3" : "flex flex-col gap-3", children: visibleFields.map(([key, config]) => {
|
|
32684
|
+
let placeholder = config.placeholder || "";
|
|
32685
|
+
if (!placeholder) {
|
|
32686
|
+
if (config.type === "integer") {
|
|
32687
|
+
placeholder = config.minValue && config.maxValue ? `Entre ${config.minValue} y ${config.maxValue}` : config.minValue ? `Mínimo ${config.minValue}` : config.maxValue ? `Máximo ${config.maxValue}` : "Introduce un número";
|
|
32688
|
+
} else if (key.toLowerCase().includes("query") || key.toLowerCase().includes("search") || key.toLowerCase().includes("term")) {
|
|
32689
|
+
placeholder = "Buscar imágenes...";
|
|
32690
|
+
} else {
|
|
32691
|
+
placeholder = `Introduce ${(config.label || key).toLowerCase()}`;
|
|
32692
|
+
}
|
|
32693
|
+
}
|
|
31754
32694
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
31755
|
-
/* @__PURE__ */
|
|
32695
|
+
/* @__PURE__ */ jsxs(
|
|
31756
32696
|
"label",
|
|
31757
32697
|
{
|
|
31758
32698
|
htmlFor: `stock-${key}`,
|
|
31759
|
-
className: "text-
|
|
31760
|
-
children:
|
|
32699
|
+
className: "text-xs font-medium text-brand-blue-900",
|
|
32700
|
+
children: [
|
|
32701
|
+
config.label || key,
|
|
32702
|
+
config.required && /* @__PURE__ */ jsx("span", { className: "text-red-600 ml-1", children: "*" })
|
|
32703
|
+
]
|
|
31761
32704
|
}
|
|
31762
32705
|
),
|
|
31763
32706
|
config.options ? /* @__PURE__ */ jsx(
|
|
@@ -31769,6 +32712,7 @@ function TabStock({ apiKey, prod, disabled, onSelected }) {
|
|
|
31769
32712
|
onChange: handleDynamicFormChange,
|
|
31770
32713
|
className: "limbo-input",
|
|
31771
32714
|
disabled,
|
|
32715
|
+
title: config.label || key,
|
|
31772
32716
|
children: config.options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt, children: opt }, opt))
|
|
31773
32717
|
}
|
|
31774
32718
|
) : /* @__PURE__ */ jsx(
|
|
@@ -31783,11 +32727,13 @@ function TabStock({ apiKey, prod, disabled, onSelected }) {
|
|
|
31783
32727
|
disabled,
|
|
31784
32728
|
required: config.required,
|
|
31785
32729
|
min: config.minValue,
|
|
31786
|
-
max: config.maxValue
|
|
32730
|
+
max: config.maxValue,
|
|
32731
|
+
placeholder,
|
|
32732
|
+
title: config.label || key
|
|
31787
32733
|
}
|
|
31788
32734
|
)
|
|
31789
32735
|
] }, key);
|
|
31790
|
-
}),
|
|
32736
|
+
}) }),
|
|
31791
32737
|
/* @__PURE__ */ jsx(
|
|
31792
32738
|
"button",
|
|
31793
32739
|
{
|
|
@@ -31812,7 +32758,7 @@ function TabStock({ apiKey, prod, disabled, onSelected }) {
|
|
|
31812
32758
|
return /* @__PURE__ */ jsx("div", { className: "alert alert-warning", children: "No hay servicios de Stock disponibles." });
|
|
31813
32759
|
}
|
|
31814
32760
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
|
|
31815
|
-
/* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-brand-blue-1000", children: "Selecciona el servicio de Stock" }),
|
|
32761
|
+
/* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-brand-blue-1000", children: stockServicesHook.services.length === 1 ? `Búsqueda en ${getServiceLabel(selectedService)}` : "Selecciona el servicio de Stock" }),
|
|
31816
32762
|
stockServicesHook.services.length > 1 && /* @__PURE__ */ jsxs(
|
|
31817
32763
|
"select",
|
|
31818
32764
|
{
|
|
@@ -31822,7 +32768,7 @@ function TabStock({ apiKey, prod, disabled, onSelected }) {
|
|
|
31822
32768
|
className: "limbo-input",
|
|
31823
32769
|
children: [
|
|
31824
32770
|
/* @__PURE__ */ jsx("option", { value: "", children: "-- Selecciona --" }),
|
|
31825
|
-
stockServicesHook.services.map((service) => /* @__PURE__ */ jsx("option", { value: service, children: service }, service))
|
|
32771
|
+
stockServicesHook.services.map((service) => /* @__PURE__ */ jsx("option", { value: service, children: getServiceLabel(service) }, service))
|
|
31826
32772
|
]
|
|
31827
32773
|
}
|
|
31828
32774
|
),
|
|
@@ -31831,62 +32777,117 @@ function TabStock({ apiKey, prod, disabled, onSelected }) {
|
|
|
31831
32777
|
imageParamsHook.error && /* @__PURE__ */ jsx("div", { className: "alert alert-danger", children: imageParamsHook.error }),
|
|
31832
32778
|
selectedService && renderDynamicForm(),
|
|
31833
32779
|
stockError && /* @__PURE__ */ jsx("div", { className: "alert alert-danger", children: stockError }),
|
|
31834
|
-
stockImages.length > 0 && /* @__PURE__ */
|
|
31835
|
-
|
|
31836
|
-
|
|
31837
|
-
|
|
31838
|
-
|
|
31839
|
-
|
|
31840
|
-
|
|
32780
|
+
stockImages.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
32781
|
+
/* @__PURE__ */ jsx(
|
|
32782
|
+
"div",
|
|
32783
|
+
{
|
|
32784
|
+
className: "mt-6 grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4",
|
|
32785
|
+
"aria-live": "polite",
|
|
32786
|
+
children: stockImages.map((img, idx) => /* @__PURE__ */ jsxs(
|
|
32787
|
+
"div",
|
|
32788
|
+
{
|
|
32789
|
+
className: "border border-brand-blue-200 rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-md transition-shadow",
|
|
32790
|
+
children: [
|
|
32791
|
+
/* @__PURE__ */ jsxs("div", { className: "relative aspect-video bg-neutral-100", children: [
|
|
32792
|
+
/* @__PURE__ */ jsx(
|
|
32793
|
+
"img",
|
|
32794
|
+
{
|
|
32795
|
+
src: img.preview || img.thumbnail || img.url,
|
|
32796
|
+
alt: img.title || `Resultado ${idx + 1}`,
|
|
32797
|
+
className: "object-cover w-full h-full",
|
|
32798
|
+
loading: "lazy"
|
|
32799
|
+
}
|
|
32800
|
+
),
|
|
32801
|
+
img.id && /* @__PURE__ */ jsxs("span", { className: "absolute bottom-1 right-1 bg-black/60 text-white text-xs px-2 py-1 rounded", children: [
|
|
32802
|
+
"ID: ",
|
|
32803
|
+
img.id
|
|
32804
|
+
] })
|
|
32805
|
+
] }),
|
|
32806
|
+
/* @__PURE__ */ jsx("div", { className: "p-2", children: /* @__PURE__ */ jsx(
|
|
32807
|
+
"button",
|
|
32808
|
+
{
|
|
32809
|
+
className: `limbo-btn w-full text-sm ${downloadingId === img.id ? "limbo-btn-disabled cursor-not-allowed" : "limbo-btn-primary"}`,
|
|
32810
|
+
onClick: () => handleImageSelect(img),
|
|
32811
|
+
disabled: stockLoading || disabled || downloadingId === img.id,
|
|
32812
|
+
children: downloadingId === img.id ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
32813
|
+
/* @__PURE__ */ jsx("span", { className: "limbo-loader limbo-loader--sm mr-1" }),
|
|
32814
|
+
"Descargando..."
|
|
32815
|
+
] }) : "Seleccionar"
|
|
32816
|
+
}
|
|
32817
|
+
) })
|
|
32818
|
+
]
|
|
32819
|
+
},
|
|
32820
|
+
img.id || idx
|
|
32821
|
+
))
|
|
32822
|
+
}
|
|
32823
|
+
),
|
|
32824
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-6 flex items-center justify-center gap-2", children: [
|
|
32825
|
+
/* @__PURE__ */ jsxs(
|
|
32826
|
+
"button",
|
|
31841
32827
|
{
|
|
31842
|
-
|
|
32828
|
+
onClick: () => handlePageChange(currentPage - 1),
|
|
32829
|
+
disabled: currentPage === 1 || stockLoading,
|
|
32830
|
+
className: "limbo-btn limbo-btn-secondary px-4 py-2 disabled:opacity-50",
|
|
31843
32831
|
children: [
|
|
31844
|
-
/* @__PURE__ */ jsx(
|
|
31845
|
-
|
|
31846
|
-
{
|
|
31847
|
-
src: img.url || img,
|
|
31848
|
-
alt: `Resultado ${idx + 1}`,
|
|
31849
|
-
className: "object-cover w-full h-40"
|
|
31850
|
-
}
|
|
31851
|
-
),
|
|
31852
|
-
/* @__PURE__ */ jsx(
|
|
31853
|
-
"button",
|
|
31854
|
-
{
|
|
31855
|
-
className: "limbo-btn limbo-btn-primary mt-2",
|
|
31856
|
-
onClick: () => handleImageSelect(img),
|
|
31857
|
-
disabled: stockLoading || disabled,
|
|
31858
|
-
children: "Seleccionar"
|
|
31859
|
-
}
|
|
31860
|
-
)
|
|
32832
|
+
/* @__PURE__ */ jsx("span", { className: "icon icon-arrow-left-white icon--sm" }),
|
|
32833
|
+
" Anterior"
|
|
31861
32834
|
]
|
|
31862
|
-
}
|
|
31863
|
-
|
|
31864
|
-
|
|
31865
|
-
|
|
31866
|
-
|
|
32835
|
+
}
|
|
32836
|
+
),
|
|
32837
|
+
/* @__PURE__ */ jsxs("span", { className: "px-4 py-2 text-sm text-neutral-700", children: [
|
|
32838
|
+
"Página ",
|
|
32839
|
+
currentPage
|
|
32840
|
+
] }),
|
|
32841
|
+
/* @__PURE__ */ jsxs(
|
|
32842
|
+
"button",
|
|
32843
|
+
{
|
|
32844
|
+
onClick: () => handlePageChange(currentPage + 1),
|
|
32845
|
+
disabled: stockLoading || stockImages.length === 0,
|
|
32846
|
+
className: "limbo-btn limbo-btn-secondary px-4 py-2 disabled:opacity-50",
|
|
32847
|
+
children: [
|
|
32848
|
+
"Siguiente ",
|
|
32849
|
+
/* @__PURE__ */ jsx("span", { className: "icon icon-arrow-right-white icon--sm" })
|
|
32850
|
+
]
|
|
32851
|
+
}
|
|
32852
|
+
)
|
|
32853
|
+
] })
|
|
32854
|
+
] }),
|
|
32855
|
+
!stockLoading && stockImages.length === 0 && selectedService && dynamicForm.query && /* @__PURE__ */ jsxs("div", { className: "mt-6 text-center text-neutral-600 py-8 bg-neutral-50 rounded-lg", children: [
|
|
32856
|
+
/* @__PURE__ */ jsx("span", { className: "icon icon-search icon--lg mb-2" }),
|
|
32857
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
32858
|
+
'No se encontraron imágenes para "',
|
|
32859
|
+
dynamicForm.query,
|
|
32860
|
+
'"'
|
|
32861
|
+
] }),
|
|
32862
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm mt-1", children: "Intenta con otros términos de búsqueda" })
|
|
32863
|
+
] })
|
|
31867
32864
|
] });
|
|
31868
32865
|
}
|
|
31869
|
-
const BASE_PATH
|
|
31870
|
-
function getExternalImageSources(
|
|
32866
|
+
const BASE_PATH = "/api";
|
|
32867
|
+
function getExternalImageSources(prod = false) {
|
|
32868
|
+
return callApi({
|
|
32869
|
+
endpoint: "/external/sources",
|
|
32870
|
+
prod,
|
|
32871
|
+
basePath: BASE_PATH
|
|
32872
|
+
});
|
|
32873
|
+
}
|
|
32874
|
+
function getExternalImages(params, prod = false) {
|
|
31871
32875
|
return callApi({
|
|
31872
|
-
endpoint:
|
|
32876
|
+
endpoint: `/external/search`,
|
|
32877
|
+
method: "POST",
|
|
32878
|
+
body: params,
|
|
31873
32879
|
prod,
|
|
31874
|
-
basePath: BASE_PATH
|
|
32880
|
+
basePath: BASE_PATH
|
|
31875
32881
|
});
|
|
31876
32882
|
}
|
|
31877
32883
|
const CACHE_TTL$1 = 24 * 60 * 60 * 1e3;
|
|
31878
32884
|
const cache$1 = /* @__PURE__ */ new Map();
|
|
31879
|
-
function usePortalSources(
|
|
32885
|
+
function usePortalSources(prod = false) {
|
|
31880
32886
|
const [sources, setSources] = useState([]);
|
|
31881
32887
|
const [loading, setLoading] = useState(true);
|
|
31882
32888
|
const [error, setError] = useState(null);
|
|
31883
32889
|
useEffect(() => {
|
|
31884
|
-
|
|
31885
|
-
setError("API Key no proporcionada");
|
|
31886
|
-
setLoading(false);
|
|
31887
|
-
return;
|
|
31888
|
-
}
|
|
31889
|
-
const cacheKey = `${apiKey}-${prod}`;
|
|
32890
|
+
const cacheKey = `portal-sources-${prod}`;
|
|
31890
32891
|
const cached = cache$1.get(cacheKey);
|
|
31891
32892
|
const now = Date.now();
|
|
31892
32893
|
if (cached && now - cached.timestamp < CACHE_TTL$1) {
|
|
@@ -31897,11 +32898,15 @@ function usePortalSources(apiKey, prod = false) {
|
|
|
31897
32898
|
let isMounted = true;
|
|
31898
32899
|
const fetchSources = async () => {
|
|
31899
32900
|
try {
|
|
31900
|
-
const data = await getExternalImageSources(
|
|
32901
|
+
const data = await getExternalImageSources(prod);
|
|
31901
32902
|
if (!isMounted) return;
|
|
31902
|
-
const result = data
|
|
31903
|
-
|
|
31904
|
-
|
|
32903
|
+
const result = data?.data?.sources || {};
|
|
32904
|
+
const sourcesArray = Object.entries(result).map(([id, source]) => ({
|
|
32905
|
+
id,
|
|
32906
|
+
...source
|
|
32907
|
+
}));
|
|
32908
|
+
setSources(sourcesArray);
|
|
32909
|
+
cache$1.set(cacheKey, { data: sourcesArray, timestamp: now });
|
|
31905
32910
|
} catch (err) {
|
|
31906
32911
|
if (isMounted) setError(err.message);
|
|
31907
32912
|
} finally {
|
|
@@ -31912,31 +32917,475 @@ function usePortalSources(apiKey, prod = false) {
|
|
|
31912
32917
|
return () => {
|
|
31913
32918
|
isMounted = false;
|
|
31914
32919
|
};
|
|
31915
|
-
}, [
|
|
32920
|
+
}, [prod]);
|
|
31916
32921
|
const invalidateCache = () => {
|
|
31917
|
-
cache$1.delete(
|
|
32922
|
+
cache$1.delete(`portal-sources-${prod}`);
|
|
31918
32923
|
};
|
|
31919
32924
|
return { sources, loading, error, invalidateCache };
|
|
31920
32925
|
}
|
|
31921
|
-
|
|
31922
|
-
const
|
|
31923
|
-
const
|
|
31924
|
-
|
|
31925
|
-
|
|
32926
|
+
const ValidatedImage = ({ src, alt, className, onError }) => {
|
|
32927
|
+
const [imageStatus, setImageStatus] = React.useState("loading");
|
|
32928
|
+
const handleImageLoad = () => {
|
|
32929
|
+
setImageStatus("loaded");
|
|
32930
|
+
};
|
|
32931
|
+
const handleImageError = () => {
|
|
32932
|
+
setImageStatus("error");
|
|
32933
|
+
if (onError) onError();
|
|
32934
|
+
};
|
|
32935
|
+
if (imageStatus === "error") {
|
|
32936
|
+
return null;
|
|
32937
|
+
}
|
|
32938
|
+
return /* @__PURE__ */ jsx(
|
|
32939
|
+
"img",
|
|
32940
|
+
{
|
|
32941
|
+
src,
|
|
32942
|
+
alt,
|
|
32943
|
+
className,
|
|
32944
|
+
loading: "lazy",
|
|
32945
|
+
onLoad: handleImageLoad,
|
|
32946
|
+
onError: handleImageError
|
|
32947
|
+
}
|
|
32948
|
+
);
|
|
32949
|
+
};
|
|
32950
|
+
function TabPortals({ prod, disabled, onSelected }) {
|
|
32951
|
+
const portalSourcesHook = usePortalSources(prod);
|
|
32952
|
+
const loadSavedState = (key, defaultValue) => {
|
|
32953
|
+
try {
|
|
32954
|
+
const saved = sessionStorage.getItem(`limbo_portals_${key}`);
|
|
32955
|
+
return saved ? JSON.parse(saved) : defaultValue;
|
|
32956
|
+
} catch {
|
|
32957
|
+
return defaultValue;
|
|
32958
|
+
}
|
|
32959
|
+
};
|
|
32960
|
+
const [selectedPortals, setSelectedPortals] = useState(() => loadSavedState("selectedPortals", []));
|
|
32961
|
+
const [searchName, setSearchName] = useState(() => loadSavedState("searchName", ""));
|
|
32962
|
+
const [limit, setLimit] = useState(() => loadSavedState("limit", 20));
|
|
32963
|
+
const [currentPage, setCurrentPage] = useState(() => loadSavedState("currentPage", 1));
|
|
32964
|
+
const [loading, setLoading] = useState(false);
|
|
32965
|
+
const [error, setError] = useState(null);
|
|
32966
|
+
const [images, setImages] = useState(() => loadSavedState("images", []));
|
|
32967
|
+
const [portalResults, setPortalResults] = useState(() => loadSavedState("portalResults", {}));
|
|
32968
|
+
const [paginationInfo, setPaginationInfo] = useState(() => loadSavedState("paginationInfo", null));
|
|
32969
|
+
const [downloadingUrl, setDownloadingUrl] = useState(null);
|
|
32970
|
+
const searchCacheRef = useRef({});
|
|
32971
|
+
const [failedImages, setFailedImages] = useState(/* @__PURE__ */ new Set());
|
|
32972
|
+
React.useEffect(() => {
|
|
32973
|
+
sessionStorage.setItem("limbo_portals_selectedPortals", JSON.stringify(selectedPortals));
|
|
32974
|
+
}, [selectedPortals]);
|
|
32975
|
+
React.useEffect(() => {
|
|
32976
|
+
sessionStorage.setItem("limbo_portals_searchName", JSON.stringify(searchName));
|
|
32977
|
+
}, [searchName]);
|
|
32978
|
+
React.useEffect(() => {
|
|
32979
|
+
sessionStorage.setItem("limbo_portals_limit", JSON.stringify(limit));
|
|
32980
|
+
}, [limit]);
|
|
32981
|
+
React.useEffect(() => {
|
|
32982
|
+
sessionStorage.setItem("limbo_portals_currentPage", JSON.stringify(currentPage));
|
|
32983
|
+
}, [currentPage]);
|
|
32984
|
+
React.useEffect(() => {
|
|
32985
|
+
sessionStorage.setItem("limbo_portals_images", JSON.stringify(images));
|
|
32986
|
+
}, [images]);
|
|
32987
|
+
React.useEffect(() => {
|
|
32988
|
+
sessionStorage.setItem("limbo_portals_portalResults", JSON.stringify(portalResults));
|
|
32989
|
+
}, [portalResults]);
|
|
32990
|
+
React.useEffect(() => {
|
|
32991
|
+
sessionStorage.setItem("limbo_portals_paginationInfo", JSON.stringify(paginationInfo));
|
|
32992
|
+
}, [paginationInfo]);
|
|
32993
|
+
const getCacheKey = (portals, name, limitVal, page) => {
|
|
32994
|
+
return `${portals.sort().join(",")}_${name}_${limitVal}_${page}`;
|
|
32995
|
+
};
|
|
32996
|
+
const handlePortalToggle = (portalKey) => {
|
|
32997
|
+
setSelectedPortals(
|
|
32998
|
+
(prev) => prev.includes(portalKey) ? prev.filter((p) => p !== portalKey) : [...prev, portalKey]
|
|
32999
|
+
);
|
|
33000
|
+
};
|
|
33001
|
+
const handleSelectAll = () => {
|
|
33002
|
+
if (selectedPortals.length === portalSourcesHook.sources.length) {
|
|
33003
|
+
setSelectedPortals([]);
|
|
33004
|
+
} else {
|
|
33005
|
+
setSelectedPortals(portalSourcesHook.sources.map((s) => s.id));
|
|
33006
|
+
}
|
|
33007
|
+
};
|
|
33008
|
+
const performSearch = async (page = 1) => {
|
|
33009
|
+
if (selectedPortals.length === 0) {
|
|
33010
|
+
setError("Selecciona al menos un portal");
|
|
33011
|
+
return;
|
|
33012
|
+
}
|
|
33013
|
+
const cacheKey = getCacheKey(
|
|
33014
|
+
selectedPortals,
|
|
33015
|
+
searchName.trim(),
|
|
33016
|
+
limit,
|
|
33017
|
+
page
|
|
33018
|
+
);
|
|
33019
|
+
const cachedData = searchCacheRef.current[cacheKey];
|
|
33020
|
+
if (cachedData) {
|
|
33021
|
+
setImages(cachedData.images);
|
|
33022
|
+
setPortalResults(cachedData.portalResults);
|
|
33023
|
+
setPaginationInfo(cachedData.paginationInfo);
|
|
33024
|
+
setCurrentPage(page);
|
|
33025
|
+
setError(null);
|
|
33026
|
+
return;
|
|
33027
|
+
}
|
|
33028
|
+
setLoading(true);
|
|
33029
|
+
setError(null);
|
|
33030
|
+
if (page === 1) {
|
|
33031
|
+
setImages([]);
|
|
33032
|
+
setPortalResults({});
|
|
33033
|
+
setPaginationInfo(null);
|
|
33034
|
+
}
|
|
33035
|
+
try {
|
|
33036
|
+
const params = {
|
|
33037
|
+
sources: selectedPortals,
|
|
33038
|
+
// Ya son strings de IDs
|
|
33039
|
+
limit,
|
|
33040
|
+
page,
|
|
33041
|
+
name: searchName.trim()
|
|
33042
|
+
};
|
|
33043
|
+
const data = await getExternalImages(params, prod);
|
|
33044
|
+
const sources = data?.data?.sources || {};
|
|
33045
|
+
const allImages = [];
|
|
33046
|
+
const newPortalResults = {};
|
|
33047
|
+
const seenUrls = /* @__PURE__ */ new Set();
|
|
33048
|
+
let paginationInfo2 = null;
|
|
33049
|
+
Object.entries(sources).forEach(([portalId, portalData]) => {
|
|
33050
|
+
newPortalResults[portalId] = {
|
|
33051
|
+
title: portalData.meta?.title || portalId,
|
|
33052
|
+
status: 200,
|
|
33053
|
+
// Si está en sources, es exitoso
|
|
33054
|
+
response: "OK",
|
|
33055
|
+
count: portalData.images?.length || 0
|
|
33056
|
+
};
|
|
33057
|
+
if (Array.isArray(portalData.images)) {
|
|
33058
|
+
portalData.images.forEach((img) => {
|
|
33059
|
+
const imageUrl = img.url || img.thumbnail;
|
|
33060
|
+
if (!imageUrl || seenUrls.has(imageUrl)) {
|
|
33061
|
+
return;
|
|
33062
|
+
}
|
|
33063
|
+
seenUrls.add(imageUrl);
|
|
33064
|
+
allImages.push({
|
|
33065
|
+
...img,
|
|
33066
|
+
source: portalId,
|
|
33067
|
+
sourceTitle: portalData.meta?.title || portalId,
|
|
33068
|
+
preview: img.thumbnail || img.url,
|
|
33069
|
+
full: img.url
|
|
33070
|
+
});
|
|
33071
|
+
});
|
|
33072
|
+
if (!paginationInfo2 && portalData.pagination) {
|
|
33073
|
+
paginationInfo2 = portalData.pagination;
|
|
33074
|
+
}
|
|
33075
|
+
}
|
|
33076
|
+
});
|
|
33077
|
+
searchCacheRef.current[cacheKey] = {
|
|
33078
|
+
images: allImages,
|
|
33079
|
+
portalResults: newPortalResults,
|
|
33080
|
+
paginationInfo: paginationInfo2
|
|
33081
|
+
};
|
|
33082
|
+
setImages(allImages);
|
|
33083
|
+
setPortalResults(newPortalResults);
|
|
33084
|
+
setPaginationInfo(paginationInfo2);
|
|
33085
|
+
setCurrentPage(page);
|
|
33086
|
+
setFailedImages(/* @__PURE__ */ new Set());
|
|
33087
|
+
if (allImages.length === 0 && !data?.result?.success) {
|
|
33088
|
+
setError(data?.result?.message || "No se encontraron imágenes");
|
|
33089
|
+
}
|
|
33090
|
+
} catch (err) {
|
|
33091
|
+
setError(err.message || "Error al buscar imágenes en portales");
|
|
33092
|
+
} finally {
|
|
33093
|
+
setLoading(false);
|
|
33094
|
+
}
|
|
31926
33095
|
};
|
|
31927
33096
|
const handleSearch = (e) => {
|
|
31928
33097
|
e.preventDefault();
|
|
31929
|
-
|
|
31930
|
-
|
|
33098
|
+
performSearch(1);
|
|
33099
|
+
};
|
|
33100
|
+
const handlePageChange = (newPage) => {
|
|
33101
|
+
if (newPage < 1) return;
|
|
33102
|
+
performSearch(newPage);
|
|
33103
|
+
};
|
|
33104
|
+
const handleImageSelect = async (img) => {
|
|
33105
|
+
setDownloadingUrl(img.url || img.full);
|
|
33106
|
+
setError(null);
|
|
33107
|
+
try {
|
|
33108
|
+
const imageUrl = img.full || img.url || img.preview;
|
|
33109
|
+
if (!imageUrl) {
|
|
33110
|
+
throw new Error("No se encontró URL de la imagen");
|
|
33111
|
+
}
|
|
33112
|
+
const apiBaseUrl = getBaseUrl({ prod });
|
|
33113
|
+
const proxyUrl = `${apiBaseUrl}/api/atenea/proxy?url=${encodeURIComponent(imageUrl)}`;
|
|
33114
|
+
const response = await fetch(proxyUrl);
|
|
33115
|
+
if (!response.ok) {
|
|
33116
|
+
throw new Error(`Error al descargar: ${response.status}`);
|
|
33117
|
+
}
|
|
33118
|
+
const blob = await response.blob();
|
|
33119
|
+
const filename = img.filename || img.title || `portal-${img.id || Date.now()}.jpg`;
|
|
33120
|
+
const file = new File([blob], filename, {
|
|
33121
|
+
type: blob.type || "image/jpeg"
|
|
33122
|
+
});
|
|
33123
|
+
if (onSelected) onSelected(file);
|
|
33124
|
+
} catch (err) {
|
|
33125
|
+
setError(err.message || "No se pudo recuperar la imagen del portal");
|
|
33126
|
+
} finally {
|
|
33127
|
+
setDownloadingUrl(null);
|
|
33128
|
+
}
|
|
31931
33129
|
};
|
|
31932
|
-
|
|
31933
|
-
/* @__PURE__ */
|
|
31934
|
-
|
|
31935
|
-
|
|
31936
|
-
|
|
33130
|
+
if (portalSourcesHook.loading) {
|
|
33131
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center py-8", children: [
|
|
33132
|
+
/* @__PURE__ */ jsx("span", { className: "limbo-loader mr-2" }),
|
|
33133
|
+
" Cargando portales disponibles..."
|
|
33134
|
+
] });
|
|
33135
|
+
}
|
|
33136
|
+
if (!portalSourcesHook.sources.length) {
|
|
33137
|
+
return /* @__PURE__ */ jsx("div", { className: "alert alert-warning", children: "No hay portales externos disponibles." });
|
|
33138
|
+
}
|
|
33139
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
|
|
33140
|
+
/* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-brand-blue-1000", children: "Buscar en Portales Externos" }),
|
|
33141
|
+
/* @__PURE__ */ jsxs(
|
|
33142
|
+
"form",
|
|
33143
|
+
{
|
|
33144
|
+
onSubmit: handleSearch,
|
|
33145
|
+
className: "flex flex-col gap-4 border-t-1 pt-4 border-brand-blue-200",
|
|
33146
|
+
children: [
|
|
33147
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
33148
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
33149
|
+
/* @__PURE__ */ jsxs("label", { className: "text-sm font-medium text-brand-blue-1000", children: [
|
|
33150
|
+
"Portales (",
|
|
33151
|
+
selectedPortals.length,
|
|
33152
|
+
" seleccionados)"
|
|
33153
|
+
] }),
|
|
33154
|
+
/* @__PURE__ */ jsx(
|
|
33155
|
+
"button",
|
|
33156
|
+
{
|
|
33157
|
+
type: "button",
|
|
33158
|
+
onClick: handleSelectAll,
|
|
33159
|
+
className: "text-xs text-brand-blue-800 hover:underline",
|
|
33160
|
+
children: selectedPortals.length === portalSourcesHook.sources.length ? "Deseleccionar todos" : "Seleccionar todos"
|
|
33161
|
+
}
|
|
33162
|
+
)
|
|
33163
|
+
] }),
|
|
33164
|
+
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 md:grid-cols-3 gap-2", children: portalSourcesHook.sources.map((portal) => /* @__PURE__ */ jsxs(
|
|
33165
|
+
"label",
|
|
33166
|
+
{
|
|
33167
|
+
className: "flex items-center gap-2 p-2 border border-brand-blue-200 rounded cursor-pointer hover:bg-brand-blue-050 transition",
|
|
33168
|
+
children: [
|
|
33169
|
+
/* @__PURE__ */ jsx(
|
|
33170
|
+
"input",
|
|
33171
|
+
{
|
|
33172
|
+
type: "checkbox",
|
|
33173
|
+
checked: selectedPortals.includes(portal.id),
|
|
33174
|
+
onChange: () => handlePortalToggle(portal.id),
|
|
33175
|
+
disabled,
|
|
33176
|
+
className: "w-4 h-4"
|
|
33177
|
+
}
|
|
33178
|
+
),
|
|
33179
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm", children: portal.title })
|
|
33180
|
+
]
|
|
33181
|
+
},
|
|
33182
|
+
portal.id
|
|
33183
|
+
)) })
|
|
33184
|
+
] }),
|
|
33185
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
33186
|
+
/* @__PURE__ */ jsx(
|
|
33187
|
+
"label",
|
|
33188
|
+
{
|
|
33189
|
+
htmlFor: "portal-search-name",
|
|
33190
|
+
className: "text-sm font-medium text-brand-blue-1000",
|
|
33191
|
+
children: "Buscar por nombre (opcional)"
|
|
33192
|
+
}
|
|
33193
|
+
),
|
|
33194
|
+
/* @__PURE__ */ jsx(
|
|
33195
|
+
"input",
|
|
33196
|
+
{
|
|
33197
|
+
id: "portal-search-name",
|
|
33198
|
+
type: "text",
|
|
33199
|
+
value: searchName,
|
|
33200
|
+
onChange: (e) => setSearchName(e.target.value),
|
|
33201
|
+
className: "limbo-input",
|
|
33202
|
+
placeholder: "Deja vacío para ver todas las imágenes",
|
|
33203
|
+
disabled
|
|
33204
|
+
}
|
|
33205
|
+
)
|
|
33206
|
+
] }),
|
|
33207
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
33208
|
+
/* @__PURE__ */ jsx(
|
|
33209
|
+
"label",
|
|
33210
|
+
{
|
|
33211
|
+
htmlFor: "portal-limit",
|
|
33212
|
+
className: "text-sm font-medium text-brand-blue-1000",
|
|
33213
|
+
children: "Imágenes por página"
|
|
33214
|
+
}
|
|
33215
|
+
),
|
|
33216
|
+
/* @__PURE__ */ jsxs(
|
|
33217
|
+
"select",
|
|
33218
|
+
{
|
|
33219
|
+
id: "portal-limit",
|
|
33220
|
+
value: limit,
|
|
33221
|
+
onChange: (e) => setLimit(Number(e.target.value)),
|
|
33222
|
+
className: "limbo-input",
|
|
33223
|
+
disabled,
|
|
33224
|
+
children: [
|
|
33225
|
+
/* @__PURE__ */ jsx("option", { value: 10, children: "10" }),
|
|
33226
|
+
/* @__PURE__ */ jsx("option", { value: 20, children: "20" }),
|
|
33227
|
+
/* @__PURE__ */ jsx("option", { value: 50, children: "50" }),
|
|
33228
|
+
/* @__PURE__ */ jsx("option", { value: 100, children: "100" })
|
|
33229
|
+
]
|
|
33230
|
+
}
|
|
33231
|
+
)
|
|
33232
|
+
] }),
|
|
33233
|
+
/* @__PURE__ */ jsx(
|
|
33234
|
+
"button",
|
|
33235
|
+
{
|
|
33236
|
+
type: "submit",
|
|
33237
|
+
disabled: loading || disabled || selectedPortals.length === 0,
|
|
33238
|
+
className: `limbo-btn w-full ${loading || selectedPortals.length === 0 ? "cursor-not-allowed limbo-btn-disabled" : "limbo-btn-primary"}`,
|
|
33239
|
+
style: { minHeight: 44 },
|
|
33240
|
+
children: loading ? "Buscando..." : "Buscar imágenes"
|
|
33241
|
+
}
|
|
33242
|
+
)
|
|
33243
|
+
]
|
|
33244
|
+
}
|
|
33245
|
+
),
|
|
33246
|
+
portalSourcesHook.error && /* @__PURE__ */ jsx("div", { className: "alert alert-danger", children: portalSourcesHook.error }),
|
|
33247
|
+
error && /* @__PURE__ */ jsx("div", { className: "alert alert-danger", children: error }),
|
|
33248
|
+
Object.keys(portalResults).length > 0 && /* @__PURE__ */ jsxs("div", { className: "bg-neutral-50 rounded-lg p-3", children: [
|
|
33249
|
+
/* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold mb-2", children: "Resultados por portal:" }),
|
|
33250
|
+
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-2 text-sm", children: Object.entries(portalResults).map(([portalId, result]) => /* @__PURE__ */ jsxs(
|
|
33251
|
+
"div",
|
|
33252
|
+
{
|
|
33253
|
+
className: `flex items-center justify-between p-2 rounded ${result.status === 200 ? "bg-green-50" : "bg-red-50"}`,
|
|
33254
|
+
children: [
|
|
33255
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: result.title }),
|
|
33256
|
+
/* @__PURE__ */ jsx(
|
|
33257
|
+
"span",
|
|
33258
|
+
{
|
|
33259
|
+
className: result.status === 200 ? "text-green-700" : "text-red-700",
|
|
33260
|
+
children: result.status === 200 ? `${result.count} imágenes` : result.response
|
|
33261
|
+
}
|
|
33262
|
+
)
|
|
33263
|
+
]
|
|
33264
|
+
},
|
|
33265
|
+
portalId
|
|
33266
|
+
)) })
|
|
33267
|
+
] }),
|
|
33268
|
+
(images.length > 0 || loading) && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
33269
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-6 relative", "aria-live": "polite", children: [
|
|
33270
|
+
loading && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-white/80 z-10 flex items-center justify-center rounded-lg", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2", children: [
|
|
33271
|
+
/* @__PURE__ */ jsx("span", { className: "limbo-loader" }),
|
|
33272
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm text-brand-blue-800", children: "Buscando imágenes..." })
|
|
33273
|
+
] }) }),
|
|
33274
|
+
/* @__PURE__ */ jsx(
|
|
33275
|
+
"div",
|
|
33276
|
+
{
|
|
33277
|
+
className: `grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 ${loading ? "opacity-50" : ""}`,
|
|
33278
|
+
children: images.map((img, idx) => {
|
|
33279
|
+
const imageKey = `${img.source}-${img.id || idx}`;
|
|
33280
|
+
const imageUrl = img.preview || img.thumbnail || img.url || img.full;
|
|
33281
|
+
if (failedImages.has(imageKey)) {
|
|
33282
|
+
return null;
|
|
33283
|
+
}
|
|
33284
|
+
return /* @__PURE__ */ jsxs(
|
|
33285
|
+
"div",
|
|
33286
|
+
{
|
|
33287
|
+
className: "border border-brand-blue-200 rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-md transition-shadow",
|
|
33288
|
+
children: [
|
|
33289
|
+
/* @__PURE__ */ jsxs("div", { className: "relative aspect-video bg-neutral-100", children: [
|
|
33290
|
+
/* @__PURE__ */ jsx(
|
|
33291
|
+
ValidatedImage,
|
|
33292
|
+
{
|
|
33293
|
+
src: imageUrl,
|
|
33294
|
+
alt: img.title || img.filename || `Imagen ${idx + 1}`,
|
|
33295
|
+
className: "object-cover w-full h-full",
|
|
33296
|
+
onError: () => {
|
|
33297
|
+
setFailedImages((prev) => {
|
|
33298
|
+
const newSet = new Set(prev);
|
|
33299
|
+
newSet.add(imageKey);
|
|
33300
|
+
return newSet;
|
|
33301
|
+
});
|
|
33302
|
+
}
|
|
33303
|
+
}
|
|
33304
|
+
),
|
|
33305
|
+
/* @__PURE__ */ jsx("span", { className: "absolute top-1 left-1 bg-black/60 text-white text-xs px-2 py-1 rounded", children: img.sourceTitle }),
|
|
33306
|
+
img.id && /* @__PURE__ */ jsxs("span", { className: "absolute bottom-1 right-1 bg-black/60 text-white text-xs px-2 py-1 rounded", children: [
|
|
33307
|
+
"ID: ",
|
|
33308
|
+
img.id
|
|
33309
|
+
] })
|
|
33310
|
+
] }),
|
|
33311
|
+
/* @__PURE__ */ jsxs("div", { className: "p-2", children: [
|
|
33312
|
+
img.title && /* @__PURE__ */ jsx(
|
|
33313
|
+
"p",
|
|
33314
|
+
{
|
|
33315
|
+
className: "text-xs text-neutral-700 mb-1 truncate",
|
|
33316
|
+
title: img.title,
|
|
33317
|
+
children: img.title
|
|
33318
|
+
}
|
|
33319
|
+
),
|
|
33320
|
+
/* @__PURE__ */ jsx(
|
|
33321
|
+
"button",
|
|
33322
|
+
{
|
|
33323
|
+
className: `limbo-btn w-full text-sm ${downloadingUrl === (img.url || img.full) ? "limbo-btn-disabled cursor-not-allowed" : "limbo-btn-primary"}`,
|
|
33324
|
+
onClick: () => handleImageSelect(img),
|
|
33325
|
+
disabled: loading || disabled || downloadingUrl === (img.url || img.full),
|
|
33326
|
+
children: downloadingUrl === (img.url || img.full) ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
33327
|
+
/* @__PURE__ */ jsx("span", { className: "limbo-loader limbo-loader--sm mr-1" }),
|
|
33328
|
+
"Descargando..."
|
|
33329
|
+
] }) : "Seleccionar"
|
|
33330
|
+
}
|
|
33331
|
+
)
|
|
33332
|
+
] })
|
|
33333
|
+
]
|
|
33334
|
+
},
|
|
33335
|
+
imageKey
|
|
33336
|
+
);
|
|
33337
|
+
})
|
|
33338
|
+
}
|
|
33339
|
+
)
|
|
33340
|
+
] }),
|
|
33341
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-6 flex items-center justify-center gap-2", children: [
|
|
33342
|
+
/* @__PURE__ */ jsxs(
|
|
33343
|
+
"button",
|
|
33344
|
+
{
|
|
33345
|
+
onClick: () => handlePageChange(currentPage - 1),
|
|
33346
|
+
disabled: currentPage === 1 || loading,
|
|
33347
|
+
className: "limbo-btn limbo-btn-secondary px-4 py-2 disabled:opacity-50" + (currentPage === 1 || loading ? " pointer-events-none cursor-default" : " limbo-btn-primary"),
|
|
33348
|
+
children: [
|
|
33349
|
+
/* @__PURE__ */ jsx("span", { className: "icon icon-arrow-left-white icon--sm" }),
|
|
33350
|
+
" ",
|
|
33351
|
+
"Anterior"
|
|
33352
|
+
]
|
|
33353
|
+
}
|
|
33354
|
+
),
|
|
33355
|
+
/* @__PURE__ */ jsxs("span", { className: "px-4 py-2 text-sm text-neutral-700", children: [
|
|
33356
|
+
"Página ",
|
|
33357
|
+
currentPage,
|
|
33358
|
+
paginationInfo && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
33359
|
+
" ",
|
|
33360
|
+
"de ",
|
|
33361
|
+
paginationInfo.pages,
|
|
33362
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-neutral-500 block", children: [
|
|
33363
|
+
"(",
|
|
33364
|
+
paginationInfo.total,
|
|
33365
|
+
" imágenes totales)"
|
|
33366
|
+
] })
|
|
33367
|
+
] })
|
|
33368
|
+
] }),
|
|
33369
|
+
/* @__PURE__ */ jsxs(
|
|
33370
|
+
"button",
|
|
33371
|
+
{
|
|
33372
|
+
onClick: () => handlePageChange(currentPage + 1),
|
|
33373
|
+
disabled: loading || paginationInfo && currentPage >= paginationInfo.pages,
|
|
33374
|
+
className: "limbo-btn limbo-btn-secondary px-4 py-2 disabled:opacity-50" + (loading || paginationInfo && currentPage >= paginationInfo.pages ? " pointer-events-none cursor-default" : " limbo-btn-primary"),
|
|
33375
|
+
children: [
|
|
33376
|
+
"Siguiente",
|
|
33377
|
+
" ",
|
|
33378
|
+
/* @__PURE__ */ jsx("span", { className: "icon icon-arrow-right-white icon--sm" })
|
|
33379
|
+
]
|
|
33380
|
+
}
|
|
33381
|
+
)
|
|
33382
|
+
] })
|
|
31937
33383
|
] }),
|
|
31938
|
-
|
|
31939
|
-
|
|
33384
|
+
!loading && images.length === 0 && Object.keys(portalResults).length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-6 text-center text-neutral-600 py-8 bg-neutral-50 rounded-lg", children: [
|
|
33385
|
+
/* @__PURE__ */ jsx("span", { className: "icon icon-search icon--lg mb-2" }),
|
|
33386
|
+
/* @__PURE__ */ jsx("p", { children: "No se encontraron imágenes en los portales seleccionados" }),
|
|
33387
|
+
searchName && /* @__PURE__ */ jsx("p", { className: "text-sm mt-1", children: "Intenta con otros términos o sin filtro de nombre" })
|
|
33388
|
+
] })
|
|
31940
33389
|
] });
|
|
31941
33390
|
}
|
|
31942
33391
|
const TABS = [
|
|
@@ -32047,8 +33496,32 @@ function UploadForm({
|
|
|
32047
33496
|
}
|
|
32048
33497
|
}
|
|
32049
33498
|
),
|
|
32050
|
-
activeTab === "stock" && /* @__PURE__ */ jsx(
|
|
32051
|
-
|
|
33499
|
+
activeTab === "stock" && /* @__PURE__ */ jsx(
|
|
33500
|
+
TabStock,
|
|
33501
|
+
{
|
|
33502
|
+
apiKey,
|
|
33503
|
+
prod,
|
|
33504
|
+
disabled,
|
|
33505
|
+
onSelected: (file2) => {
|
|
33506
|
+
setActiveTab("upload");
|
|
33507
|
+
setFile(file2);
|
|
33508
|
+
setPreviewUrl(URL.createObjectURL(file2));
|
|
33509
|
+
}
|
|
33510
|
+
}
|
|
33511
|
+
),
|
|
33512
|
+
activeTab === "portals" && /* @__PURE__ */ jsx(
|
|
33513
|
+
TabPortals,
|
|
33514
|
+
{
|
|
33515
|
+
apiKey,
|
|
33516
|
+
prod,
|
|
33517
|
+
disabled,
|
|
33518
|
+
onSelected: (file2) => {
|
|
33519
|
+
setActiveTab("upload");
|
|
33520
|
+
setFile(file2);
|
|
33521
|
+
setPreviewUrl(URL.createObjectURL(file2));
|
|
33522
|
+
}
|
|
33523
|
+
}
|
|
33524
|
+
)
|
|
32052
33525
|
] })
|
|
32053
33526
|
] });
|
|
32054
33527
|
}
|
|
@@ -34551,150 +36024,69 @@ CropperImage.$define();
|
|
|
34551
36024
|
CropperSelection.$define();
|
|
34552
36025
|
CropperShade.$define();
|
|
34553
36026
|
CropperViewer.$define();
|
|
34554
|
-
function
|
|
34555
|
-
if (!v2Response?.success || !Array.isArray(v2Response?.data)) {
|
|
34556
|
-
return { result: [] };
|
|
34557
|
-
}
|
|
34558
|
-
const legacyAssets = v2Response.data.map((asset) => {
|
|
34559
|
-
return {
|
|
34560
|
-
id: asset.id,
|
|
34561
|
-
filename: asset.filename || asset.original_filename,
|
|
34562
|
-
mime_type: asset.mime_type,
|
|
34563
|
-
file_size: asset.file_size,
|
|
34564
|
-
width: asset.width,
|
|
34565
|
-
height: asset.height,
|
|
34566
|
-
upload_date: asset.upload_date || asset.created_at,
|
|
34567
|
-
processing_status: asset.processing_status || asset.status,
|
|
34568
|
-
// URL principal
|
|
34569
|
-
url: asset.master_url || asset.master?.url_signed,
|
|
34570
|
-
webp_available: asset.webp_available || !!asset.webp_url,
|
|
34571
|
-
// Solo metadatos básicos en listado
|
|
34572
|
-
metadata: asset.metadata || {},
|
|
34573
|
-
variants_count: asset.variants_count || 0
|
|
34574
|
-
};
|
|
34575
|
-
});
|
|
34576
|
-
return { result: legacyAssets };
|
|
34577
|
-
}
|
|
34578
|
-
function adaptUploadFromV2(v2Response) {
|
|
34579
|
-
if (!v2Response?.success || !v2Response?.data) {
|
|
34580
|
-
return { result: null };
|
|
34581
|
-
}
|
|
34582
|
-
const asset = v2Response.data;
|
|
34583
|
-
const legacyResponse = {
|
|
34584
|
-
id: asset.id,
|
|
34585
|
-
filename: asset.original_filename,
|
|
34586
|
-
mime_type: asset.mime_type,
|
|
34587
|
-
file_size: asset.file_size,
|
|
34588
|
-
width: asset.width,
|
|
34589
|
-
height: asset.height,
|
|
34590
|
-
status: asset.status,
|
|
34591
|
-
upload_date: asset.created_at,
|
|
34592
|
-
// URL del master
|
|
34593
|
-
url: asset.master?.url_signed,
|
|
34594
|
-
master_format: asset.master?.format,
|
|
34595
|
-
// Estado de procesamiento
|
|
34596
|
-
processing: asset.processing || {
|
|
34597
|
-
master_webp: asset.status === "processing" ? "queued" : "completed",
|
|
34598
|
-
variants: asset.status === "processing" ? "queued" : "completed"
|
|
34599
|
-
},
|
|
34600
|
-
// Información adicional
|
|
34601
|
-
checksum: asset.checksum,
|
|
34602
|
-
storage_path: asset.storage_path_base,
|
|
34603
|
-
metadata: asset.metadata || {}
|
|
34604
|
-
};
|
|
34605
|
-
return { result: legacyResponse };
|
|
34606
|
-
}
|
|
34607
|
-
function adaptErrorFromV2(error) {
|
|
34608
|
-
if (error.message && !error.message.includes("API Error:")) {
|
|
34609
|
-
return error;
|
|
34610
|
-
}
|
|
34611
|
-
let errorMessage = error.message || "Unknown API error";
|
|
34612
|
-
if (errorMessage.startsWith("API Error: ")) {
|
|
34613
|
-
errorMessage = errorMessage.substring(11);
|
|
34614
|
-
}
|
|
34615
|
-
return new Error(errorMessage);
|
|
34616
|
-
}
|
|
34617
|
-
const BASE_PATH = "/api/v2";
|
|
34618
|
-
async function listAssets(params = {}) {
|
|
34619
|
-
try {
|
|
34620
|
-
const queryString = new URLSearchParams(params).toString();
|
|
34621
|
-
const endpoint = queryString ? `/assets?${queryString}` : "/assets";
|
|
34622
|
-
const response = await callApi({
|
|
34623
|
-
endpoint,
|
|
34624
|
-
method: "GET",
|
|
34625
|
-
basePath: BASE_PATH,
|
|
34626
|
-
useJWT: true
|
|
34627
|
-
});
|
|
34628
|
-
return adaptAssetsListFromV2(response);
|
|
34629
|
-
} catch (error) {
|
|
34630
|
-
throw adaptErrorFromV2(error);
|
|
34631
|
-
}
|
|
34632
|
-
}
|
|
34633
|
-
async function uploadAsset(file, uploaded_by = null, store_original = false) {
|
|
34634
|
-
try {
|
|
34635
|
-
const formData = new FormData();
|
|
34636
|
-
formData.append("file", file);
|
|
34637
|
-
if (uploaded_by) formData.append("uploaded_by", uploaded_by);
|
|
34638
|
-
if (store_original) formData.append("store_original", store_original.toString());
|
|
34639
|
-
const response = await callApi({
|
|
34640
|
-
endpoint: "/assets",
|
|
34641
|
-
method: "POST",
|
|
34642
|
-
body: formData,
|
|
34643
|
-
basePath: BASE_PATH,
|
|
34644
|
-
isFormData: true,
|
|
34645
|
-
useJWT: true
|
|
34646
|
-
});
|
|
34647
|
-
return adaptUploadFromV2(response);
|
|
34648
|
-
} catch (error) {
|
|
34649
|
-
throw adaptErrorFromV2(error);
|
|
34650
|
-
}
|
|
34651
|
-
}
|
|
34652
|
-
async function deleteAsset(assetId) {
|
|
34653
|
-
try {
|
|
34654
|
-
const response = await callApi({
|
|
34655
|
-
endpoint: `/assets/${assetId}`,
|
|
34656
|
-
method: "DELETE",
|
|
34657
|
-
basePath: BASE_PATH,
|
|
34658
|
-
useJWT: true
|
|
34659
|
-
});
|
|
34660
|
-
return response;
|
|
34661
|
-
} catch (error) {
|
|
34662
|
-
throw adaptErrorFromV2(error);
|
|
34663
|
-
}
|
|
34664
|
-
}
|
|
34665
|
-
function useUploadImage() {
|
|
36027
|
+
function useCreateVariant() {
|
|
34666
36028
|
const [loading, setLoading] = useState(false);
|
|
34667
36029
|
const [error, setError] = useState(null);
|
|
34668
|
-
const [
|
|
34669
|
-
const
|
|
34670
|
-
if (!
|
|
34671
|
-
setError("
|
|
36030
|
+
const [createdVariant, setCreatedVariant] = useState(null);
|
|
36031
|
+
const createVariant = async (assetId, variantConfig) => {
|
|
36032
|
+
if (!assetId || !variantConfig) {
|
|
36033
|
+
setError("ID de asset y configuración de variante son requeridos");
|
|
34672
36034
|
return null;
|
|
34673
36035
|
}
|
|
34674
36036
|
setLoading(true);
|
|
34675
36037
|
setError(null);
|
|
34676
|
-
|
|
36038
|
+
setCreatedVariant(null);
|
|
34677
36039
|
try {
|
|
34678
|
-
const
|
|
34679
|
-
|
|
34680
|
-
|
|
34681
|
-
|
|
36040
|
+
const response = await generateVariant(assetId, {
|
|
36041
|
+
variant_name: variantConfig.name,
|
|
36042
|
+
width: variantConfig.width,
|
|
36043
|
+
height: variantConfig.height,
|
|
36044
|
+
crop_params: variantConfig.crop_params,
|
|
36045
|
+
preset_aspect: variantConfig.preset_aspect,
|
|
36046
|
+
preset_size: variantConfig.preset_size,
|
|
36047
|
+
output_format: variantConfig.output_format
|
|
36048
|
+
});
|
|
36049
|
+
if (response?.result) {
|
|
36050
|
+
setCreatedVariant(response.result);
|
|
36051
|
+
return response.result;
|
|
36052
|
+
} else {
|
|
36053
|
+
throw new Error("No se pudo crear la variante");
|
|
36054
|
+
}
|
|
34682
36055
|
} catch (err) {
|
|
34683
|
-
|
|
36056
|
+
console.error("Error creating variant:", err);
|
|
36057
|
+
setError(err.message || "Error desconocido al crear variante");
|
|
34684
36058
|
return null;
|
|
34685
36059
|
} finally {
|
|
34686
36060
|
setLoading(false);
|
|
34687
36061
|
}
|
|
34688
36062
|
};
|
|
36063
|
+
const createCropVariant = async (assetId, cropData, options = {}) => {
|
|
36064
|
+
const variantConfig = {
|
|
36065
|
+
name: options.name || `crop_${Date.now()}`,
|
|
36066
|
+
width: options.width || 800,
|
|
36067
|
+
// Usar las dimensiones calculadas de options
|
|
36068
|
+
height: options.height || 600,
|
|
36069
|
+
// Usar las dimensiones calculadas de options
|
|
36070
|
+
output_format: options.format || "webp",
|
|
36071
|
+
crop_params: {
|
|
36072
|
+
x: cropData.x || 0,
|
|
36073
|
+
y: cropData.y || 0,
|
|
36074
|
+
width: cropData.width || 1,
|
|
36075
|
+
height: cropData.height || 1
|
|
36076
|
+
}
|
|
36077
|
+
};
|
|
36078
|
+
return await createVariant(assetId, variantConfig);
|
|
36079
|
+
};
|
|
34689
36080
|
const reset = () => {
|
|
34690
36081
|
setError(null);
|
|
34691
|
-
|
|
36082
|
+
setCreatedVariant(null);
|
|
34692
36083
|
};
|
|
34693
36084
|
return {
|
|
34694
|
-
|
|
36085
|
+
createVariant,
|
|
36086
|
+
createCropVariant,
|
|
34695
36087
|
loading,
|
|
34696
36088
|
error,
|
|
34697
|
-
|
|
36089
|
+
createdVariant,
|
|
34698
36090
|
reset
|
|
34699
36091
|
};
|
|
34700
36092
|
}
|
|
@@ -35406,8 +36798,8 @@ function CropperView({
|
|
|
35406
36798
|
onCancel,
|
|
35407
36799
|
onDelete,
|
|
35408
36800
|
deleting = false,
|
|
35409
|
-
|
|
35410
|
-
|
|
36801
|
+
onVariantCreated = null
|
|
36802
|
+
// Callback cuando se crea una variante
|
|
35411
36803
|
}) {
|
|
35412
36804
|
const [showPreview, setShowPreview] = useState(false);
|
|
35413
36805
|
const [previewUrl, setPreviewUrl] = useState(null);
|
|
@@ -35435,10 +36827,10 @@ function CropperView({
|
|
|
35435
36827
|
];
|
|
35436
36828
|
}, []);
|
|
35437
36829
|
const {
|
|
35438
|
-
|
|
35439
|
-
loading:
|
|
35440
|
-
error:
|
|
35441
|
-
} =
|
|
36830
|
+
createCropVariant,
|
|
36831
|
+
loading: creatingVariant,
|
|
36832
|
+
error: variantError
|
|
36833
|
+
} = useCreateVariant();
|
|
35442
36834
|
const cropper = useCropper(image, {
|
|
35443
36835
|
aspectRatio,
|
|
35444
36836
|
showGrid,
|
|
@@ -35449,6 +36841,13 @@ function CropperView({
|
|
|
35449
36841
|
const { refs, state, transform, selection, utils } = cropper;
|
|
35450
36842
|
const { canvasRef, imageRef, selectionRef } = refs;
|
|
35451
36843
|
const { cropData, imageInfo, canExport, transformVersion } = state;
|
|
36844
|
+
const effectiveImageInfo = useMemo(() => imageInfo || {
|
|
36845
|
+
naturalWidth: image.width || 1920,
|
|
36846
|
+
// Usar dimensiones de la imagen prop como fallback
|
|
36847
|
+
naturalHeight: image.height || 1080,
|
|
36848
|
+
currentWidth: image.width || 1920,
|
|
36849
|
+
currentHeight: image.height || 1080
|
|
36850
|
+
}, [imageInfo, image.width, image.height]);
|
|
35452
36851
|
const toggleGrid = useCallback(() => setShowGrid((v) => !v), []);
|
|
35453
36852
|
const toggleShade = useCallback(() => setShade((v) => !v), []);
|
|
35454
36853
|
const toggleTips = useCallback(() => setShowTips((v) => !v), []);
|
|
@@ -35550,50 +36949,79 @@ function CropperView({
|
|
|
35550
36949
|
alert(errorMsg);
|
|
35551
36950
|
return;
|
|
35552
36951
|
}
|
|
35553
|
-
|
|
36952
|
+
if (!state.isReady) {
|
|
36953
|
+
const errorMsg = "El cropper aún no está inicializado. Espera un momento e inténtalo de nuevo.";
|
|
36954
|
+
accessibilityManager?.announceError(errorMsg);
|
|
36955
|
+
alert(errorMsg);
|
|
36956
|
+
return;
|
|
36957
|
+
}
|
|
36958
|
+
accessibilityManager?.announce("Creando variante recortada de la imagen");
|
|
35554
36959
|
try {
|
|
35555
|
-
|
|
35556
|
-
|
|
35557
|
-
|
|
35558
|
-
|
|
35559
|
-
|
|
36960
|
+
if (!cropData || !effectiveImageInfo) {
|
|
36961
|
+
console.error("❌ Datos faltantes:", { cropData, effectiveImageInfo });
|
|
36962
|
+
throw new Error(`No hay datos de recorte disponibles. CropData: ${!!cropData}, ImageInfo: ${!!effectiveImageInfo}`);
|
|
36963
|
+
}
|
|
36964
|
+
if (!cropData.x && cropData.x !== 0 || !cropData.y && cropData.y !== 0 || !cropData.width || !cropData.height) {
|
|
36965
|
+
console.error("❌ CropData inválido:", cropData);
|
|
36966
|
+
throw new Error("Los datos de recorte no tienen las propiedades esperadas");
|
|
36967
|
+
}
|
|
36968
|
+
if (!effectiveImageInfo.naturalWidth || !effectiveImageInfo.naturalHeight) {
|
|
36969
|
+
console.error("❌ ImageInfo inválido:", effectiveImageInfo);
|
|
36970
|
+
throw new Error("Los datos de imagen no tienen las dimensiones esperadas");
|
|
36971
|
+
}
|
|
36972
|
+
const { x, y, width, height } = cropData;
|
|
36973
|
+
const { naturalWidth, naturalHeight } = effectiveImageInfo;
|
|
36974
|
+
const cropParams = {
|
|
36975
|
+
x: x / naturalWidth,
|
|
36976
|
+
y: y / naturalHeight,
|
|
36977
|
+
width: width / naturalWidth,
|
|
36978
|
+
height: height / naturalHeight
|
|
36979
|
+
};
|
|
36980
|
+
const cropAspectRatio = width / height;
|
|
36981
|
+
let variantWidth, variantHeight;
|
|
36982
|
+
const maxDimension = 1200;
|
|
36983
|
+
if (cropAspectRatio > 1) {
|
|
36984
|
+
variantWidth = Math.min(width, maxDimension);
|
|
36985
|
+
variantHeight = Math.round(variantWidth / cropAspectRatio);
|
|
36986
|
+
} else {
|
|
36987
|
+
variantHeight = Math.min(height, maxDimension);
|
|
36988
|
+
variantWidth = Math.round(variantHeight * cropAspectRatio);
|
|
36989
|
+
}
|
|
36990
|
+
const ts = Date.now();
|
|
36991
|
+
const [name] = image.filename.split(".");
|
|
36992
|
+
const variantName = `${name}_crop_${ts}`;
|
|
36993
|
+
const result = await createCropVariant(image.id, cropParams, {
|
|
36994
|
+
name: variantName,
|
|
36995
|
+
width: variantWidth,
|
|
36996
|
+
height: variantHeight,
|
|
36997
|
+
format: "webp",
|
|
36998
|
+
quality: 90
|
|
35560
36999
|
});
|
|
35561
|
-
if (
|
|
35562
|
-
|
|
35563
|
-
|
|
35564
|
-
|
|
35565
|
-
|
|
35566
|
-
|
|
35567
|
-
|
|
35568
|
-
const newName = `${name}_cropped_${ts}.${ext}`;
|
|
35569
|
-
const file = new File([blob], newName, {
|
|
35570
|
-
type: blob.type || "image/jpeg"
|
|
35571
|
-
});
|
|
35572
|
-
const result = await upload(file, `cropped_from_${image.id}`);
|
|
35573
|
-
if (result) {
|
|
35574
|
-
accessibilityManager?.announceSuccess(
|
|
35575
|
-
`Imagen recortada guardada como ${newName}`
|
|
35576
|
-
);
|
|
35577
|
-
onSave(result);
|
|
35578
|
-
}
|
|
35579
|
-
},
|
|
35580
|
-
"image/jpeg",
|
|
35581
|
-
0.9
|
|
35582
|
-
);
|
|
37000
|
+
if (result) {
|
|
37001
|
+
accessibilityManager?.announceSuccess(
|
|
37002
|
+
`Variante recortada creada: ${variantName}`
|
|
37003
|
+
);
|
|
37004
|
+
onVariantCreated?.(image.id, result);
|
|
37005
|
+
onSave(result);
|
|
37006
|
+
}
|
|
35583
37007
|
} catch (error) {
|
|
35584
|
-
console.warn("Error
|
|
35585
|
-
const errorMsg = "No se
|
|
37008
|
+
console.warn("Error creating crop variant:", error);
|
|
37009
|
+
const errorMsg = "No se pudo crear la variante recortada. Inténtalo de nuevo.";
|
|
35586
37010
|
accessibilityManager?.announceError(errorMsg);
|
|
35587
37011
|
alert(errorMsg);
|
|
35588
37012
|
}
|
|
35589
37013
|
}, [
|
|
35590
37014
|
canExport,
|
|
37015
|
+
cropData,
|
|
37016
|
+
imageInfo,
|
|
37017
|
+
effectiveImageInfo,
|
|
35591
37018
|
image.filename,
|
|
35592
37019
|
image.id,
|
|
35593
|
-
|
|
37020
|
+
createCropVariant,
|
|
35594
37021
|
onSave,
|
|
35595
|
-
|
|
35596
|
-
accessibilityManager
|
|
37022
|
+
onVariantCreated,
|
|
37023
|
+
accessibilityManager,
|
|
37024
|
+
state.isReady
|
|
35597
37025
|
]);
|
|
35598
37026
|
useEffect(() => {
|
|
35599
37027
|
setShowPreview(false);
|
|
@@ -35691,7 +37119,7 @@ function CropperView({
|
|
|
35691
37119
|
"button",
|
|
35692
37120
|
{
|
|
35693
37121
|
onClick: onCancel,
|
|
35694
|
-
disabled:
|
|
37122
|
+
disabled: creatingVariant,
|
|
35695
37123
|
className: "limbo-btn limbo-btn-secondary px-4 py-2 flex-1 sm:flex-initial",
|
|
35696
37124
|
children: "Cancelar"
|
|
35697
37125
|
}
|
|
@@ -35700,7 +37128,7 @@ function CropperView({
|
|
|
35700
37128
|
"button",
|
|
35701
37129
|
{
|
|
35702
37130
|
onClick: () => onDelete?.(image),
|
|
35703
|
-
disabled: deleting |
|
|
37131
|
+
disabled: deleting | creatingVariant,
|
|
35704
37132
|
className: "limbo-btn limbo-btn-danger px-4 py-2 flex-1 sm:flex-initial",
|
|
35705
37133
|
"aria-label": `Eliminar imagen ${image.filename}`,
|
|
35706
37134
|
children: deleting ? "Eliminando..." : "Eliminar"
|
|
@@ -35709,10 +37137,10 @@ function CropperView({
|
|
|
35709
37137
|
] })
|
|
35710
37138
|
] }),
|
|
35711
37139
|
/* @__PURE__ */ jsxs("div", { className: "limbo-cropper-status bg-white border-gray-100 px-4 sm:px-6 py-2 pb-0 flex-shrink-0", children: [
|
|
35712
|
-
|
|
37140
|
+
variantError && /* @__PURE__ */ jsxs("div", { className: "alert alert-danger mb-2 text-sm", role: "alert", children: [
|
|
35713
37141
|
/* @__PURE__ */ jsx("strong", { children: "Error:" }),
|
|
35714
37142
|
" ",
|
|
35715
|
-
|
|
37143
|
+
variantError
|
|
35716
37144
|
] }),
|
|
35717
37145
|
cropData && cropData.width > 0 && /* @__PURE__ */ jsxs("div", { className: "alert alert-info mb-2 text-sm", role: "status", children: [
|
|
35718
37146
|
/* @__PURE__ */ jsx("strong", { children: "Área:" }),
|
|
@@ -35798,7 +37226,7 @@ function CropperView({
|
|
|
35798
37226
|
"cropper-image",
|
|
35799
37227
|
{
|
|
35800
37228
|
ref: imageRef,
|
|
35801
|
-
src: image.path,
|
|
37229
|
+
src: image.url || image.path,
|
|
35802
37230
|
alt: image.filename,
|
|
35803
37231
|
id: "limbo-cropperjs-image",
|
|
35804
37232
|
action: "move",
|
|
@@ -35869,7 +37297,7 @@ function CropperView({
|
|
|
35869
37297
|
"button",
|
|
35870
37298
|
{
|
|
35871
37299
|
onClick: preview,
|
|
35872
|
-
disabled:
|
|
37300
|
+
disabled: creatingVariant || !canExport,
|
|
35873
37301
|
className: `w-full min-h-10 transition-colors ${showPreview ? "limbo-btn limbo-btn-danger" : "limbo-btn limbo-btn-primary"}`,
|
|
35874
37302
|
"aria-label": "Generar vista previa del recorte",
|
|
35875
37303
|
children: [
|
|
@@ -35887,10 +37315,10 @@ function CropperView({
|
|
|
35887
37315
|
"button",
|
|
35888
37316
|
{
|
|
35889
37317
|
onClick: saveCrop,
|
|
35890
|
-
disabled:
|
|
37318
|
+
disabled: creatingVariant || !cropData || !effectiveImageInfo || !canExport || !state.isReady,
|
|
35891
37319
|
className: "w-full limbo-btn limbo-btn-success min-h-10",
|
|
35892
37320
|
"aria-label": "Guardar imagen recortada",
|
|
35893
|
-
children:
|
|
37321
|
+
children: creatingVariant ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
35894
37322
|
/* @__PURE__ */ jsx("span", { className: "icon icon-save-white" }),
|
|
35895
37323
|
" ",
|
|
35896
37324
|
"Guardando..."
|
|
@@ -35904,7 +37332,7 @@ function CropperView({
|
|
|
35904
37332
|
"button",
|
|
35905
37333
|
{
|
|
35906
37334
|
onClick: resetAll,
|
|
35907
|
-
disabled:
|
|
37335
|
+
disabled: creatingVariant,
|
|
35908
37336
|
className: "w-full limbo-btn limbo-btn-secondary min-h-10",
|
|
35909
37337
|
"aria-label": "Reiniciar todas las configuraciones",
|
|
35910
37338
|
children: [
|
|
@@ -36057,7 +37485,7 @@ function CropperView({
|
|
|
36057
37485
|
{
|
|
36058
37486
|
onClick: () => handleAspectRatio(ratio.value),
|
|
36059
37487
|
className: `p-2 text-xs rounded border transition-colors cursor-pointer ${aspectRatio === ratio.value ? "bg-blue-100 border-blue-300 text-blue-800" : "bg-gray-100 border-gray-300 text-gray-700"}`,
|
|
36060
|
-
disabled:
|
|
37488
|
+
disabled: creatingVariant,
|
|
36061
37489
|
title: `Cambiar a proporción ${ratio.label}`,
|
|
36062
37490
|
children: ratio.label
|
|
36063
37491
|
},
|
|
@@ -36069,7 +37497,7 @@ function CropperView({
|
|
|
36069
37497
|
value: aspectRatio,
|
|
36070
37498
|
onChange: (e) => handleAspectRatio(e.target.value),
|
|
36071
37499
|
className: "w-full form-control hidden md:block",
|
|
36072
|
-
disabled:
|
|
37500
|
+
disabled: creatingVariant,
|
|
36073
37501
|
"aria-label": "Seleccionar proporción de aspecto",
|
|
36074
37502
|
children: allowedAspectRatios.map((ratio) => /* @__PURE__ */ jsx("option", { value: ratio.value, children: ratio.label }, ratio.value))
|
|
36075
37503
|
}
|
|
@@ -36083,7 +37511,7 @@ function CropperView({
|
|
|
36083
37511
|
{
|
|
36084
37512
|
onClick: toggleGrid,
|
|
36085
37513
|
className: `w-full flex cursor-pointer items-center justify-between p-2 rounded transition-colors ${showGrid ? "bg-blue-100 border border-blue-300 text-blue-800" : "bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200"}`,
|
|
36086
|
-
disabled:
|
|
37514
|
+
disabled: creatingVariant,
|
|
36087
37515
|
"aria-pressed": showGrid,
|
|
36088
37516
|
"aria-label": "Activar/desactivar grid",
|
|
36089
37517
|
children: [
|
|
@@ -36104,7 +37532,7 @@ function CropperView({
|
|
|
36104
37532
|
{
|
|
36105
37533
|
onClick: toggleShade,
|
|
36106
37534
|
className: `w-full flex cursor-pointer items-center justify-between p-2 rounded transition-colors ${shade ? "bg-blue-100 border border-blue-300 text-blue-800" : "bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200"}`,
|
|
36107
|
-
disabled:
|
|
37535
|
+
disabled: creatingVariant,
|
|
36108
37536
|
"aria-pressed": shade,
|
|
36109
37537
|
"aria-label": "Activar/desactivar sombreado",
|
|
36110
37538
|
children: [
|
|
@@ -36132,7 +37560,7 @@ function CropperView({
|
|
|
36132
37560
|
onClick: centerSelection,
|
|
36133
37561
|
className: "w-full p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
|
|
36134
37562
|
title: "Centrar selección",
|
|
36135
|
-
disabled:
|
|
37563
|
+
disabled: creatingVariant,
|
|
36136
37564
|
"aria-label": "Centrar área de selección",
|
|
36137
37565
|
children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxs("span", { children: [
|
|
36138
37566
|
/* @__PURE__ */ jsx("span", { className: "icon icon-radio-button icon--sm align-[middle!important] " }),
|
|
@@ -36147,7 +37575,7 @@ function CropperView({
|
|
|
36147
37575
|
onClick: resetSelection,
|
|
36148
37576
|
className: "w-full p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
|
|
36149
37577
|
title: "Reiniciar selección",
|
|
36150
|
-
disabled:
|
|
37578
|
+
disabled: creatingVariant,
|
|
36151
37579
|
"aria-label": "Reiniciar área de selección",
|
|
36152
37580
|
children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxs("span", { children: [
|
|
36153
37581
|
/* @__PURE__ */ jsx("span", { className: "icon icon-refresh icon--sm lign-[middle!important] " }),
|
|
@@ -36165,7 +37593,7 @@ function CropperView({
|
|
|
36165
37593
|
{
|
|
36166
37594
|
onClick: () => setSelectionCoverage(0.5),
|
|
36167
37595
|
className: "p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
|
|
36168
|
-
disabled:
|
|
37596
|
+
disabled: creatingVariant,
|
|
36169
37597
|
"aria-label": "Selección 50%",
|
|
36170
37598
|
children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "50%" }) })
|
|
36171
37599
|
}
|
|
@@ -36175,7 +37603,7 @@ function CropperView({
|
|
|
36175
37603
|
{
|
|
36176
37604
|
onClick: () => setSelectionCoverage(0.7),
|
|
36177
37605
|
className: "p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
|
|
36178
|
-
disabled:
|
|
37606
|
+
disabled: creatingVariant,
|
|
36179
37607
|
"aria-label": "Selección 70%",
|
|
36180
37608
|
children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "70%" }) })
|
|
36181
37609
|
}
|
|
@@ -36185,7 +37613,7 @@ function CropperView({
|
|
|
36185
37613
|
{
|
|
36186
37614
|
onClick: () => setSelectionCoverage(0.9),
|
|
36187
37615
|
className: "p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
|
|
36188
|
-
disabled:
|
|
37616
|
+
disabled: creatingVariant,
|
|
36189
37617
|
"aria-label": "Selección 90%",
|
|
36190
37618
|
children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "90%" }) })
|
|
36191
37619
|
}
|
|
@@ -36195,7 +37623,7 @@ function CropperView({
|
|
|
36195
37623
|
{
|
|
36196
37624
|
onClick: () => setSelectionCoverage(1),
|
|
36197
37625
|
className: "p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
|
|
36198
|
-
disabled:
|
|
37626
|
+
disabled: creatingVariant,
|
|
36199
37627
|
"aria-label": "Selección completa",
|
|
36200
37628
|
children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "100%" }) })
|
|
36201
37629
|
}
|
|
@@ -36215,7 +37643,7 @@ function CropperView({
|
|
|
36215
37643
|
onClick: () => move(0, -10),
|
|
36216
37644
|
className: "p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
|
|
36217
37645
|
title: "Mover arriba",
|
|
36218
|
-
disabled:
|
|
37646
|
+
disabled: creatingVariant,
|
|
36219
37647
|
"aria-label": "Mover imagen hacia arriba",
|
|
36220
37648
|
children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-up-blue" }) })
|
|
36221
37649
|
}
|
|
@@ -36227,7 +37655,7 @@ function CropperView({
|
|
|
36227
37655
|
onClick: () => move(-10, 0),
|
|
36228
37656
|
className: "p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
|
|
36229
37657
|
title: "Mover izquierda",
|
|
36230
|
-
disabled:
|
|
37658
|
+
disabled: creatingVariant,
|
|
36231
37659
|
"aria-label": "Mover imagen hacia la izquierda",
|
|
36232
37660
|
children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-left-blue" }) })
|
|
36233
37661
|
}
|
|
@@ -36238,7 +37666,7 @@ function CropperView({
|
|
|
36238
37666
|
onClick: centerImage,
|
|
36239
37667
|
className: "p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
|
|
36240
37668
|
title: "Centrar y ajustar imagen",
|
|
36241
|
-
disabled:
|
|
37669
|
+
disabled: creatingVariant,
|
|
36242
37670
|
"aria-label": "Centrar y ajustar imagen",
|
|
36243
37671
|
children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-radio-button-blue icon-md" }) })
|
|
36244
37672
|
}
|
|
@@ -36249,7 +37677,7 @@ function CropperView({
|
|
|
36249
37677
|
onClick: () => move(10, 0),
|
|
36250
37678
|
className: "p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
|
|
36251
37679
|
title: "Mover derecha",
|
|
36252
|
-
disabled:
|
|
37680
|
+
disabled: creatingVariant,
|
|
36253
37681
|
"aria-label": "Mover imagen hacia la derecha",
|
|
36254
37682
|
children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-right-blue" }) })
|
|
36255
37683
|
}
|
|
@@ -36261,7 +37689,7 @@ function CropperView({
|
|
|
36261
37689
|
onClick: () => move(0, 10),
|
|
36262
37690
|
className: "p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
|
|
36263
37691
|
title: "Mover abajo",
|
|
36264
|
-
disabled:
|
|
37692
|
+
disabled: creatingVariant,
|
|
36265
37693
|
"aria-label": "Mover imagen hacia abajo",
|
|
36266
37694
|
children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-down-blue" }) })
|
|
36267
37695
|
}
|
|
@@ -36281,7 +37709,7 @@ function CropperView({
|
|
|
36281
37709
|
onClick: () => zoom(-0.2),
|
|
36282
37710
|
className: "flex-1 p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
|
|
36283
37711
|
title: "Alejar 20%",
|
|
36284
|
-
disabled:
|
|
37712
|
+
disabled: creatingVariant,
|
|
36285
37713
|
"aria-label": "Alejar imagen",
|
|
36286
37714
|
children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-zoom-out-blue" }) })
|
|
36287
37715
|
}
|
|
@@ -36292,7 +37720,7 @@ function CropperView({
|
|
|
36292
37720
|
onClick: resetZoomOnly,
|
|
36293
37721
|
className: "flex-1 p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
|
|
36294
37722
|
title: "Restablecer zoom original",
|
|
36295
|
-
disabled:
|
|
37723
|
+
disabled: creatingVariant,
|
|
36296
37724
|
"aria-label": "Restablecer el zoom para que la imagen se vea con su resolución original",
|
|
36297
37725
|
children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-screenshot-blue" }) })
|
|
36298
37726
|
}
|
|
@@ -36303,7 +37731,7 @@ function CropperView({
|
|
|
36303
37731
|
onClick: () => zoom(0.2),
|
|
36304
37732
|
className: "flex-1 p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
|
|
36305
37733
|
title: "Acercar 20%",
|
|
36306
|
-
disabled:
|
|
37734
|
+
disabled: creatingVariant,
|
|
36307
37735
|
"aria-label": "Acercar imagen",
|
|
36308
37736
|
children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-zoom-in-blue" }) })
|
|
36309
37737
|
}
|
|
@@ -36319,7 +37747,7 @@ function CropperView({
|
|
|
36319
37747
|
onClick: () => rotate(-90),
|
|
36320
37748
|
className: "flex-1 p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
|
|
36321
37749
|
title: "Rotar -90°",
|
|
36322
|
-
disabled:
|
|
37750
|
+
disabled: creatingVariant,
|
|
36323
37751
|
"aria-label": "Rotar imagen 90 grados a la izquierda",
|
|
36324
37752
|
children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-reply-blue" }) })
|
|
36325
37753
|
}
|
|
@@ -36330,7 +37758,7 @@ function CropperView({
|
|
|
36330
37758
|
onClick: () => rotate(-45),
|
|
36331
37759
|
className: "flex-1 p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
|
|
36332
37760
|
title: "Rotar -45°",
|
|
36333
|
-
disabled:
|
|
37761
|
+
disabled: creatingVariant,
|
|
36334
37762
|
"aria-label": "Rotar imagen 45 grados a la izquierda",
|
|
36335
37763
|
children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "-45°" }) })
|
|
36336
37764
|
}
|
|
@@ -36341,7 +37769,7 @@ function CropperView({
|
|
|
36341
37769
|
onClick: () => rotate(45),
|
|
36342
37770
|
className: "flex-1 p-2 rounded cursor-pointer transition-colors text-xs bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
|
|
36343
37771
|
title: "Rotar +45°",
|
|
36344
|
-
disabled:
|
|
37772
|
+
disabled: creatingVariant,
|
|
36345
37773
|
"aria-label": "Rotar imagen 45 grados a la derecha",
|
|
36346
37774
|
children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { children: "+45°" }) })
|
|
36347
37775
|
}
|
|
@@ -36352,7 +37780,7 @@ function CropperView({
|
|
|
36352
37780
|
onClick: () => rotate(90),
|
|
36353
37781
|
className: "flex-1 p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
|
|
36354
37782
|
title: "Rotar +90°",
|
|
36355
|
-
disabled:
|
|
37783
|
+
disabled: creatingVariant,
|
|
36356
37784
|
"aria-label": "Rotar imagen 90 grados a la derecha",
|
|
36357
37785
|
children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-send-arrow-blue" }) })
|
|
36358
37786
|
}
|
|
@@ -36368,7 +37796,7 @@ function CropperView({
|
|
|
36368
37796
|
onClick: flipHorizontal,
|
|
36369
37797
|
className: `flex-1 p-2 rounded cursor-pointer transition-colors text-sm ${flipStates.horizontal ? "bg-blue-100 border border-blue-300 text-blue-800" : "bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200"}`,
|
|
36370
37798
|
title: `Voltear horizontalmente ${flipStates.horizontal ? "(activo)" : ""}`,
|
|
36371
|
-
disabled:
|
|
37799
|
+
disabled: creatingVariant,
|
|
36372
37800
|
"aria-label": "Voltear imagen horizontalmente",
|
|
36373
37801
|
"aria-pressed": flipStates.horizontal,
|
|
36374
37802
|
children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-left-right-blue border border-brand-blue-1000 rounded" }) })
|
|
@@ -36380,7 +37808,7 @@ function CropperView({
|
|
|
36380
37808
|
onClick: flipVertical,
|
|
36381
37809
|
className: `flex-1 p-2 cursor-pointer rounded transition-colors text-sm ${flipStates.vertical ? "bg-blue-100 border border-blue-300 text-blue-800" : "bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200"}`,
|
|
36382
37810
|
title: `Voltear verticalmente ${flipStates.vertical ? "(activo)" : ""}`,
|
|
36383
|
-
disabled:
|
|
37811
|
+
disabled: creatingVariant,
|
|
36384
37812
|
"aria-label": "Voltear imagen verticalmente",
|
|
36385
37813
|
"aria-pressed": flipStates.vertical,
|
|
36386
37814
|
children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "icon icon-arrow-up-down-blue border border-brand-blue-1000 rounded" }) })
|
|
@@ -36397,7 +37825,7 @@ function CropperView({
|
|
|
36397
37825
|
},
|
|
36398
37826
|
className: "w-full p-2 rounded cursor-pointer transition-colors text-sm bg-gray-100 border border-gray-300 text-gray-700 hover:bg-gray-200",
|
|
36399
37827
|
title: "Reiniciar transformaciones",
|
|
36400
|
-
disabled:
|
|
37828
|
+
disabled: creatingVariant,
|
|
36401
37829
|
"aria-label": "Reiniciar todas las transformaciones de la imagen",
|
|
36402
37830
|
children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxs("span", { children: [
|
|
36403
37831
|
/* @__PURE__ */ jsx("span", { className: "icon icon-refresh-blue icon--sm align-[middle!important] " }),
|
|
@@ -36411,6 +37839,187 @@ function CropperView({
|
|
|
36411
37839
|
] })
|
|
36412
37840
|
] });
|
|
36413
37841
|
}
|
|
37842
|
+
function Pagination({
|
|
37843
|
+
currentPage,
|
|
37844
|
+
totalPages,
|
|
37845
|
+
onPageChange,
|
|
37846
|
+
disabled = false
|
|
37847
|
+
}) {
|
|
37848
|
+
if (!totalPages || totalPages <= 1) {
|
|
37849
|
+
return null;
|
|
37850
|
+
}
|
|
37851
|
+
const handlePrevious = () => {
|
|
37852
|
+
if (currentPage > 1) {
|
|
37853
|
+
onPageChange(currentPage - 1);
|
|
37854
|
+
}
|
|
37855
|
+
};
|
|
37856
|
+
const handleNext = () => {
|
|
37857
|
+
if (currentPage < totalPages) {
|
|
37858
|
+
onPageChange(currentPage + 1);
|
|
37859
|
+
}
|
|
37860
|
+
};
|
|
37861
|
+
const handlePageClick = (page) => {
|
|
37862
|
+
if (page !== currentPage) {
|
|
37863
|
+
onPageChange(page);
|
|
37864
|
+
}
|
|
37865
|
+
};
|
|
37866
|
+
const getVisiblePages = () => {
|
|
37867
|
+
const delta = 2;
|
|
37868
|
+
const range = [];
|
|
37869
|
+
const rangeWithDots = [];
|
|
37870
|
+
const start = Math.max(1, currentPage - delta);
|
|
37871
|
+
const end = Math.min(totalPages, currentPage + delta);
|
|
37872
|
+
for (let i = start; i <= end; i++) {
|
|
37873
|
+
range.push(i);
|
|
37874
|
+
}
|
|
37875
|
+
if (start > 1) {
|
|
37876
|
+
rangeWithDots.push(1);
|
|
37877
|
+
if (start > 2) {
|
|
37878
|
+
rangeWithDots.push("...");
|
|
37879
|
+
}
|
|
37880
|
+
}
|
|
37881
|
+
rangeWithDots.push(...range);
|
|
37882
|
+
if (end < totalPages) {
|
|
37883
|
+
if (end < totalPages - 1) {
|
|
37884
|
+
rangeWithDots.push("...");
|
|
37885
|
+
}
|
|
37886
|
+
rangeWithDots.push(totalPages);
|
|
37887
|
+
}
|
|
37888
|
+
return rangeWithDots;
|
|
37889
|
+
};
|
|
37890
|
+
const visiblePages = getVisiblePages();
|
|
37891
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center space-x-2 py-4", children: [
|
|
37892
|
+
/* @__PURE__ */ jsxs(
|
|
37893
|
+
"button",
|
|
37894
|
+
{
|
|
37895
|
+
onClick: handlePrevious,
|
|
37896
|
+
disabled: disabled || currentPage <= 1,
|
|
37897
|
+
className: `
|
|
37898
|
+
px-3 py-2 text-sm font-medium rounded-md transition-colors disabled:cursor-default cursor-pointer
|
|
37899
|
+
${disabled || currentPage <= 1 ? "text-gray-400 bg-gray-100 cursor-not-allowed" : "text-gray-700 bg-white border border-gray-300 hover:bg-gray-50 focus:ring-2 focus:ring-blue-500"}
|
|
37900
|
+
`,
|
|
37901
|
+
children: [
|
|
37902
|
+
/* @__PURE__ */ jsx("span", { className: "icon icon-arrow-left-white icon--sm" }),
|
|
37903
|
+
" Anterior"
|
|
37904
|
+
]
|
|
37905
|
+
}
|
|
37906
|
+
),
|
|
37907
|
+
/* @__PURE__ */ jsx("div", { className: "flex space-x-1", children: visiblePages.map((page, index) => /* @__PURE__ */ jsx(React.Fragment, { children: page === "..." ? /* @__PURE__ */ jsx("span", { className: "px-3 py-2 text-sm text-gray-500", children: "..." }) : /* @__PURE__ */ jsx(
|
|
37908
|
+
"button",
|
|
37909
|
+
{
|
|
37910
|
+
onClick: () => handlePageClick(page),
|
|
37911
|
+
disabled,
|
|
37912
|
+
className: `
|
|
37913
|
+
px-3 py-2 text-sm font-medium rounded-md transition-colors disabled:cursor-default cursor-pointer
|
|
37914
|
+
${page === currentPage ? "bg-blue-600 text-white border border-blue-600" : disabled ? "text-gray-400 bg-gray-100 cursor-not-allowed" : "text-gray-700 bg-white border border-gray-300 hover:bg-gray-50 focus:ring-2 focus:ring-blue-500"}
|
|
37915
|
+
`,
|
|
37916
|
+
children: page
|
|
37917
|
+
}
|
|
37918
|
+
) }, `page-${page}-${index}`)) }),
|
|
37919
|
+
/* @__PURE__ */ jsxs(
|
|
37920
|
+
"button",
|
|
37921
|
+
{
|
|
37922
|
+
onClick: handleNext,
|
|
37923
|
+
disabled: disabled || currentPage >= totalPages,
|
|
37924
|
+
className: `
|
|
37925
|
+
px-3 py-2 text-sm font-medium rounded-md transition-colors disabled:cursor-default cursor-pointer
|
|
37926
|
+
${disabled || currentPage >= totalPages ? "text-gray-400 bg-gray-100 cursor-not-allowed" : "text-gray-700 bg-white border border-gray-300 hover:bg-gray-50 focus:ring-2 focus:ring-blue-500"}
|
|
37927
|
+
`,
|
|
37928
|
+
children: [
|
|
37929
|
+
"Siguiente ",
|
|
37930
|
+
/* @__PURE__ */ jsx("span", { className: "icon icon-arrow-right-white icon--sm" })
|
|
37931
|
+
]
|
|
37932
|
+
}
|
|
37933
|
+
),
|
|
37934
|
+
/* @__PURE__ */ jsxs("div", { className: "text-sm text-gray-500 ml-4", children: [
|
|
37935
|
+
"Página ",
|
|
37936
|
+
currentPage,
|
|
37937
|
+
" de ",
|
|
37938
|
+
totalPages
|
|
37939
|
+
] })
|
|
37940
|
+
] });
|
|
37941
|
+
}
|
|
37942
|
+
function TokenExpiredModal({ isOpen, onClose }) {
|
|
37943
|
+
if (!isOpen) return null;
|
|
37944
|
+
return /* @__PURE__ */ jsx(
|
|
37945
|
+
"div",
|
|
37946
|
+
{
|
|
37947
|
+
className: "fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50",
|
|
37948
|
+
style: { zIndex: 9999 },
|
|
37949
|
+
children: /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-lg p-6 max-w-md mx-4 shadow-xl", children: [
|
|
37950
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center mb-4", children: [
|
|
37951
|
+
/* @__PURE__ */ jsx(
|
|
37952
|
+
"svg",
|
|
37953
|
+
{
|
|
37954
|
+
className: "w-6 h-6 text-red-600 mr-3",
|
|
37955
|
+
fill: "none",
|
|
37956
|
+
stroke: "currentColor",
|
|
37957
|
+
viewBox: "0 0 24 24",
|
|
37958
|
+
children: /* @__PURE__ */ jsx(
|
|
37959
|
+
"path",
|
|
37960
|
+
{
|
|
37961
|
+
strokeLinecap: "round",
|
|
37962
|
+
strokeLinejoin: "round",
|
|
37963
|
+
strokeWidth: 2,
|
|
37964
|
+
d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
|
|
37965
|
+
}
|
|
37966
|
+
)
|
|
37967
|
+
}
|
|
37968
|
+
),
|
|
37969
|
+
/* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-gray-900", children: "Sesión Expirada" })
|
|
37970
|
+
] }),
|
|
37971
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
|
|
37972
|
+
/* @__PURE__ */ jsx("p", { className: "text-gray-600 mb-2", children: "Su sesión ha expirado por seguridad." }),
|
|
37973
|
+
/* @__PURE__ */ jsx("p", { className: "text-gray-600", children: "Es necesario recargar la página para obtener un nuevo token de acceso." })
|
|
37974
|
+
] }),
|
|
37975
|
+
/* @__PURE__ */ jsx("div", { className: "flex justify-end space-x-3", children: /* @__PURE__ */ jsx(
|
|
37976
|
+
"button",
|
|
37977
|
+
{
|
|
37978
|
+
onClick: onClose,
|
|
37979
|
+
className: "px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors",
|
|
37980
|
+
children: "Recargar Página"
|
|
37981
|
+
}
|
|
37982
|
+
) })
|
|
37983
|
+
] })
|
|
37984
|
+
}
|
|
37985
|
+
);
|
|
37986
|
+
}
|
|
37987
|
+
function useUploadImage() {
|
|
37988
|
+
const [loading, setLoading] = useState(false);
|
|
37989
|
+
const [error, setError] = useState(null);
|
|
37990
|
+
const [uploadedImage, setUploadedImage] = useState(null);
|
|
37991
|
+
const upload = async (file, uploadedBy = null) => {
|
|
37992
|
+
if (!file) {
|
|
37993
|
+
setError("No se ha proporcionado ningún archivo");
|
|
37994
|
+
return null;
|
|
37995
|
+
}
|
|
37996
|
+
setLoading(true);
|
|
37997
|
+
setError(null);
|
|
37998
|
+
setUploadedImage(null);
|
|
37999
|
+
try {
|
|
38000
|
+
const data = await uploadAsset(file, uploadedBy, false);
|
|
38001
|
+
const result = data.result || data;
|
|
38002
|
+
setUploadedImage(result);
|
|
38003
|
+
return result;
|
|
38004
|
+
} catch (err) {
|
|
38005
|
+
setError(err.message);
|
|
38006
|
+
return null;
|
|
38007
|
+
} finally {
|
|
38008
|
+
setLoading(false);
|
|
38009
|
+
}
|
|
38010
|
+
};
|
|
38011
|
+
const reset = () => {
|
|
38012
|
+
setError(null);
|
|
38013
|
+
setUploadedImage(null);
|
|
38014
|
+
};
|
|
38015
|
+
return {
|
|
38016
|
+
upload,
|
|
38017
|
+
loading,
|
|
38018
|
+
error,
|
|
38019
|
+
uploadedImage,
|
|
38020
|
+
reset
|
|
38021
|
+
};
|
|
38022
|
+
}
|
|
36414
38023
|
function useDeleteImage() {
|
|
36415
38024
|
const [loading, setLoading] = useState(false);
|
|
36416
38025
|
const [error, setError] = useState(null);
|
|
@@ -36447,29 +38056,48 @@ function useImages(apiKey, prod = false, params = {}) {
|
|
|
36447
38056
|
const [images, setImages] = useState([]);
|
|
36448
38057
|
const [loading, setLoading] = useState(true);
|
|
36449
38058
|
const [error, setError] = useState(null);
|
|
38059
|
+
const [pagination, setPagination] = useState(null);
|
|
38060
|
+
const [retryCount, setRetryCount] = useState(0);
|
|
38061
|
+
const MAX_RETRIES = 3;
|
|
38062
|
+
const paramsKey = useMemo(() => JSON.stringify(params), [params]);
|
|
36450
38063
|
useEffect(() => {
|
|
36451
|
-
const paramsKey = JSON.stringify(params);
|
|
36452
38064
|
const cacheKey = `${paramsKey}`;
|
|
36453
38065
|
const cached = cache.get(cacheKey);
|
|
36454
38066
|
const now = Date.now();
|
|
36455
38067
|
if (cached && now - cached.timestamp < CACHE_TTL) {
|
|
36456
38068
|
setImages(cached.data);
|
|
38069
|
+
setPagination(cached.pagination || null);
|
|
36457
38070
|
setLoading(false);
|
|
36458
38071
|
return;
|
|
36459
38072
|
}
|
|
36460
38073
|
let isMounted = true;
|
|
36461
38074
|
const fetchImages = async () => {
|
|
38075
|
+
if (retryCount >= MAX_RETRIES) {
|
|
38076
|
+
if (isMounted) {
|
|
38077
|
+
setError(`Error de autenticación: Verifica tus credenciales API`);
|
|
38078
|
+
setLoading(false);
|
|
38079
|
+
}
|
|
38080
|
+
return;
|
|
38081
|
+
}
|
|
36462
38082
|
try {
|
|
36463
38083
|
const data = await listAssets(params);
|
|
36464
38084
|
if (!isMounted) return;
|
|
36465
38085
|
const result = data.result || [];
|
|
38086
|
+
const paginationData = data.pagination || null;
|
|
36466
38087
|
setImages(result);
|
|
38088
|
+
setPagination(paginationData);
|
|
38089
|
+
setError(null);
|
|
38090
|
+
setRetryCount(0);
|
|
36467
38091
|
cache.set(cacheKey, {
|
|
36468
38092
|
data: result,
|
|
38093
|
+
pagination: paginationData,
|
|
36469
38094
|
timestamp: now
|
|
36470
38095
|
});
|
|
36471
38096
|
} catch (err) {
|
|
36472
|
-
if (isMounted)
|
|
38097
|
+
if (isMounted) {
|
|
38098
|
+
setError(err.message);
|
|
38099
|
+
setRetryCount((prev) => prev + 1);
|
|
38100
|
+
}
|
|
36473
38101
|
} finally {
|
|
36474
38102
|
if (isMounted) setLoading(false);
|
|
36475
38103
|
}
|
|
@@ -36478,12 +38106,45 @@ function useImages(apiKey, prod = false, params = {}) {
|
|
|
36478
38106
|
return () => {
|
|
36479
38107
|
isMounted = false;
|
|
36480
38108
|
};
|
|
36481
|
-
}, [apiKey, prod, params]);
|
|
38109
|
+
}, [apiKey, prod, paramsKey, retryCount, params]);
|
|
36482
38110
|
const invalidateCache = () => {
|
|
36483
|
-
const paramsKey = JSON.stringify(params);
|
|
36484
38111
|
cache.delete(`${paramsKey}`);
|
|
36485
38112
|
};
|
|
36486
|
-
return { images, loading, error, setImages, invalidateCache };
|
|
38113
|
+
return { images, loading, error, pagination, setImages, invalidateCache };
|
|
38114
|
+
}
|
|
38115
|
+
function useTokenExpiration() {
|
|
38116
|
+
const [isTokenExpired, setIsTokenExpired] = useState(false);
|
|
38117
|
+
const checkTokenExpiration = useCallback((error) => {
|
|
38118
|
+
if (error?.response?.status === 401) {
|
|
38119
|
+
const errorData = error.response.data;
|
|
38120
|
+
if (errorData?.error === "token_expired" || errorData?.message?.includes("expired") || errorData?.message?.includes("caducado") || errorData?.error?.includes("expired")) {
|
|
38121
|
+
setIsTokenExpired(true);
|
|
38122
|
+
return true;
|
|
38123
|
+
}
|
|
38124
|
+
}
|
|
38125
|
+
return false;
|
|
38126
|
+
}, []);
|
|
38127
|
+
const handleTokenExpiredClose = useCallback(() => {
|
|
38128
|
+
setIsTokenExpired(false);
|
|
38129
|
+
alert("El token ha expirado. La página se recargará para obtener un nuevo token.");
|
|
38130
|
+
window.location.reload();
|
|
38131
|
+
}, []);
|
|
38132
|
+
useEffect(() => {
|
|
38133
|
+
const handleGlobalError = (event) => {
|
|
38134
|
+
if (event.detail && event.detail.error) {
|
|
38135
|
+
checkTokenExpiration(event.detail.error);
|
|
38136
|
+
}
|
|
38137
|
+
};
|
|
38138
|
+
window.addEventListener("tokenExpiredError", handleGlobalError);
|
|
38139
|
+
return () => {
|
|
38140
|
+
window.removeEventListener("tokenExpiredError", handleGlobalError);
|
|
38141
|
+
};
|
|
38142
|
+
}, [checkTokenExpiration]);
|
|
38143
|
+
return {
|
|
38144
|
+
isTokenExpired,
|
|
38145
|
+
checkTokenExpiration,
|
|
38146
|
+
handleTokenExpiredClose
|
|
38147
|
+
};
|
|
36487
38148
|
}
|
|
36488
38149
|
function App({
|
|
36489
38150
|
apiKey,
|
|
@@ -36494,7 +38155,7 @@ function App({
|
|
|
36494
38155
|
modeUI = "full",
|
|
36495
38156
|
// full | gallery-only | upload-only | crop-only | ia-only
|
|
36496
38157
|
ui = {
|
|
36497
|
-
showActions: ["select", "download", "copy", "delete", "crop"],
|
|
38158
|
+
showActions: ["select", "download", "copy", "delete", "crop", "variants"],
|
|
36498
38159
|
hideActions: [],
|
|
36499
38160
|
theme: "light",
|
|
36500
38161
|
language: "es",
|
|
@@ -36502,7 +38163,9 @@ function App({
|
|
|
36502
38163
|
showTabs: true
|
|
36503
38164
|
},
|
|
36504
38165
|
callbacks = {},
|
|
36505
|
-
instanceId = null
|
|
38166
|
+
instanceId = null,
|
|
38167
|
+
itemsPerPage = 10
|
|
38168
|
+
// Número de elementos por página
|
|
36506
38169
|
}) {
|
|
36507
38170
|
const getFilteredFeatures = () => {
|
|
36508
38171
|
switch (modeUI) {
|
|
@@ -36529,6 +38192,14 @@ function App({
|
|
|
36529
38192
|
};
|
|
36530
38193
|
const [activeTab, setActiveTab] = useState(getInitialTab());
|
|
36531
38194
|
const [selectedImage, setSelectedImage] = useState(null);
|
|
38195
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
38196
|
+
const {
|
|
38197
|
+
isTokenExpired,
|
|
38198
|
+
handleTokenExpiredClose
|
|
38199
|
+
} = useTokenExpiration();
|
|
38200
|
+
const handlePageChange = (newPage) => {
|
|
38201
|
+
setCurrentPage(newPage);
|
|
38202
|
+
};
|
|
36532
38203
|
const {
|
|
36533
38204
|
upload,
|
|
36534
38205
|
loading: uploading,
|
|
@@ -36546,9 +38217,14 @@ function App({
|
|
|
36546
38217
|
images,
|
|
36547
38218
|
loading: loadingImages,
|
|
36548
38219
|
error: imagesError,
|
|
38220
|
+
pagination,
|
|
36549
38221
|
invalidateCache,
|
|
36550
38222
|
setImages
|
|
36551
|
-
} = useImages(apiKey, prod);
|
|
38223
|
+
} = useImages(apiKey, prod, { limit: itemsPerPage, page: currentPage });
|
|
38224
|
+
const { refreshVariants } = useImageVariants();
|
|
38225
|
+
const handleVariantCreated = (assetId, variantData) => {
|
|
38226
|
+
refreshVariants(assetId);
|
|
38227
|
+
};
|
|
36552
38228
|
const isActionAllowed = (action) => {
|
|
36553
38229
|
if (ui.hideActions?.includes(action)) return false;
|
|
36554
38230
|
if (ui.showActions?.includes(action)) return true;
|
|
@@ -36569,6 +38245,7 @@ function App({
|
|
|
36569
38245
|
const result = await upload(file);
|
|
36570
38246
|
if (result) {
|
|
36571
38247
|
invalidateCache();
|
|
38248
|
+
setCurrentPage(1);
|
|
36572
38249
|
setImages((prev) => [result, ...prev]);
|
|
36573
38250
|
setActiveTab("gallery");
|
|
36574
38251
|
if (callbacks.onUpload) {
|
|
@@ -36591,6 +38268,7 @@ function App({
|
|
|
36591
38268
|
const success = await deleteImg(imageId);
|
|
36592
38269
|
if (success) {
|
|
36593
38270
|
invalidateCache();
|
|
38271
|
+
setCurrentPage(1);
|
|
36594
38272
|
setImages((prev) => prev.filter((img) => img.id !== imageId));
|
|
36595
38273
|
if (selectedImage && selectedImage.id === imageId) {
|
|
36596
38274
|
setSelectedImage(null);
|
|
@@ -36654,6 +38332,15 @@ function App({
|
|
|
36654
38332
|
crop: isActionAllowed("crop")
|
|
36655
38333
|
}
|
|
36656
38334
|
}
|
|
38335
|
+
),
|
|
38336
|
+
pagination && !loadingImages && /* @__PURE__ */ jsx(
|
|
38337
|
+
Pagination,
|
|
38338
|
+
{
|
|
38339
|
+
currentPage: pagination.page || currentPage,
|
|
38340
|
+
totalPages: pagination.pages || 1,
|
|
38341
|
+
onPageChange: handlePageChange,
|
|
38342
|
+
disabled: loadingImages || deleting
|
|
38343
|
+
}
|
|
36657
38344
|
)
|
|
36658
38345
|
] }),
|
|
36659
38346
|
activeTab === "upload" && /* @__PURE__ */ jsxs("div", { children: [
|
|
@@ -36680,11 +38367,8 @@ function App({
|
|
|
36680
38367
|
CropperView,
|
|
36681
38368
|
{
|
|
36682
38369
|
image: selectedImage,
|
|
36683
|
-
|
|
36684
|
-
prod,
|
|
36685
|
-
onSave: (newImage) => {
|
|
38370
|
+
onSave: () => {
|
|
36686
38371
|
invalidateCache();
|
|
36687
|
-
setImages((prev) => [newImage, ...prev]);
|
|
36688
38372
|
setSelectedImage(null);
|
|
36689
38373
|
setActiveTab("gallery");
|
|
36690
38374
|
},
|
|
@@ -36693,7 +38377,15 @@ function App({
|
|
|
36693
38377
|
setActiveTab("gallery");
|
|
36694
38378
|
},
|
|
36695
38379
|
onDelete: () => handleDelete(selectedImage?.id),
|
|
36696
|
-
deleting
|
|
38380
|
+
deleting,
|
|
38381
|
+
onVariantCreated: handleVariantCreated
|
|
38382
|
+
}
|
|
38383
|
+
),
|
|
38384
|
+
/* @__PURE__ */ jsx(
|
|
38385
|
+
TokenExpiredModal,
|
|
38386
|
+
{
|
|
38387
|
+
isOpen: isTokenExpired,
|
|
38388
|
+
onClose: handleTokenExpiredClose
|
|
36697
38389
|
}
|
|
36698
38390
|
)
|
|
36699
38391
|
] });
|
|
@@ -37104,8 +38796,16 @@ class LimboInstance {
|
|
|
37104
38796
|
* Validar configuración
|
|
37105
38797
|
*/
|
|
37106
38798
|
_validateConfig() {
|
|
37107
|
-
|
|
37108
|
-
|
|
38799
|
+
const hasAuth = this.config.auth?.apiKey || this.config.auth?.publicKey || this.config.apiKey || this.config.publicKey;
|
|
38800
|
+
if (!hasAuth) {
|
|
38801
|
+
throw new Error(`LimboInstance ${this.id}: Authentication is required. Provide either auth.publicKey (recommended) or auth.apiKey (for server-side only)`);
|
|
38802
|
+
}
|
|
38803
|
+
if ((this.config.auth?.apiKey || this.config.apiKey) && typeof window !== "undefined") {
|
|
38804
|
+
console.warn(
|
|
38805
|
+
`⚠️ SECURITY WARNING: API Key detected in client-side code.
|
|
38806
|
+
This is a security risk! Use publicKey instead for client applications.
|
|
38807
|
+
API Keys should only be used in server-side environments.`
|
|
38808
|
+
);
|
|
37109
38809
|
}
|
|
37110
38810
|
if (!["embed", "modal", "button"].includes(this.config.mode)) {
|
|
37111
38811
|
throw new Error(`LimboInstance ${this.id}: invalid mode ${this.config.mode}`);
|
|
@@ -39819,15 +41519,25 @@ class LimboCore {
|
|
|
39819
41519
|
*/
|
|
39820
41520
|
configure(options) {
|
|
39821
41521
|
this.config.setGlobal(options);
|
|
39822
|
-
|
|
39823
|
-
|
|
39824
|
-
|
|
39825
|
-
|
|
39826
|
-
|
|
39827
|
-
|
|
39828
|
-
|
|
41522
|
+
configureApiClient({
|
|
41523
|
+
publicKey: options.publicKey,
|
|
41524
|
+
apiKey: options.apiKey,
|
|
41525
|
+
// Solo para authMode: "dev"
|
|
41526
|
+
token: options.token,
|
|
41527
|
+
// Solo para authMode: "manual"
|
|
41528
|
+
authMode: options.authMode,
|
|
41529
|
+
// "session" | "dev" | "manual"
|
|
41530
|
+
prod: options.prod || false
|
|
41531
|
+
});
|
|
39829
41532
|
return this.config;
|
|
39830
41533
|
}
|
|
41534
|
+
/**
|
|
41535
|
+
* Establecer token JWT (generado por backend)
|
|
41536
|
+
*/
|
|
41537
|
+
setToken(token) {
|
|
41538
|
+
setToken(token);
|
|
41539
|
+
return this;
|
|
41540
|
+
}
|
|
39831
41541
|
/**
|
|
39832
41542
|
* Crear instancia manual
|
|
39833
41543
|
*/
|
|
@@ -39990,18 +41700,21 @@ const Limbo = new LimboCore();
|
|
|
39990
41700
|
if (typeof window !== "undefined") {
|
|
39991
41701
|
window.Limbo = Limbo;
|
|
39992
41702
|
}
|
|
39993
|
-
const PUBLIC_KEY = "
|
|
41703
|
+
const PUBLIC_KEY = "pk_9fcfdd91a14755cc68d0e11a14269554";
|
|
39994
41704
|
if (typeof window !== "undefined" && document.querySelector("#root")) {
|
|
39995
41705
|
Limbo.configure({
|
|
39996
|
-
|
|
39997
|
-
|
|
41706
|
+
prod: false,
|
|
41707
|
+
publicKey: PUBLIC_KEY,
|
|
41708
|
+
authMode: "session",
|
|
41709
|
+
tokenEndpoint: "/auth/token"
|
|
41710
|
+
// Endpoint que acepta solo public_key
|
|
39998
41711
|
});
|
|
39999
41712
|
Limbo.create({
|
|
40000
41713
|
container: "#root",
|
|
40001
41714
|
mode: "embed",
|
|
40002
41715
|
modeUI: "full",
|
|
40003
41716
|
features: ["gallery", "upload", "cropper"],
|
|
40004
|
-
title: "Limbo Image Manager -
|
|
41717
|
+
title: "Limbo Image Manager - Development",
|
|
40005
41718
|
url: true
|
|
40006
41719
|
});
|
|
40007
41720
|
Limbo.configureAutoInputs({
|