@interware/content-generator 0.7.1 → 0.8.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/dist/_chunks/{App-h4_txTsI.mjs → App-DD1MzaWm.mjs} +2 -2
- package/dist/_chunks/{App-Cwr5ra5m.js → App-DspaMfyW.js} +2 -2
- package/dist/_chunks/{ImageStudioTabs-B-mOwDfY.mjs → ImageStudioTabs-DwaLxRjl.mjs} +367 -61
- package/dist/_chunks/{ImageStudioTabs-CG4KTn1S.js → ImageStudioTabs-uXSBkFeu.js} +365 -59
- package/dist/_chunks/{Settings-BT-EqvXs.mjs → Settings-C6TbUrgU.mjs} +170 -2
- package/dist/_chunks/{Settings-D4NGBTk1.js → Settings-ps0o7-fC.js} +169 -1
- package/dist/_chunks/{StudioApp-xaEy-uIk.mjs → StudioApp-B5I1OfGw.mjs} +2 -2
- package/dist/_chunks/{StudioApp-_ViSAwCm.js → StudioApp-B7kGpqqo.js} +2 -2
- package/dist/_chunks/{index-NA9OuMGs.js → index-BudahdfX.js} +3 -3
- package/dist/_chunks/{index-D67xWUPz.mjs → index-zenHkX-p.mjs} +3 -3
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/admin/src/components/settings/DriveSettings.d.ts +77 -0
- package/dist/admin/src/features/drive/DriveTab.d.ts +42 -0
- package/dist/admin/src/features/image-studio/ImageStudioTabs.d.ts +3 -0
- package/dist/admin/src/features/library/FileCard.d.ts +5 -2
- package/dist/admin/src/features/library/driveSync.d.ts +41 -0
- package/dist/admin/src/features/templates/editor/ColorField.d.ts +4 -2
- package/dist/admin/src/features/templates/export/templateOrigin.d.ts +10 -0
- package/dist/admin/src/features/templates/model/palette.d.ts +2 -2
- package/dist/admin/src/lib/imageLimits.d.ts +9 -0
- package/dist/admin/src/lib/use-cases/drive/index.d.ts +3 -0
- package/dist/server/index.js +1414 -51
- package/dist/server/index.mjs +1412 -49
- package/dist/server/src/bootstrap.d.ts +2 -0
- package/dist/server/src/content-types/drive-link/index.d.ts +62 -0
- package/dist/server/src/content-types/drive-link/schema.d.ts +68 -0
- package/dist/server/src/content-types/drive-op/index.d.ts +46 -0
- package/dist/server/src/content-types/drive-op/schema.d.ts +49 -0
- package/dist/server/src/content-types/index.d.ts +106 -0
- package/dist/server/src/controllers/drive.d.ts +30 -0
- package/dist/server/src/controllers/index.d.ts +130 -0
- package/dist/server/src/drive/client.d.ts +32 -0
- package/dist/server/src/drive/clientConfig.d.ts +27 -0
- package/dist/server/src/drive/common.d.ts +6 -0
- package/dist/server/src/drive/fakes.d.ts +104 -0
- package/dist/server/src/drive/files.d.ts +136 -0
- package/dist/server/src/drive/lifecycles.d.ts +7 -0
- package/dist/server/src/drive/links.d.ts +23 -0
- package/dist/server/src/drive/model.d.ts +99 -0
- package/dist/server/src/drive/oauth.d.ts +47 -0
- package/dist/server/src/drive/pull.d.ts +23 -0
- package/dist/server/src/drive/strapiPorts.d.ts +10 -0
- package/dist/server/src/drive/sync.d.ts +40 -0
- package/dist/server/src/index.d.ts +294 -0
- package/dist/server/src/services/drive.d.ts +141 -0
- package/dist/server/src/services/index.d.ts +55 -0
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const jsxRuntime = require("react/jsx-runtime");
|
|
3
3
|
const designSystem = require("@strapi/design-system");
|
|
4
4
|
const reactRouterDom = require("react-router-dom");
|
|
5
|
-
const index = require("./index-
|
|
5
|
+
const index = require("./index-BudahdfX.js");
|
|
6
6
|
const react = require("react");
|
|
7
7
|
const icons = require("@strapi/icons");
|
|
8
8
|
const styledComponents = require("styled-components");
|
|
@@ -740,6 +740,9 @@ const RemoveBackgroundButton = ({ src, onDone }) => {
|
|
|
740
740
|
)
|
|
741
741
|
] });
|
|
742
742
|
};
|
|
743
|
+
const IMAGE_MAX_MB = 30;
|
|
744
|
+
const IMAGE_MAX_BYTES = IMAGE_MAX_MB * 1024 * 1024;
|
|
745
|
+
const IMAGE_FORMATS_HINT = `PNG, JPG o WEBP de hasta ${IMAGE_MAX_MB} MB.`;
|
|
743
746
|
const NameDialog = ({ open, title, label, initial = "", confirmLabel, validate, onClose, onConfirm }) => {
|
|
744
747
|
const [name, setName] = react.useState(initial);
|
|
745
748
|
const [error, setError] = react.useState(null);
|
|
@@ -1171,7 +1174,7 @@ const closestRatio = (width, height, ratios) => {
|
|
|
1171
1174
|
};
|
|
1172
1175
|
return ratios.reduce((best, r) => Math.abs(value(r) - target) < Math.abs(value(best) - target) ? r : best);
|
|
1173
1176
|
};
|
|
1174
|
-
const BASE = "/strapi-content-generator-gemini/image-studio";
|
|
1177
|
+
const BASE$2 = "/strapi-content-generator-gemini/image-studio";
|
|
1175
1178
|
const toResult = ({ image, mimeType, model, grounding }) => ({
|
|
1176
1179
|
src: `data:${mimeType};base64,${image}`,
|
|
1177
1180
|
model,
|
|
@@ -1183,7 +1186,7 @@ const errorMessage = (err, fallback) => {
|
|
|
1183
1186
|
};
|
|
1184
1187
|
const createGeneratorApi = (client) => ({
|
|
1185
1188
|
async models() {
|
|
1186
|
-
const { data } = await client.get(`${BASE}/models`);
|
|
1189
|
+
const { data } = await client.get(`${BASE$2}/models`);
|
|
1187
1190
|
return data;
|
|
1188
1191
|
},
|
|
1189
1192
|
async generate(p) {
|
|
@@ -1196,7 +1199,7 @@ const createGeneratorApi = (client) => ({
|
|
|
1196
1199
|
form.append("allowPeople", String(p.allowPeople));
|
|
1197
1200
|
form.append("grounding", String(p.grounding));
|
|
1198
1201
|
p.references.forEach((f) => form.append("references", f, f.name));
|
|
1199
|
-
const { data } = await client.post(`${BASE}/generate`, form);
|
|
1202
|
+
const { data } = await client.post(`${BASE$2}/generate`, form);
|
|
1200
1203
|
return toResult(data);
|
|
1201
1204
|
},
|
|
1202
1205
|
async edit(p) {
|
|
@@ -1209,11 +1212,11 @@ const createGeneratorApi = (client) => ({
|
|
|
1209
1212
|
p.references.forEach((f) => form.append("references", f, f.name));
|
|
1210
1213
|
if (p.aspectRatio) form.append("aspectRatio", p.aspectRatio);
|
|
1211
1214
|
form.append("image", new File([blob], `base.${blob.type.split("/")[1] ?? "png"}`, { type: blob.type }));
|
|
1212
|
-
const { data } = await client.post(`${BASE}/edit`, form);
|
|
1215
|
+
const { data } = await client.post(`${BASE$2}/edit`, form);
|
|
1213
1216
|
return toResult(data);
|
|
1214
1217
|
},
|
|
1215
1218
|
async expand(prompt) {
|
|
1216
|
-
const { data } = await client.post(`${BASE}/expand`, { prompt });
|
|
1219
|
+
const { data } = await client.post(`${BASE$2}/expand`, { prompt });
|
|
1217
1220
|
return data.prompt;
|
|
1218
1221
|
}
|
|
1219
1222
|
});
|
|
@@ -1253,7 +1256,10 @@ const EditEmpty = ({ disabled, onUpload, onOpenLibrary, onOpenRecent }) => {
|
|
|
1253
1256
|
style: { width: "100%", maxWidth: 600, border: `2px dashed ${over ? c.primary600 : c.neutral200}` },
|
|
1254
1257
|
children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "center", gap: 3, style: { textAlign: "center" }, children: [
|
|
1255
1258
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "beta", tag: "h2", children: "Elige la imagen que quieres editar" }),
|
|
1256
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1259
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { textColor: "neutral600", children: [
|
|
1260
|
+
"Arrástrala aquí, súbela o elígela de la biblioteca. ",
|
|
1261
|
+
IMAGE_FORMATS_HINT
|
|
1262
|
+
] }),
|
|
1257
1263
|
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 3, paddingTop: 2, children: [
|
|
1258
1264
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Upload, {}), disabled, onClick: () => input.current?.click(), children: "Subir imagen" }),
|
|
1259
1265
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Folder, {}), disabled, onClick: onOpenLibrary, children: "Elegir de la biblioteca" })
|
|
@@ -1328,7 +1334,7 @@ const ReferencePicker = ({
|
|
|
1328
1334
|
onChange,
|
|
1329
1335
|
onPickFromLibrary,
|
|
1330
1336
|
label = "Imágenes de referencia",
|
|
1331
|
-
hint =
|
|
1337
|
+
hint = IMAGE_FORMATS_HINT,
|
|
1332
1338
|
noun = REFERENCES
|
|
1333
1339
|
}) => {
|
|
1334
1340
|
const input = react.useRef(null);
|
|
@@ -1522,8 +1528,8 @@ const GeneratorTab = () => {
|
|
|
1522
1528
|
};
|
|
1523
1529
|
const uploadBase = (file) => {
|
|
1524
1530
|
if (!file) return;
|
|
1525
|
-
if (!IMAGE_MIMES.includes(file.type) || file.size >
|
|
1526
|
-
notify("warning",
|
|
1531
|
+
if (!IMAGE_MIMES.includes(file.type) || file.size > IMAGE_MAX_BYTES) {
|
|
1532
|
+
notify("warning", `Usa una imagen ${IMAGE_FORMATS_HINT}`);
|
|
1527
1533
|
return;
|
|
1528
1534
|
}
|
|
1529
1535
|
const reader = new FileReader();
|
|
@@ -1598,7 +1604,7 @@ const GeneratorTab = () => {
|
|
|
1598
1604
|
ReferencePicker,
|
|
1599
1605
|
{
|
|
1600
1606
|
label: "Referencias de estilo",
|
|
1601
|
-
hint:
|
|
1607
|
+
hint: `Guían el estilo, no se copian. ${IMAGE_FORMATS_HINT}`,
|
|
1602
1608
|
files: references,
|
|
1603
1609
|
max: model?.maxReferences ?? 0,
|
|
1604
1610
|
modelLabel: model?.label ?? "Este modelo",
|
|
@@ -1832,7 +1838,7 @@ const isImage = (f) => (f.mime ?? "").startsWith("image/");
|
|
|
1832
1838
|
const canRemoveBackground = (f) => isImage(f) && f.mime !== "image/svg+xml";
|
|
1833
1839
|
const isGenerated = (f) => (f.caption ?? "").startsWith("AI generated content");
|
|
1834
1840
|
const formatSize = (kb = 0) => kb >= 1024 ? `${(kb / 1024).toFixed(1)} MB` : `${Math.round(kb)} KB`;
|
|
1835
|
-
const FileCard = ({ file, onAction, busy }) => {
|
|
1841
|
+
const FileCard = ({ file, onAction, busy, drive }) => {
|
|
1836
1842
|
const c = useThemeColors();
|
|
1837
1843
|
const thumb2 = file.formats?.thumbnail?.url ?? file.formats?.small?.url ?? file.url;
|
|
1838
1844
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -1871,7 +1877,8 @@ const FileCard = ({ file, onAction, busy }) => {
|
|
|
1871
1877
|
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
|
|
1872
1878
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: formatSize(file.size) }),
|
|
1873
1879
|
isGenerated(file) && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: "Generada" }),
|
|
1874
|
-
busy && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { active: true, children: busy })
|
|
1880
|
+
busy && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { active: true, children: busy }),
|
|
1881
|
+
drive?.label && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { variant: drive.tone, title: drive.title, children: drive.label })
|
|
1875
1882
|
] })
|
|
1876
1883
|
] }),
|
|
1877
1884
|
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Menu.Root, { children: [
|
|
@@ -1883,6 +1890,8 @@ const FileCard = ({ file, onAction, busy }) => {
|
|
|
1883
1890
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Menu.Item, { onSelect: () => onAction("replace", file), children: "Reemplazar" }),
|
|
1884
1891
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Menu.Item, { onSelect: () => onAction("move", file), children: "Mover a…" }),
|
|
1885
1892
|
canRemoveBackground(file) && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Menu.Item, { onSelect: () => onAction("removeBackground", file), disabled: !!busy, children: "Quitar fondo" }),
|
|
1893
|
+
drive?.action === "mark" && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Menu.Item, { onSelect: () => onAction("syncDrive", file), children: "Sincronizar con Drive" }),
|
|
1894
|
+
drive?.action === "unmark" && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Menu.Item, { onSelect: () => onAction("unsyncDrive", file), children: "Dejar de sincronizar" }),
|
|
1886
1895
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Menu.Item, { onSelect: () => onAction("delete", file), variant: "danger", children: "Eliminar" })
|
|
1887
1896
|
] })
|
|
1888
1897
|
] })
|
|
@@ -1891,10 +1900,255 @@ const FileCard = ({ file, onAction, busy }) => {
|
|
|
1891
1900
|
}
|
|
1892
1901
|
);
|
|
1893
1902
|
};
|
|
1903
|
+
const BASE$1 = "/strapi-content-generator-gemini/drive";
|
|
1904
|
+
const FOLDER = "application/vnd.google-apps.folder";
|
|
1905
|
+
const NATIVE_TYPES = {
|
|
1906
|
+
"application/vnd.google-apps.document": "Documento de Google",
|
|
1907
|
+
"application/vnd.google-apps.spreadsheet": "Hoja de cálculo de Google",
|
|
1908
|
+
"application/vnd.google-apps.presentation": "Presentación de Google",
|
|
1909
|
+
"application/vnd.google-apps.form": "Formulario de Google",
|
|
1910
|
+
"application/vnd.google-apps.drawing": "Dibujo de Google",
|
|
1911
|
+
"application/vnd.google-apps.shortcut": "Acceso directo"
|
|
1912
|
+
};
|
|
1913
|
+
const typeLabel = (mime) => {
|
|
1914
|
+
if (mime === FOLDER) return "Carpeta";
|
|
1915
|
+
if (NATIVE_TYPES[mime]) return NATIVE_TYPES[mime];
|
|
1916
|
+
if (mime.startsWith("application/vnd.google-apps.")) return "Archivo de Google";
|
|
1917
|
+
if (mime === "application/pdf") return "PDF";
|
|
1918
|
+
if (mime.startsWith("image/")) return "Imagen";
|
|
1919
|
+
if (mime.startsWith("video/")) return "Video";
|
|
1920
|
+
if (mime.startsWith("audio/")) return "Audio";
|
|
1921
|
+
return "Archivo";
|
|
1922
|
+
};
|
|
1923
|
+
const TypeIcon = ({ mime }) => mime === FOLDER ? /* @__PURE__ */ jsxRuntime.jsx(icons.Folder, { width: 16, height: 16, "aria-hidden": true }) : mime === "application/pdf" ? /* @__PURE__ */ jsxRuntime.jsx(icons.FilePdf, { width: 16, height: 16, "aria-hidden": true }) : /* @__PURE__ */ jsxRuntime.jsx(icons.File, { width: 16, height: 16, "aria-hidden": true });
|
|
1924
|
+
const formatDate = (iso) => iso ? new Date(iso).toLocaleString("es-MX", { dateStyle: "medium", timeStyle: "short", timeZone: "America/Mexico_City" }) : "—";
|
|
1925
|
+
const confirmText = (item, count) => {
|
|
1926
|
+
const parts = [`«${item.name}» irá a la papelera de Drive, donde se puede recuperar durante 30 días.`];
|
|
1927
|
+
if (item.mimeType === FOLDER && count) {
|
|
1928
|
+
const total = count.files + count.folders;
|
|
1929
|
+
parts.push(
|
|
1930
|
+
total === 0 ? "La carpeta está vacía." : `Contiene ${count.truncated ? "más de " : ""}${total.toLocaleString("es-MX")} ${total === 1 ? "elemento" : "elementos"} (${count.files} archivos y ${count.folders} carpetas), que también se van.`
|
|
1931
|
+
);
|
|
1932
|
+
}
|
|
1933
|
+
if (item.synced || count?.synced) parts.push("Está sincronizado: también se borrará de la Biblioteca del CMS.");
|
|
1934
|
+
return parts.join(" ");
|
|
1935
|
+
};
|
|
1936
|
+
const serverMessage$1 = (error, fallback) => error?.response?.data?.error?.message ?? (error instanceof Error && error.message ? error.message : fallback);
|
|
1937
|
+
const useDriveBrowserApi = () => {
|
|
1938
|
+
const { get, del } = admin.useFetchClient();
|
|
1939
|
+
return react.useMemo(
|
|
1940
|
+
() => ({
|
|
1941
|
+
browse: async (folderId, pageToken) => {
|
|
1942
|
+
try {
|
|
1943
|
+
return (await get(`${BASE$1}/folders/${encodeURIComponent(folderId)}`, { params: pageToken ? { pageToken } : {} })).data;
|
|
1944
|
+
} catch (error) {
|
|
1945
|
+
throw new Error(serverMessage$1(error, "No se pudo leer Google Drive."));
|
|
1946
|
+
}
|
|
1947
|
+
},
|
|
1948
|
+
count: async (id) => (await get(`${BASE$1}/items/${encodeURIComponent(id)}/count`)).data,
|
|
1949
|
+
trash: async (id) => {
|
|
1950
|
+
try {
|
|
1951
|
+
await del(`${BASE$1}/items/${encodeURIComponent(id)}`);
|
|
1952
|
+
} catch (error) {
|
|
1953
|
+
throw new Error(serverMessage$1(error, "No se pudo mandar a la papelera."));
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
}),
|
|
1957
|
+
[get, del]
|
|
1958
|
+
);
|
|
1959
|
+
};
|
|
1960
|
+
const DriveTab = ({ api: injected, notify }) => {
|
|
1961
|
+
const fallbackApi = useDriveBrowserApi();
|
|
1962
|
+
const api = injected ?? fallbackApi;
|
|
1963
|
+
const { toggleNotification } = admin.useNotification();
|
|
1964
|
+
const say = notify ?? ((type, message) => toggleNotification({ type, message }));
|
|
1965
|
+
const [folderId, setFolderId] = react.useState("root");
|
|
1966
|
+
const [page, setPage] = react.useState(null);
|
|
1967
|
+
const [error, setError] = react.useState(null);
|
|
1968
|
+
const [loading2, setLoading] = react.useState(true);
|
|
1969
|
+
const [loadingMore, setLoadingMore] = react.useState(false);
|
|
1970
|
+
const [doomed, setDoomed] = react.useState(null);
|
|
1971
|
+
const load = react.useCallback(async () => {
|
|
1972
|
+
setLoading(true);
|
|
1973
|
+
setError(null);
|
|
1974
|
+
try {
|
|
1975
|
+
setPage(await api.browse(folderId));
|
|
1976
|
+
} catch (err) {
|
|
1977
|
+
setError(err instanceof Error ? err.message : "No se pudo leer Google Drive.");
|
|
1978
|
+
} finally {
|
|
1979
|
+
setLoading(false);
|
|
1980
|
+
}
|
|
1981
|
+
}, [api, folderId]);
|
|
1982
|
+
react.useEffect(() => {
|
|
1983
|
+
load();
|
|
1984
|
+
}, [load]);
|
|
1985
|
+
const more = async () => {
|
|
1986
|
+
if (!page?.nextPageToken) return;
|
|
1987
|
+
setLoadingMore(true);
|
|
1988
|
+
try {
|
|
1989
|
+
const next = await api.browse(folderId, page.nextPageToken);
|
|
1990
|
+
setPage({ ...next, items: [...page.items, ...next.items] });
|
|
1991
|
+
} finally {
|
|
1992
|
+
setLoadingMore(false);
|
|
1993
|
+
}
|
|
1994
|
+
};
|
|
1995
|
+
const askDelete = async (item) => {
|
|
1996
|
+
setDoomed({ item, count: null });
|
|
1997
|
+
if (item.mimeType === FOLDER) {
|
|
1998
|
+
try {
|
|
1999
|
+
const count = await api.count(item.id);
|
|
2000
|
+
setDoomed((d) => d?.item.id === item.id ? { item, count } : d);
|
|
2001
|
+
} catch {
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
};
|
|
2005
|
+
const confirmDelete = async () => {
|
|
2006
|
+
if (!doomed) return;
|
|
2007
|
+
const { item } = doomed;
|
|
2008
|
+
setDoomed(null);
|
|
2009
|
+
try {
|
|
2010
|
+
await api.trash(item.id);
|
|
2011
|
+
say("success", `«${item.name}» se mandó a la papelera de Drive.`);
|
|
2012
|
+
await load();
|
|
2013
|
+
} catch (err) {
|
|
2014
|
+
say("danger", err instanceof Error ? err.message : "No se pudo mandar a la papelera.");
|
|
2015
|
+
}
|
|
2016
|
+
};
|
|
2017
|
+
if (error) return /* @__PURE__ */ jsxRuntime.jsx(EmptyNote, { children: error });
|
|
2018
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
|
|
2019
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { "data-testid": "drive-breadcrumb", gap: 1, alignItems: "center", wrap: "wrap", paddingBottom: 4, children: [
|
|
2020
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "ghost", size: "S", onClick: () => setFolderId("root"), children: "Mi unidad" }),
|
|
2021
|
+
(page?.path ?? []).map((p, i, all) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, alignItems: "center", children: [
|
|
2022
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.ChevronRight, { width: 10, height: 10, "aria-hidden": true }),
|
|
2023
|
+
i === all.length - 1 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children: p.name }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "ghost", size: "S", onClick: () => setFolderId(p.id), children: p.name })
|
|
2024
|
+
] }, p.id))
|
|
2025
|
+
] }),
|
|
2026
|
+
loading2 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Cargando Google Drive…" }) }) : !page?.items.length ? /* @__PURE__ */ jsxRuntime.jsx(EmptyNote, { children: "Esta carpeta de Drive está vacía." }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2027
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: 6, rowCount: page.items.length + 1, children: [
|
|
2028
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
|
|
2029
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Nombre" }) }),
|
|
2030
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Tipo" }) }),
|
|
2031
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Modificado" }) }),
|
|
2032
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Tamaño" }) }),
|
|
2033
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Estado" }) }),
|
|
2034
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.VisuallyHidden, { children: "Acciones" }) })
|
|
2035
|
+
] }) }),
|
|
2036
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: page.items.map((item) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
|
|
2037
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { style: { maxWidth: 420, whiteSpace: "normal" }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
|
|
2038
|
+
/* @__PURE__ */ jsxRuntime.jsx(TypeIcon, { mime: item.mimeType }),
|
|
2039
|
+
item.mimeType === FOLDER ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.TextButton, { onClick: () => setFolderId(item.id), style: { textAlign: "left", height: "auto" }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "semiBold", textColor: "neutral800", style: { overflowWrap: "anywhere" }, children: item.name }) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral800", style: { overflowWrap: "anywhere" }, children: item.name })
|
|
2040
|
+
] }) }),
|
|
2041
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral700", children: typeLabel(item.mimeType) }) }),
|
|
2042
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral700", style: { whiteSpace: "nowrap" }, children: formatDate(item.modifiedTime) }) }),
|
|
2043
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral700", children: item.size === null ? "—" : formatSize(item.size / 1e3) }) }),
|
|
2044
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: item.synced && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { variant: "success", children: "Sincronizado" }) }),
|
|
2045
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, justifyContent: "end", children: [
|
|
2046
|
+
item.webViewLink && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2047
|
+
designSystem.LinkButton,
|
|
2048
|
+
{
|
|
2049
|
+
variant: "tertiary",
|
|
2050
|
+
size: "S",
|
|
2051
|
+
href: item.webViewLink,
|
|
2052
|
+
target: "_blank",
|
|
2053
|
+
rel: "noreferrer noopener",
|
|
2054
|
+
endIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ExternalLink, {}),
|
|
2055
|
+
children: "Abrir en Drive"
|
|
2056
|
+
}
|
|
2057
|
+
),
|
|
2058
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.IconButton, { label: `Eliminar ${item.name}`, variant: "ghost", onClick: () => askDelete(item), children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {}) })
|
|
2059
|
+
] }) })
|
|
2060
|
+
] }, item.id)) })
|
|
2061
|
+
] }),
|
|
2062
|
+
page.nextPageToken && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", paddingTop: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", onClick: more, loading: loadingMore, children: "Mostrar más" }) })
|
|
2063
|
+
] }),
|
|
2064
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2065
|
+
ConfirmDialog,
|
|
2066
|
+
{
|
|
2067
|
+
open: !!doomed,
|
|
2068
|
+
title: "¿Mandar a la papelera de Drive?",
|
|
2069
|
+
confirmLabel: "Mandar a la papelera",
|
|
2070
|
+
onClose: () => setDoomed(null),
|
|
2071
|
+
onConfirm: confirmDelete,
|
|
2072
|
+
children: doomed ? confirmText(doomed.item, doomed.count) : ""
|
|
2073
|
+
}
|
|
2074
|
+
)
|
|
2075
|
+
] });
|
|
2076
|
+
};
|
|
2077
|
+
const BASE = "/strapi-content-generator-gemini/drive";
|
|
2078
|
+
const POLL_MS = 1e4;
|
|
2079
|
+
const LABELS = {
|
|
2080
|
+
synced: { label: "Drive", tone: "success" },
|
|
2081
|
+
pending: { label: "Drive: pendiente", tone: "neutral" },
|
|
2082
|
+
error: { label: "Drive: error", tone: "danger" },
|
|
2083
|
+
skipped: { label: "Drive: omitido", tone: "warning" }
|
|
2084
|
+
};
|
|
2085
|
+
const driveView = (links, key, parentInside) => {
|
|
2086
|
+
if (!links?.connected) return null;
|
|
2087
|
+
const item = links.items[key];
|
|
2088
|
+
if (item?.inside) {
|
|
2089
|
+
return { ...LABELS[item.status], title: item.error ?? void 0, action: item.root ? "unmark" : null };
|
|
2090
|
+
}
|
|
2091
|
+
if (parentInside) return { ...LABELS.pending, action: null };
|
|
2092
|
+
return { label: null, tone: "neutral", action: "mark" };
|
|
2093
|
+
};
|
|
2094
|
+
const serverMessage = (error, fallback) => error?.response?.data?.error?.message ?? fallback;
|
|
2095
|
+
const useDriveSync = (watching = false) => {
|
|
2096
|
+
const { get, post, del } = admin.useFetchClient();
|
|
2097
|
+
const [links, setLinks] = react.useState(null);
|
|
2098
|
+
const refresh = react.useCallback(async () => {
|
|
2099
|
+
try {
|
|
2100
|
+
const { data } = await get(`${BASE}/links`);
|
|
2101
|
+
setLinks(data);
|
|
2102
|
+
} catch {
|
|
2103
|
+
setLinks(null);
|
|
2104
|
+
}
|
|
2105
|
+
}, [get]);
|
|
2106
|
+
react.useEffect(() => {
|
|
2107
|
+
refresh();
|
|
2108
|
+
}, [refresh]);
|
|
2109
|
+
const busy = !!links?.connected && (watching || links.pending > 0 || Object.values(links.items).some((i) => i.status === "pending"));
|
|
2110
|
+
react.useEffect(() => {
|
|
2111
|
+
if (!busy) return;
|
|
2112
|
+
const timer = setInterval(refresh, POLL_MS);
|
|
2113
|
+
return () => clearInterval(timer);
|
|
2114
|
+
}, [busy, refresh]);
|
|
2115
|
+
react.useEffect(() => {
|
|
2116
|
+
const onVisible = () => document.visibilityState === "visible" && refresh();
|
|
2117
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
2118
|
+
return () => document.removeEventListener("visibilitychange", onVisible);
|
|
2119
|
+
}, [refresh]);
|
|
2120
|
+
return react.useMemo(
|
|
2121
|
+
() => ({
|
|
2122
|
+
links,
|
|
2123
|
+
refresh,
|
|
2124
|
+
async mark(cmsType, cmsId) {
|
|
2125
|
+
try {
|
|
2126
|
+
await post(`${BASE}/links`, { cmsType, cmsId });
|
|
2127
|
+
} catch (error) {
|
|
2128
|
+
throw new Error(serverMessage(error, "No se pudo sincronizar con Drive."));
|
|
2129
|
+
} finally {
|
|
2130
|
+
await refresh();
|
|
2131
|
+
}
|
|
2132
|
+
},
|
|
2133
|
+
async unmark(cmsType, cmsId) {
|
|
2134
|
+
try {
|
|
2135
|
+
await del(`${BASE}/links/${cmsType}/${cmsId}`);
|
|
2136
|
+
} catch (error) {
|
|
2137
|
+
throw new Error(serverMessage(error, "No se pudo dejar de sincronizar."));
|
|
2138
|
+
} finally {
|
|
2139
|
+
await refresh();
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
}),
|
|
2143
|
+
[links, refresh, post, del]
|
|
2144
|
+
);
|
|
2145
|
+
};
|
|
1894
2146
|
const status = (err) => err?.response?.status ?? err?.status;
|
|
1895
2147
|
const LibraryTab = () => {
|
|
1896
2148
|
const c = useThemeColors();
|
|
1897
2149
|
const { api, notify } = useLibraryApi();
|
|
2150
|
+
const [drivePending, setDrivePending] = react.useState(false);
|
|
2151
|
+
const drive = useDriveSync(drivePending);
|
|
1898
2152
|
const [tree, setTree] = react.useState([]);
|
|
1899
2153
|
const [counts, setCounts] = react.useState({});
|
|
1900
2154
|
const [current, setCurrent] = react.useState(null);
|
|
@@ -1938,7 +2192,20 @@ const LibraryTab = () => {
|
|
|
1938
2192
|
react.useEffect(() => {
|
|
1939
2193
|
loadFiles();
|
|
1940
2194
|
}, [loadFiles]);
|
|
1941
|
-
const refresh = () => Promise.all([loadTree(), loadFiles()]);
|
|
2195
|
+
const refresh = () => Promise.all([loadTree(), loadFiles(), drive.refresh()]);
|
|
2196
|
+
const toggleDrive = async (mode, cmsType, cmsId) => {
|
|
2197
|
+
try {
|
|
2198
|
+
if (mode === "mark") {
|
|
2199
|
+
await drive.mark(cmsType, cmsId);
|
|
2200
|
+
notify("success", "Se sincronizará con Drive en unos segundos.");
|
|
2201
|
+
} else {
|
|
2202
|
+
await drive.unmark(cmsType, cmsId);
|
|
2203
|
+
notify("success", "Dejó de sincronizarse. No se borró nada en Drive ni aquí.");
|
|
2204
|
+
}
|
|
2205
|
+
} catch (err) {
|
|
2206
|
+
notify("danger", err instanceof Error ? err.message : "No se pudo completar la operación con Drive.");
|
|
2207
|
+
}
|
|
2208
|
+
};
|
|
1942
2209
|
const run = async (action, ok) => {
|
|
1943
2210
|
try {
|
|
1944
2211
|
await action();
|
|
@@ -1995,12 +2262,24 @@ const LibraryTab = () => {
|
|
|
1995
2262
|
if (action === "rename") return setDialog({ kind: "renameFile", file });
|
|
1996
2263
|
if (action === "move") return setDialog({ kind: "moveFile", file });
|
|
1997
2264
|
if (action === "delete") return setDialog({ kind: "deleteFile", file });
|
|
2265
|
+
if (action === "syncDrive") return toggleDrive("mark", "file", file.id);
|
|
2266
|
+
if (action === "unsyncDrive") return toggleDrive("unmark", "file", file.id);
|
|
1998
2267
|
};
|
|
1999
2268
|
const validateFolder = (parent, except) => (name) => cleanFolderName(name) !== except && nameTaken(tree, parent, name) ? "Ya existe una carpeta con ese nombre aquí." : null;
|
|
2269
|
+
const pendingShown = (() => {
|
|
2270
|
+
const inside = current !== null && !!drive.links?.items[`folder:${current}`]?.inside;
|
|
2271
|
+
const isPending = (key) => driveView(drive.links, key, inside)?.label === "Drive: pendiente";
|
|
2272
|
+
return files.some((f) => isPending(`file:${f.id}`)) || childrenOf(tree, current).some((f) => isPending(`folder:${f.id}`));
|
|
2273
|
+
})();
|
|
2274
|
+
react.useEffect(() => {
|
|
2275
|
+
setDrivePending(pendingShown);
|
|
2276
|
+
}, [pendingShown]);
|
|
2000
2277
|
if (forbidden) {
|
|
2001
2278
|
return /* @__PURE__ */ jsxRuntime.jsx(EmptyNote, { children: "Para usar la biblioteca necesitas permiso de la Media Library de Strapi. Pídelo a un administrador." });
|
|
2002
2279
|
}
|
|
2003
2280
|
const subfolders = childrenOf(tree, current);
|
|
2281
|
+
const currentInside = current !== null && !!drive.links?.items[`folder:${current}`]?.inside;
|
|
2282
|
+
const folderDrive = current !== null ? driveView(drive.links, `folder:${current}`, !!(folder && path.length > 1 && drive.links?.items[`folder:${path[path.length - 2].id}`]?.inside)) : null;
|
|
2004
2283
|
return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, alignItems: "stretch", children: [
|
|
2005
2284
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "280px", shrink: 0, background: "neutral0", padding: 3, hasRadius: true, style: { maxHeight: "calc(100vh - 220px)", overflowY: "auto" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2006
2285
|
FolderTree,
|
|
@@ -2047,6 +2326,9 @@ const LibraryTab = () => {
|
|
|
2047
2326
|
] }, f.id))
|
|
2048
2327
|
] }),
|
|
2049
2328
|
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
|
|
2329
|
+
folder && folderDrive?.label && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { variant: folderDrive.tone, title: folderDrive.title, children: folderDrive.label }),
|
|
2330
|
+
folder && folderDrive?.action === "mark" && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "secondary", onClick: () => toggleDrive("mark", "folder", folder.id), children: "Sincronizar con Drive" }),
|
|
2331
|
+
folder && folderDrive?.action === "unmark" && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", onClick: () => toggleDrive("unmark", "folder", folder.id), children: "Dejar de sincronizar" }),
|
|
2050
2332
|
folder && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2051
2333
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Pencil, {}), onClick: () => setDialog({ kind: "renameFolder" }), children: "Renombrar carpeta" }),
|
|
2052
2334
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "danger-light", startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {}), onClick: () => setDialog({ kind: "deleteFolder" }), children: "Eliminar carpeta" })
|
|
@@ -2071,11 +2353,15 @@ const LibraryTab = () => {
|
|
|
2071
2353
|
children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
|
|
2072
2354
|
/* @__PURE__ */ jsxRuntime.jsx(icons.Folder, { width: 16, height: 16, "aria-hidden": true }),
|
|
2073
2355
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, style: { flex: 1 }, children: f.name }),
|
|
2356
|
+
(() => {
|
|
2357
|
+
const view = driveView(drive.links, `folder:${f.id}`, currentInside);
|
|
2358
|
+
return view?.label ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { size: "S", variant: view.tone, title: view.title, children: view.label }) : null;
|
|
2359
|
+
})(),
|
|
2074
2360
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: counts[f.id]?.files ?? 0 })
|
|
2075
2361
|
] })
|
|
2076
2362
|
}
|
|
2077
2363
|
) }, f.id)) }),
|
|
2078
|
-
loading2 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Cargando archivos…" }) }) : files.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(EmptyNote, { children: subfolders.length ? "No hay archivos sueltos en esta carpeta." : "Carpeta vacía. Arrastra archivos aquí o usa «Subir archivos»." }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Root, { gap: 4, children: files.map((f) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 3, m: 4, s: 6, xs: 12, children: /* @__PURE__ */ jsxRuntime.jsx(FileCard, { file: f, onAction: onFileAction, busy: bgBusy[f.id] }) }, f.id)) }),
|
|
2364
|
+
loading2 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Cargando archivos…" }) }) : files.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(EmptyNote, { children: subfolders.length ? "No hay archivos sueltos en esta carpeta." : "Carpeta vacía. Arrastra archivos aquí o usa «Subir archivos»." }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Root, { gap: 4, children: files.map((f) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 3, m: 4, s: 6, xs: 12, children: /* @__PURE__ */ jsxRuntime.jsx(FileCard, { file: f, onAction: onFileAction, busy: bgBusy[f.id], drive: driveView(drive.links, `file:${f.id}`, currentInside) }) }, f.id)) }),
|
|
2079
2365
|
dragOver && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { position: "absolute", justifyContent: "center", background: "primary100", hasRadius: true, style: { inset: 0, border: `2px dashed ${c.primary600}`, opacity: 0.95 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "beta", textColor: "primary700", children: [
|
|
2080
2366
|
"Suelta para subir a «",
|
|
2081
2367
|
folder?.name ?? ROOT_LABEL,
|
|
@@ -2187,17 +2473,6 @@ const LibraryTab = () => {
|
|
|
2187
2473
|
/* @__PURE__ */ jsxRuntime.jsx(RemoveBackgroundDialog, { open: !!bgTarget, src: bgTarget?.src ?? "", onClose: () => setBgTarget(null), onApply: saveNoBackground })
|
|
2188
2474
|
] });
|
|
2189
2475
|
};
|
|
2190
|
-
const PALETTE = [
|
|
2191
|
-
{ label: "Navy", value: "#1F2A44" },
|
|
2192
|
-
{ label: "Rojo", value: "#E4002B" },
|
|
2193
|
-
{ label: "Teal", value: "#00C7B1" },
|
|
2194
|
-
{ label: "Teal suave", value: "#92CFC7" },
|
|
2195
|
-
{ label: "Amarillo", value: "#FFC844" },
|
|
2196
|
-
{ label: "Amarillo suave", value: "#F8DF8D" },
|
|
2197
|
-
{ label: "Gris", value: "#5B6473" },
|
|
2198
|
-
{ label: "Papel", value: "#EEF1F5" },
|
|
2199
|
-
{ label: "Blanco", value: "#FFFFFF" }
|
|
2200
|
-
];
|
|
2201
2476
|
const NAVY = "#1F2A44";
|
|
2202
2477
|
const RED$1 = "#E4002B";
|
|
2203
2478
|
const TEAL$1 = "#00C7B1";
|
|
@@ -2768,6 +3043,15 @@ const editorReducer = (state, action) => {
|
|
|
2768
3043
|
}
|
|
2769
3044
|
}
|
|
2770
3045
|
};
|
|
3046
|
+
const ORIGIN_URL = "/strapi-content-generator-gemini/drive/origin";
|
|
3047
|
+
const recordTemplateOrigin = async (post, files, template) => {
|
|
3048
|
+
for (const file of files) {
|
|
3049
|
+
try {
|
|
3050
|
+
await post(ORIGIN_URL, { fileId: file.id, templateDocumentId: template.documentId ?? template.id, templateName: template.name });
|
|
3051
|
+
} catch {
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
};
|
|
2771
3055
|
const useEditorIO = () => {
|
|
2772
3056
|
const { post } = admin.useFetchClient();
|
|
2773
3057
|
const { toggleNotification } = admin.useNotification();
|
|
@@ -2800,7 +3084,8 @@ const useEditorIO = () => {
|
|
|
2800
3084
|
if (!node) return false;
|
|
2801
3085
|
try {
|
|
2802
3086
|
const blob = await renderPiece(node, template, "png");
|
|
2803
|
-
await api.saveBlob(blob, /\.png$/i.test(name) ? name : `${name}.png`, folderId);
|
|
3087
|
+
const saved = await api.saveBlob(blob, /\.png$/i.test(name) ? name : `${name}.png`, folderId);
|
|
3088
|
+
await recordTemplateOrigin((u, body) => post(u, body), saved, template);
|
|
2804
3089
|
toggleNotification({ type: "success", message: "Pieza guardada en la biblioteca." });
|
|
2805
3090
|
return true;
|
|
2806
3091
|
} catch {
|
|
@@ -2808,7 +3093,7 @@ const useEditorIO = () => {
|
|
|
2808
3093
|
return false;
|
|
2809
3094
|
}
|
|
2810
3095
|
},
|
|
2811
|
-
[api, toggleNotification]
|
|
3096
|
+
[api, post, toggleNotification]
|
|
2812
3097
|
);
|
|
2813
3098
|
return { loadImage: loadImage2, exportAs, saveToLibrary };
|
|
2814
3099
|
};
|
|
@@ -3937,44 +4222,63 @@ const SaveTemplateDialog = ({ open, defaultName, defaultFolder, folders, onClose
|
|
|
3937
4222
|
] })
|
|
3938
4223
|
] }) });
|
|
3939
4224
|
};
|
|
4225
|
+
const parseHex = (raw) => {
|
|
4226
|
+
const hex = raw.trim().replace(/^#/, "");
|
|
4227
|
+
if (/^[0-9a-f]{3}$/i.test(hex)) return `#${[...hex].map((c) => c + c).join("")}`.toUpperCase();
|
|
4228
|
+
if (/^[0-9a-f]{6}$/i.test(hex)) return `#${hex}`.toUpperCase();
|
|
4229
|
+
return null;
|
|
4230
|
+
};
|
|
3940
4231
|
const ColorField = ({ label, value, onChange }) => {
|
|
3941
4232
|
const tc = useThemeColors();
|
|
4233
|
+
const current = parseHex(value ?? "") ?? "#000000";
|
|
4234
|
+
const [draft, setDraft] = react.useState(current);
|
|
4235
|
+
react.useEffect(() => setDraft((d) => parseHex(d) === current ? d : current), [current]);
|
|
4236
|
+
const apply = (text) => {
|
|
4237
|
+
const hex = parseHex(text);
|
|
4238
|
+
if (hex && hex !== current) onChange(hex);
|
|
4239
|
+
};
|
|
4240
|
+
const typeHex = (text) => {
|
|
4241
|
+
setDraft(text);
|
|
4242
|
+
if (text.trim().replace(/^#/, "").length === 6) apply(text);
|
|
4243
|
+
};
|
|
4244
|
+
const leave = () => {
|
|
4245
|
+
apply(draft);
|
|
4246
|
+
setDraft(parseHex(draft) ?? current);
|
|
4247
|
+
};
|
|
3942
4248
|
return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 1, children: [
|
|
3943
4249
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", children: label }),
|
|
3944
|
-
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap:
|
|
3945
|
-
PALETTE.map((c) => {
|
|
3946
|
-
const active = value?.toUpperCase() === c.value;
|
|
3947
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3948
|
-
designSystem.Box,
|
|
3949
|
-
{
|
|
3950
|
-
tag: "button",
|
|
3951
|
-
color: "neutral800",
|
|
3952
|
-
type: "button",
|
|
3953
|
-
"aria-label": `${label}: ${c.label}`,
|
|
3954
|
-
"aria-pressed": active,
|
|
3955
|
-
title: c.label,
|
|
3956
|
-
onClick: () => onChange(c.value),
|
|
3957
|
-
style: {
|
|
3958
|
-
width: 22,
|
|
3959
|
-
height: 22,
|
|
3960
|
-
borderRadius: 4,
|
|
3961
|
-
cursor: "pointer",
|
|
3962
|
-
background: c.value,
|
|
3963
|
-
border: active ? `2px solid ${tc.primary600}` : `1px solid ${tc.neutral200}`,
|
|
3964
|
-
padding: 0
|
|
3965
|
-
}
|
|
3966
|
-
},
|
|
3967
|
-
c.value
|
|
3968
|
-
);
|
|
3969
|
-
}),
|
|
4250
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
|
|
3970
4251
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3971
4252
|
"input",
|
|
3972
4253
|
{
|
|
3973
4254
|
type: "color",
|
|
3974
|
-
"aria-label": `${label}
|
|
3975
|
-
|
|
4255
|
+
"aria-label": `${label}: selector`,
|
|
4256
|
+
title: "Abrir selector de color",
|
|
4257
|
+
value: current.toLowerCase(),
|
|
3976
4258
|
onChange: (e) => onChange(e.target.value.toUpperCase()),
|
|
3977
|
-
style: {
|
|
4259
|
+
style: {
|
|
4260
|
+
flexShrink: 0,
|
|
4261
|
+
width: 32,
|
|
4262
|
+
height: 32,
|
|
4263
|
+
padding: 2,
|
|
4264
|
+
borderRadius: 4,
|
|
4265
|
+
cursor: "pointer",
|
|
4266
|
+
border: `1px solid ${tc.neutral200}`,
|
|
4267
|
+
background: tc.neutral0
|
|
4268
|
+
}
|
|
4269
|
+
}
|
|
4270
|
+
),
|
|
4271
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4272
|
+
designSystem.TextInput,
|
|
4273
|
+
{
|
|
4274
|
+
"aria-label": `${label}: hexadecimal`,
|
|
4275
|
+
size: "S",
|
|
4276
|
+
value: draft,
|
|
4277
|
+
placeholder: "#1F2A44",
|
|
4278
|
+
maxLength: 7,
|
|
4279
|
+
spellCheck: false,
|
|
4280
|
+
onChange: (e) => typeHex(e.target.value),
|
|
4281
|
+
onBlur: leave
|
|
3978
4282
|
}
|
|
3979
4283
|
)
|
|
3980
4284
|
] })
|
|
@@ -5234,7 +5538,8 @@ const TemplatesTab = () => {
|
|
|
5234
5538
|
const STUDIO_TABS = [
|
|
5235
5539
|
{ value: "generator", label: "Generador" },
|
|
5236
5540
|
{ value: "templates", label: "Plantillas" },
|
|
5237
|
-
{ value: "library", label: "Imágenes y documentos" }
|
|
5541
|
+
{ value: "library", label: "Imágenes y documentos" },
|
|
5542
|
+
{ value: "drive", label: "Google Drive" }
|
|
5238
5543
|
];
|
|
5239
5544
|
const PANEL = { height: "100%", overflowY: "auto" };
|
|
5240
5545
|
const isStudioTab = (v) => STUDIO_TABS.some((t) => t.value === v);
|
|
@@ -5262,7 +5567,8 @@ const ImageStudioTabs = () => {
|
|
|
5262
5567
|
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { flex: "1", style: { minHeight: 0 }, children: [
|
|
5263
5568
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "generator", style: PANEL, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 6, children: /* @__PURE__ */ jsxRuntime.jsx(GeneratorTab, {}) }) }),
|
|
5264
5569
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "templates", style: PANEL, children: /* @__PURE__ */ jsxRuntime.jsx(TemplatesTab, {}) }),
|
|
5265
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "library", style: PANEL, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 6, children: /* @__PURE__ */ jsxRuntime.jsx(LibraryTab, {}) }) })
|
|
5570
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "library", style: PANEL, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 6, children: /* @__PURE__ */ jsxRuntime.jsx(LibraryTab, {}) }) }),
|
|
5571
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "drive", style: PANEL, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 6, children: tab === "drive" && /* @__PURE__ */ jsxRuntime.jsx(DriveTab, {}) }) })
|
|
5266
5572
|
] })
|
|
5267
5573
|
]
|
|
5268
5574
|
}
|