@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
|
@@ -5,8 +5,8 @@ import { P as PluginProviders } from "./PluginProviders-DWMpcAIh.mjs";
|
|
|
5
5
|
import { useMemo, useState, useEffect, useRef, useCallback } from "react";
|
|
6
6
|
import { Box, Flex, Typography, Button, Badge, TextInput, Loader, Table, Thead, Tr, Th, VisuallyHidden, Tbody, Td, TextButton, IconButton, Link, LinkButton, Field, Radio, Modal, Textarea, Combobox, ComboboxOption, Toggle, Grid } from "@strapi/design-system";
|
|
7
7
|
import { Plus, Search, Check, Pencil, ExternalLink, ArrowLeft, Image, Clock, WarningCircle, Trash, Sparkle, PlusCircle } from "@strapi/icons";
|
|
8
|
-
import { P as PLUGIN_ID, S as STUDIO_PATH, p as pluginPermissions } from "./index-
|
|
9
|
-
import { L as LibraryImagePicker, P as PromptComposer, a as LEGACY_ROUTES, S as StudioRedirect, l as legacyRedirect } from "./ImageStudioTabs-
|
|
8
|
+
import { P as PLUGIN_ID, S as STUDIO_PATH, p as pluginPermissions } from "./index-zenHkX-p.mjs";
|
|
9
|
+
import { L as LibraryImagePicker, P as PromptComposer, a as LEGACY_ROUTES, S as StudioRedirect, l as legacyRedirect } from "./ImageStudioTabs-DwaLxRjl.mjs";
|
|
10
10
|
import { useIntl } from "react-intl";
|
|
11
11
|
import { useFormik } from "formik";
|
|
12
12
|
import * as Yup from "yup";
|
|
@@ -7,8 +7,8 @@ const PluginProviders = require("./PluginProviders-D79TeGSE.js");
|
|
|
7
7
|
const react = require("react");
|
|
8
8
|
const designSystem = require("@strapi/design-system");
|
|
9
9
|
const icons = require("@strapi/icons");
|
|
10
|
-
const index = require("./index-
|
|
11
|
-
const ImageStudioTabs = require("./ImageStudioTabs-
|
|
10
|
+
const index = require("./index-BudahdfX.js");
|
|
11
|
+
const ImageStudioTabs = require("./ImageStudioTabs-uXSBkFeu.js");
|
|
12
12
|
const reactIntl = require("react-intl");
|
|
13
13
|
const formik = require("formik");
|
|
14
14
|
const Yup = require("yup");
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Modal, Flex, Button, Loader, Typography, Grid, Box, lightTheme, Checkbox, Field, TextInput, Dialog, SingleSelect, SingleSelectOption, Link, Badge, IconButtonGroup, IconButton, Toggle, Menu, NumberInput, Divider, Textarea, Tabs } from "@strapi/design-system";
|
|
2
|
+
import { Modal, Flex, Button, Loader, Typography, Grid, Box, lightTheme, Checkbox, Field, TextInput, Dialog, SingleSelect, SingleSelectOption, Link, Badge, IconButtonGroup, IconButton, Toggle, Menu, Table, Thead, Tr, Th, VisuallyHidden, Tbody, Td, TextButton, LinkButton, NumberInput, Divider, Textarea, Tabs } from "@strapi/design-system";
|
|
3
3
|
import { useLocation, Navigate, useSearchParams } from "react-router-dom";
|
|
4
|
-
import { S as STUDIO_PATH } from "./index-
|
|
4
|
+
import { S as STUDIO_PATH } from "./index-zenHkX-p.mjs";
|
|
5
5
|
import { useMemo, useState, useEffect, useId, useRef, useReducer, useCallback, useSyncExternalStore, useLayoutEffect, memo, forwardRef } from "react";
|
|
6
|
-
import { ChevronRight, Folder, Magic, Pencil, Upload, Sparkle, Plus, Cross, Download, ArrowClockwise, House, ChevronDown, FilePdf, File as File$1, More, Trash, User, Star, SquaresFour, Quotes, PriceTag, Paragraph, NumberList, Minus, Italic, Image as Image$1, HeadingTwo, HeadingOne, Hashtag, Feather, Drag, BulletList, ArrowLeft, Check, ArrowsCounterClockwise, ArrowLineLeft, ArrowLineRight, Expand, WarningCircle, Play, Phone, Mail, PinMap, Earth, PresentationChart, ChartPie, ChartBubble, Cog, Database, Cloud, Globe, Server, Lightbulb, Crown, SealCheck, Briefcase, Rocket, Shield, CheckCircle, Lightning, Heart, Collapse, Bold, ArrowUp, ArrowDown, Duplicate, Lock, EyeStriked, Eye } from "@strapi/icons";
|
|
6
|
+
import { ChevronRight, Folder, Magic, Pencil, Upload, Sparkle, Plus, Cross, Download, ArrowClockwise, House, ChevronDown, FilePdf, File as File$1, More, ExternalLink, Trash, User, Star, SquaresFour, Quotes, PriceTag, Paragraph, NumberList, Minus, Italic, Image as Image$1, HeadingTwo, HeadingOne, Hashtag, Feather, Drag, BulletList, ArrowLeft, Check, ArrowsCounterClockwise, ArrowLineLeft, ArrowLineRight, Expand, WarningCircle, Play, Phone, Mail, PinMap, Earth, PresentationChart, ChartPie, ChartBubble, Cog, Database, Cloud, Globe, Server, Lightbulb, Crown, SealCheck, Briefcase, Rocket, Shield, CheckCircle, Lightning, Heart, Collapse, Bold, ArrowUp, ArrowDown, Duplicate, Lock, EyeStriked, Eye } from "@strapi/icons";
|
|
7
7
|
import { useTheme } from "styled-components";
|
|
8
8
|
import { useFetchClient, useNotification } from "@strapi/strapi/admin";
|
|
9
9
|
import { toPng, toSvg, toJpeg } from "html-to-image";
|
|
@@ -737,6 +737,9 @@ const RemoveBackgroundButton = ({ src, onDone }) => {
|
|
|
737
737
|
)
|
|
738
738
|
] });
|
|
739
739
|
};
|
|
740
|
+
const IMAGE_MAX_MB = 30;
|
|
741
|
+
const IMAGE_MAX_BYTES = IMAGE_MAX_MB * 1024 * 1024;
|
|
742
|
+
const IMAGE_FORMATS_HINT = `PNG, JPG o WEBP de hasta ${IMAGE_MAX_MB} MB.`;
|
|
740
743
|
const NameDialog = ({ open, title, label, initial = "", confirmLabel, validate, onClose, onConfirm }) => {
|
|
741
744
|
const [name, setName] = useState(initial);
|
|
742
745
|
const [error, setError] = useState(null);
|
|
@@ -1168,7 +1171,7 @@ const closestRatio = (width, height, ratios) => {
|
|
|
1168
1171
|
};
|
|
1169
1172
|
return ratios.reduce((best, r) => Math.abs(value(r) - target) < Math.abs(value(best) - target) ? r : best);
|
|
1170
1173
|
};
|
|
1171
|
-
const BASE = "/strapi-content-generator-gemini/image-studio";
|
|
1174
|
+
const BASE$2 = "/strapi-content-generator-gemini/image-studio";
|
|
1172
1175
|
const toResult = ({ image, mimeType, model, grounding }) => ({
|
|
1173
1176
|
src: `data:${mimeType};base64,${image}`,
|
|
1174
1177
|
model,
|
|
@@ -1180,7 +1183,7 @@ const errorMessage = (err, fallback) => {
|
|
|
1180
1183
|
};
|
|
1181
1184
|
const createGeneratorApi = (client) => ({
|
|
1182
1185
|
async models() {
|
|
1183
|
-
const { data } = await client.get(`${BASE}/models`);
|
|
1186
|
+
const { data } = await client.get(`${BASE$2}/models`);
|
|
1184
1187
|
return data;
|
|
1185
1188
|
},
|
|
1186
1189
|
async generate(p) {
|
|
@@ -1193,7 +1196,7 @@ const createGeneratorApi = (client) => ({
|
|
|
1193
1196
|
form.append("allowPeople", String(p.allowPeople));
|
|
1194
1197
|
form.append("grounding", String(p.grounding));
|
|
1195
1198
|
p.references.forEach((f) => form.append("references", f, f.name));
|
|
1196
|
-
const { data } = await client.post(`${BASE}/generate`, form);
|
|
1199
|
+
const { data } = await client.post(`${BASE$2}/generate`, form);
|
|
1197
1200
|
return toResult(data);
|
|
1198
1201
|
},
|
|
1199
1202
|
async edit(p) {
|
|
@@ -1206,11 +1209,11 @@ const createGeneratorApi = (client) => ({
|
|
|
1206
1209
|
p.references.forEach((f) => form.append("references", f, f.name));
|
|
1207
1210
|
if (p.aspectRatio) form.append("aspectRatio", p.aspectRatio);
|
|
1208
1211
|
form.append("image", new File([blob], `base.${blob.type.split("/")[1] ?? "png"}`, { type: blob.type }));
|
|
1209
|
-
const { data } = await client.post(`${BASE}/edit`, form);
|
|
1212
|
+
const { data } = await client.post(`${BASE$2}/edit`, form);
|
|
1210
1213
|
return toResult(data);
|
|
1211
1214
|
},
|
|
1212
1215
|
async expand(prompt) {
|
|
1213
|
-
const { data } = await client.post(`${BASE}/expand`, { prompt });
|
|
1216
|
+
const { data } = await client.post(`${BASE$2}/expand`, { prompt });
|
|
1214
1217
|
return data.prompt;
|
|
1215
1218
|
}
|
|
1216
1219
|
});
|
|
@@ -1250,7 +1253,10 @@ const EditEmpty = ({ disabled, onUpload, onOpenLibrary, onOpenRecent }) => {
|
|
|
1250
1253
|
style: { width: "100%", maxWidth: 600, border: `2px dashed ${over ? c.primary600 : c.neutral200}` },
|
|
1251
1254
|
children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "center", gap: 3, style: { textAlign: "center" }, children: [
|
|
1252
1255
|
/* @__PURE__ */ jsx(Typography, { variant: "beta", tag: "h2", children: "Elige la imagen que quieres editar" }),
|
|
1253
|
-
/* @__PURE__ */
|
|
1256
|
+
/* @__PURE__ */ jsxs(Typography, { textColor: "neutral600", children: [
|
|
1257
|
+
"Arrástrala aquí, súbela o elígela de la biblioteca. ",
|
|
1258
|
+
IMAGE_FORMATS_HINT
|
|
1259
|
+
] }),
|
|
1254
1260
|
/* @__PURE__ */ jsxs(Flex, { gap: 3, paddingTop: 2, children: [
|
|
1255
1261
|
/* @__PURE__ */ jsx(Button, { variant: "tertiary", startIcon: /* @__PURE__ */ jsx(Upload, {}), disabled, onClick: () => input.current?.click(), children: "Subir imagen" }),
|
|
1256
1262
|
/* @__PURE__ */ jsx(Button, { startIcon: /* @__PURE__ */ jsx(Folder, {}), disabled, onClick: onOpenLibrary, children: "Elegir de la biblioteca" })
|
|
@@ -1325,7 +1331,7 @@ const ReferencePicker = ({
|
|
|
1325
1331
|
onChange,
|
|
1326
1332
|
onPickFromLibrary,
|
|
1327
1333
|
label = "Imágenes de referencia",
|
|
1328
|
-
hint =
|
|
1334
|
+
hint = IMAGE_FORMATS_HINT,
|
|
1329
1335
|
noun = REFERENCES
|
|
1330
1336
|
}) => {
|
|
1331
1337
|
const input = useRef(null);
|
|
@@ -1519,8 +1525,8 @@ const GeneratorTab = () => {
|
|
|
1519
1525
|
};
|
|
1520
1526
|
const uploadBase = (file) => {
|
|
1521
1527
|
if (!file) return;
|
|
1522
|
-
if (!IMAGE_MIMES.includes(file.type) || file.size >
|
|
1523
|
-
notify("warning",
|
|
1528
|
+
if (!IMAGE_MIMES.includes(file.type) || file.size > IMAGE_MAX_BYTES) {
|
|
1529
|
+
notify("warning", `Usa una imagen ${IMAGE_FORMATS_HINT}`);
|
|
1524
1530
|
return;
|
|
1525
1531
|
}
|
|
1526
1532
|
const reader = new FileReader();
|
|
@@ -1595,7 +1601,7 @@ const GeneratorTab = () => {
|
|
|
1595
1601
|
ReferencePicker,
|
|
1596
1602
|
{
|
|
1597
1603
|
label: "Referencias de estilo",
|
|
1598
|
-
hint:
|
|
1604
|
+
hint: `Guían el estilo, no se copian. ${IMAGE_FORMATS_HINT}`,
|
|
1599
1605
|
files: references,
|
|
1600
1606
|
max: model?.maxReferences ?? 0,
|
|
1601
1607
|
modelLabel: model?.label ?? "Este modelo",
|
|
@@ -1829,7 +1835,7 @@ const isImage = (f) => (f.mime ?? "").startsWith("image/");
|
|
|
1829
1835
|
const canRemoveBackground = (f) => isImage(f) && f.mime !== "image/svg+xml";
|
|
1830
1836
|
const isGenerated = (f) => (f.caption ?? "").startsWith("AI generated content");
|
|
1831
1837
|
const formatSize = (kb = 0) => kb >= 1024 ? `${(kb / 1024).toFixed(1)} MB` : `${Math.round(kb)} KB`;
|
|
1832
|
-
const FileCard = ({ file, onAction, busy }) => {
|
|
1838
|
+
const FileCard = ({ file, onAction, busy, drive }) => {
|
|
1833
1839
|
const c = useThemeColors();
|
|
1834
1840
|
const thumb2 = file.formats?.thumbnail?.url ?? file.formats?.small?.url ?? file.url;
|
|
1835
1841
|
return /* @__PURE__ */ jsxs(
|
|
@@ -1868,7 +1874,8 @@ const FileCard = ({ file, onAction, busy }) => {
|
|
|
1868
1874
|
/* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
|
|
1869
1875
|
/* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: formatSize(file.size) }),
|
|
1870
1876
|
isGenerated(file) && /* @__PURE__ */ jsx(Badge, { children: "Generada" }),
|
|
1871
|
-
busy && /* @__PURE__ */ jsx(Badge, { active: true, children: busy })
|
|
1877
|
+
busy && /* @__PURE__ */ jsx(Badge, { active: true, children: busy }),
|
|
1878
|
+
drive?.label && /* @__PURE__ */ jsx(Badge, { variant: drive.tone, title: drive.title, children: drive.label })
|
|
1872
1879
|
] })
|
|
1873
1880
|
] }),
|
|
1874
1881
|
/* @__PURE__ */ jsxs(Menu.Root, { children: [
|
|
@@ -1880,6 +1887,8 @@ const FileCard = ({ file, onAction, busy }) => {
|
|
|
1880
1887
|
/* @__PURE__ */ jsx(Menu.Item, { onSelect: () => onAction("replace", file), children: "Reemplazar" }),
|
|
1881
1888
|
/* @__PURE__ */ jsx(Menu.Item, { onSelect: () => onAction("move", file), children: "Mover a…" }),
|
|
1882
1889
|
canRemoveBackground(file) && /* @__PURE__ */ jsx(Menu.Item, { onSelect: () => onAction("removeBackground", file), disabled: !!busy, children: "Quitar fondo" }),
|
|
1890
|
+
drive?.action === "mark" && /* @__PURE__ */ jsx(Menu.Item, { onSelect: () => onAction("syncDrive", file), children: "Sincronizar con Drive" }),
|
|
1891
|
+
drive?.action === "unmark" && /* @__PURE__ */ jsx(Menu.Item, { onSelect: () => onAction("unsyncDrive", file), children: "Dejar de sincronizar" }),
|
|
1883
1892
|
/* @__PURE__ */ jsx(Menu.Item, { onSelect: () => onAction("delete", file), variant: "danger", children: "Eliminar" })
|
|
1884
1893
|
] })
|
|
1885
1894
|
] })
|
|
@@ -1888,10 +1897,255 @@ const FileCard = ({ file, onAction, busy }) => {
|
|
|
1888
1897
|
}
|
|
1889
1898
|
);
|
|
1890
1899
|
};
|
|
1900
|
+
const BASE$1 = "/strapi-content-generator-gemini/drive";
|
|
1901
|
+
const FOLDER = "application/vnd.google-apps.folder";
|
|
1902
|
+
const NATIVE_TYPES = {
|
|
1903
|
+
"application/vnd.google-apps.document": "Documento de Google",
|
|
1904
|
+
"application/vnd.google-apps.spreadsheet": "Hoja de cálculo de Google",
|
|
1905
|
+
"application/vnd.google-apps.presentation": "Presentación de Google",
|
|
1906
|
+
"application/vnd.google-apps.form": "Formulario de Google",
|
|
1907
|
+
"application/vnd.google-apps.drawing": "Dibujo de Google",
|
|
1908
|
+
"application/vnd.google-apps.shortcut": "Acceso directo"
|
|
1909
|
+
};
|
|
1910
|
+
const typeLabel = (mime) => {
|
|
1911
|
+
if (mime === FOLDER) return "Carpeta";
|
|
1912
|
+
if (NATIVE_TYPES[mime]) return NATIVE_TYPES[mime];
|
|
1913
|
+
if (mime.startsWith("application/vnd.google-apps.")) return "Archivo de Google";
|
|
1914
|
+
if (mime === "application/pdf") return "PDF";
|
|
1915
|
+
if (mime.startsWith("image/")) return "Imagen";
|
|
1916
|
+
if (mime.startsWith("video/")) return "Video";
|
|
1917
|
+
if (mime.startsWith("audio/")) return "Audio";
|
|
1918
|
+
return "Archivo";
|
|
1919
|
+
};
|
|
1920
|
+
const TypeIcon = ({ mime }) => mime === FOLDER ? /* @__PURE__ */ jsx(Folder, { width: 16, height: 16, "aria-hidden": true }) : mime === "application/pdf" ? /* @__PURE__ */ jsx(FilePdf, { width: 16, height: 16, "aria-hidden": true }) : /* @__PURE__ */ jsx(File$1, { width: 16, height: 16, "aria-hidden": true });
|
|
1921
|
+
const formatDate = (iso) => iso ? new Date(iso).toLocaleString("es-MX", { dateStyle: "medium", timeStyle: "short", timeZone: "America/Mexico_City" }) : "—";
|
|
1922
|
+
const confirmText = (item, count) => {
|
|
1923
|
+
const parts = [`«${item.name}» irá a la papelera de Drive, donde se puede recuperar durante 30 días.`];
|
|
1924
|
+
if (item.mimeType === FOLDER && count) {
|
|
1925
|
+
const total = count.files + count.folders;
|
|
1926
|
+
parts.push(
|
|
1927
|
+
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.`
|
|
1928
|
+
);
|
|
1929
|
+
}
|
|
1930
|
+
if (item.synced || count?.synced) parts.push("Está sincronizado: también se borrará de la Biblioteca del CMS.");
|
|
1931
|
+
return parts.join(" ");
|
|
1932
|
+
};
|
|
1933
|
+
const serverMessage$1 = (error, fallback) => error?.response?.data?.error?.message ?? (error instanceof Error && error.message ? error.message : fallback);
|
|
1934
|
+
const useDriveBrowserApi = () => {
|
|
1935
|
+
const { get, del } = useFetchClient();
|
|
1936
|
+
return useMemo(
|
|
1937
|
+
() => ({
|
|
1938
|
+
browse: async (folderId, pageToken) => {
|
|
1939
|
+
try {
|
|
1940
|
+
return (await get(`${BASE$1}/folders/${encodeURIComponent(folderId)}`, { params: pageToken ? { pageToken } : {} })).data;
|
|
1941
|
+
} catch (error) {
|
|
1942
|
+
throw new Error(serverMessage$1(error, "No se pudo leer Google Drive."));
|
|
1943
|
+
}
|
|
1944
|
+
},
|
|
1945
|
+
count: async (id) => (await get(`${BASE$1}/items/${encodeURIComponent(id)}/count`)).data,
|
|
1946
|
+
trash: async (id) => {
|
|
1947
|
+
try {
|
|
1948
|
+
await del(`${BASE$1}/items/${encodeURIComponent(id)}`);
|
|
1949
|
+
} catch (error) {
|
|
1950
|
+
throw new Error(serverMessage$1(error, "No se pudo mandar a la papelera."));
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
}),
|
|
1954
|
+
[get, del]
|
|
1955
|
+
);
|
|
1956
|
+
};
|
|
1957
|
+
const DriveTab = ({ api: injected, notify }) => {
|
|
1958
|
+
const fallbackApi = useDriveBrowserApi();
|
|
1959
|
+
const api = injected ?? fallbackApi;
|
|
1960
|
+
const { toggleNotification } = useNotification();
|
|
1961
|
+
const say = notify ?? ((type, message) => toggleNotification({ type, message }));
|
|
1962
|
+
const [folderId, setFolderId] = useState("root");
|
|
1963
|
+
const [page, setPage] = useState(null);
|
|
1964
|
+
const [error, setError] = useState(null);
|
|
1965
|
+
const [loading2, setLoading] = useState(true);
|
|
1966
|
+
const [loadingMore, setLoadingMore] = useState(false);
|
|
1967
|
+
const [doomed, setDoomed] = useState(null);
|
|
1968
|
+
const load = useCallback(async () => {
|
|
1969
|
+
setLoading(true);
|
|
1970
|
+
setError(null);
|
|
1971
|
+
try {
|
|
1972
|
+
setPage(await api.browse(folderId));
|
|
1973
|
+
} catch (err) {
|
|
1974
|
+
setError(err instanceof Error ? err.message : "No se pudo leer Google Drive.");
|
|
1975
|
+
} finally {
|
|
1976
|
+
setLoading(false);
|
|
1977
|
+
}
|
|
1978
|
+
}, [api, folderId]);
|
|
1979
|
+
useEffect(() => {
|
|
1980
|
+
load();
|
|
1981
|
+
}, [load]);
|
|
1982
|
+
const more = async () => {
|
|
1983
|
+
if (!page?.nextPageToken) return;
|
|
1984
|
+
setLoadingMore(true);
|
|
1985
|
+
try {
|
|
1986
|
+
const next = await api.browse(folderId, page.nextPageToken);
|
|
1987
|
+
setPage({ ...next, items: [...page.items, ...next.items] });
|
|
1988
|
+
} finally {
|
|
1989
|
+
setLoadingMore(false);
|
|
1990
|
+
}
|
|
1991
|
+
};
|
|
1992
|
+
const askDelete = async (item) => {
|
|
1993
|
+
setDoomed({ item, count: null });
|
|
1994
|
+
if (item.mimeType === FOLDER) {
|
|
1995
|
+
try {
|
|
1996
|
+
const count = await api.count(item.id);
|
|
1997
|
+
setDoomed((d) => d?.item.id === item.id ? { item, count } : d);
|
|
1998
|
+
} catch {
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
};
|
|
2002
|
+
const confirmDelete = async () => {
|
|
2003
|
+
if (!doomed) return;
|
|
2004
|
+
const { item } = doomed;
|
|
2005
|
+
setDoomed(null);
|
|
2006
|
+
try {
|
|
2007
|
+
await api.trash(item.id);
|
|
2008
|
+
say("success", `«${item.name}» se mandó a la papelera de Drive.`);
|
|
2009
|
+
await load();
|
|
2010
|
+
} catch (err) {
|
|
2011
|
+
say("danger", err instanceof Error ? err.message : "No se pudo mandar a la papelera.");
|
|
2012
|
+
}
|
|
2013
|
+
};
|
|
2014
|
+
if (error) return /* @__PURE__ */ jsx(EmptyNote, { children: error });
|
|
2015
|
+
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
2016
|
+
/* @__PURE__ */ jsxs(Flex, { "data-testid": "drive-breadcrumb", gap: 1, alignItems: "center", wrap: "wrap", paddingBottom: 4, children: [
|
|
2017
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "S", onClick: () => setFolderId("root"), children: "Mi unidad" }),
|
|
2018
|
+
(page?.path ?? []).map((p, i, all) => /* @__PURE__ */ jsxs(Flex, { gap: 1, alignItems: "center", children: [
|
|
2019
|
+
/* @__PURE__ */ jsx(ChevronRight, { width: 10, height: 10, "aria-hidden": true }),
|
|
2020
|
+
i === all.length - 1 ? /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", children: p.name }) : /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "S", onClick: () => setFolderId(p.id), children: p.name })
|
|
2021
|
+
] }, p.id))
|
|
2022
|
+
] }),
|
|
2023
|
+
loading2 ? /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsx(Loader, { children: "Cargando Google Drive…" }) }) : !page?.items.length ? /* @__PURE__ */ jsx(EmptyNote, { children: "Esta carpeta de Drive está vacía." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2024
|
+
/* @__PURE__ */ jsxs(Table, { colCount: 6, rowCount: page.items.length + 1, children: [
|
|
2025
|
+
/* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsxs(Tr, { children: [
|
|
2026
|
+
/* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Nombre" }) }),
|
|
2027
|
+
/* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Tipo" }) }),
|
|
2028
|
+
/* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Modificado" }) }),
|
|
2029
|
+
/* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Tamaño" }) }),
|
|
2030
|
+
/* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Estado" }) }),
|
|
2031
|
+
/* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(VisuallyHidden, { children: "Acciones" }) })
|
|
2032
|
+
] }) }),
|
|
2033
|
+
/* @__PURE__ */ jsx(Tbody, { children: page.items.map((item) => /* @__PURE__ */ jsxs(Tr, { children: [
|
|
2034
|
+
/* @__PURE__ */ jsx(Td, { style: { maxWidth: 420, whiteSpace: "normal" }, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
|
|
2035
|
+
/* @__PURE__ */ jsx(TypeIcon, { mime: item.mimeType }),
|
|
2036
|
+
item.mimeType === FOLDER ? /* @__PURE__ */ jsx(TextButton, { onClick: () => setFolderId(item.id), style: { textAlign: "left", height: "auto" }, children: /* @__PURE__ */ jsx(Typography, { fontWeight: "semiBold", textColor: "neutral800", style: { overflowWrap: "anywhere" }, children: item.name }) }) : /* @__PURE__ */ jsx(Typography, { textColor: "neutral800", style: { overflowWrap: "anywhere" }, children: item.name })
|
|
2037
|
+
] }) }),
|
|
2038
|
+
/* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral700", children: typeLabel(item.mimeType) }) }),
|
|
2039
|
+
/* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral700", style: { whiteSpace: "nowrap" }, children: formatDate(item.modifiedTime) }) }),
|
|
2040
|
+
/* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral700", children: item.size === null ? "—" : formatSize(item.size / 1e3) }) }),
|
|
2041
|
+
/* @__PURE__ */ jsx(Td, { children: item.synced && /* @__PURE__ */ jsx(Badge, { variant: "success", children: "Sincronizado" }) }),
|
|
2042
|
+
/* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsxs(Flex, { gap: 1, justifyContent: "end", children: [
|
|
2043
|
+
item.webViewLink && /* @__PURE__ */ jsx(
|
|
2044
|
+
LinkButton,
|
|
2045
|
+
{
|
|
2046
|
+
variant: "tertiary",
|
|
2047
|
+
size: "S",
|
|
2048
|
+
href: item.webViewLink,
|
|
2049
|
+
target: "_blank",
|
|
2050
|
+
rel: "noreferrer noopener",
|
|
2051
|
+
endIcon: /* @__PURE__ */ jsx(ExternalLink, {}),
|
|
2052
|
+
children: "Abrir en Drive"
|
|
2053
|
+
}
|
|
2054
|
+
),
|
|
2055
|
+
/* @__PURE__ */ jsx(IconButton, { label: `Eliminar ${item.name}`, variant: "ghost", onClick: () => askDelete(item), children: /* @__PURE__ */ jsx(Trash, {}) })
|
|
2056
|
+
] }) })
|
|
2057
|
+
] }, item.id)) })
|
|
2058
|
+
] }),
|
|
2059
|
+
page.nextPageToken && /* @__PURE__ */ jsx(Flex, { justifyContent: "center", paddingTop: 4, children: /* @__PURE__ */ jsx(Button, { variant: "tertiary", onClick: more, loading: loadingMore, children: "Mostrar más" }) })
|
|
2060
|
+
] }),
|
|
2061
|
+
/* @__PURE__ */ jsx(
|
|
2062
|
+
ConfirmDialog,
|
|
2063
|
+
{
|
|
2064
|
+
open: !!doomed,
|
|
2065
|
+
title: "¿Mandar a la papelera de Drive?",
|
|
2066
|
+
confirmLabel: "Mandar a la papelera",
|
|
2067
|
+
onClose: () => setDoomed(null),
|
|
2068
|
+
onConfirm: confirmDelete,
|
|
2069
|
+
children: doomed ? confirmText(doomed.item, doomed.count) : ""
|
|
2070
|
+
}
|
|
2071
|
+
)
|
|
2072
|
+
] });
|
|
2073
|
+
};
|
|
2074
|
+
const BASE = "/strapi-content-generator-gemini/drive";
|
|
2075
|
+
const POLL_MS = 1e4;
|
|
2076
|
+
const LABELS = {
|
|
2077
|
+
synced: { label: "Drive", tone: "success" },
|
|
2078
|
+
pending: { label: "Drive: pendiente", tone: "neutral" },
|
|
2079
|
+
error: { label: "Drive: error", tone: "danger" },
|
|
2080
|
+
skipped: { label: "Drive: omitido", tone: "warning" }
|
|
2081
|
+
};
|
|
2082
|
+
const driveView = (links, key, parentInside) => {
|
|
2083
|
+
if (!links?.connected) return null;
|
|
2084
|
+
const item = links.items[key];
|
|
2085
|
+
if (item?.inside) {
|
|
2086
|
+
return { ...LABELS[item.status], title: item.error ?? void 0, action: item.root ? "unmark" : null };
|
|
2087
|
+
}
|
|
2088
|
+
if (parentInside) return { ...LABELS.pending, action: null };
|
|
2089
|
+
return { label: null, tone: "neutral", action: "mark" };
|
|
2090
|
+
};
|
|
2091
|
+
const serverMessage = (error, fallback) => error?.response?.data?.error?.message ?? fallback;
|
|
2092
|
+
const useDriveSync = (watching = false) => {
|
|
2093
|
+
const { get, post, del } = useFetchClient();
|
|
2094
|
+
const [links, setLinks] = useState(null);
|
|
2095
|
+
const refresh = useCallback(async () => {
|
|
2096
|
+
try {
|
|
2097
|
+
const { data } = await get(`${BASE}/links`);
|
|
2098
|
+
setLinks(data);
|
|
2099
|
+
} catch {
|
|
2100
|
+
setLinks(null);
|
|
2101
|
+
}
|
|
2102
|
+
}, [get]);
|
|
2103
|
+
useEffect(() => {
|
|
2104
|
+
refresh();
|
|
2105
|
+
}, [refresh]);
|
|
2106
|
+
const busy = !!links?.connected && (watching || links.pending > 0 || Object.values(links.items).some((i) => i.status === "pending"));
|
|
2107
|
+
useEffect(() => {
|
|
2108
|
+
if (!busy) return;
|
|
2109
|
+
const timer = setInterval(refresh, POLL_MS);
|
|
2110
|
+
return () => clearInterval(timer);
|
|
2111
|
+
}, [busy, refresh]);
|
|
2112
|
+
useEffect(() => {
|
|
2113
|
+
const onVisible = () => document.visibilityState === "visible" && refresh();
|
|
2114
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
2115
|
+
return () => document.removeEventListener("visibilitychange", onVisible);
|
|
2116
|
+
}, [refresh]);
|
|
2117
|
+
return useMemo(
|
|
2118
|
+
() => ({
|
|
2119
|
+
links,
|
|
2120
|
+
refresh,
|
|
2121
|
+
async mark(cmsType, cmsId) {
|
|
2122
|
+
try {
|
|
2123
|
+
await post(`${BASE}/links`, { cmsType, cmsId });
|
|
2124
|
+
} catch (error) {
|
|
2125
|
+
throw new Error(serverMessage(error, "No se pudo sincronizar con Drive."));
|
|
2126
|
+
} finally {
|
|
2127
|
+
await refresh();
|
|
2128
|
+
}
|
|
2129
|
+
},
|
|
2130
|
+
async unmark(cmsType, cmsId) {
|
|
2131
|
+
try {
|
|
2132
|
+
await del(`${BASE}/links/${cmsType}/${cmsId}`);
|
|
2133
|
+
} catch (error) {
|
|
2134
|
+
throw new Error(serverMessage(error, "No se pudo dejar de sincronizar."));
|
|
2135
|
+
} finally {
|
|
2136
|
+
await refresh();
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
}),
|
|
2140
|
+
[links, refresh, post, del]
|
|
2141
|
+
);
|
|
2142
|
+
};
|
|
1891
2143
|
const status = (err) => err?.response?.status ?? err?.status;
|
|
1892
2144
|
const LibraryTab = () => {
|
|
1893
2145
|
const c = useThemeColors();
|
|
1894
2146
|
const { api, notify } = useLibraryApi();
|
|
2147
|
+
const [drivePending, setDrivePending] = useState(false);
|
|
2148
|
+
const drive = useDriveSync(drivePending);
|
|
1895
2149
|
const [tree, setTree] = useState([]);
|
|
1896
2150
|
const [counts, setCounts] = useState({});
|
|
1897
2151
|
const [current, setCurrent] = useState(null);
|
|
@@ -1935,7 +2189,20 @@ const LibraryTab = () => {
|
|
|
1935
2189
|
useEffect(() => {
|
|
1936
2190
|
loadFiles();
|
|
1937
2191
|
}, [loadFiles]);
|
|
1938
|
-
const refresh = () => Promise.all([loadTree(), loadFiles()]);
|
|
2192
|
+
const refresh = () => Promise.all([loadTree(), loadFiles(), drive.refresh()]);
|
|
2193
|
+
const toggleDrive = async (mode, cmsType, cmsId) => {
|
|
2194
|
+
try {
|
|
2195
|
+
if (mode === "mark") {
|
|
2196
|
+
await drive.mark(cmsType, cmsId);
|
|
2197
|
+
notify("success", "Se sincronizará con Drive en unos segundos.");
|
|
2198
|
+
} else {
|
|
2199
|
+
await drive.unmark(cmsType, cmsId);
|
|
2200
|
+
notify("success", "Dejó de sincronizarse. No se borró nada en Drive ni aquí.");
|
|
2201
|
+
}
|
|
2202
|
+
} catch (err) {
|
|
2203
|
+
notify("danger", err instanceof Error ? err.message : "No se pudo completar la operación con Drive.");
|
|
2204
|
+
}
|
|
2205
|
+
};
|
|
1939
2206
|
const run = async (action, ok) => {
|
|
1940
2207
|
try {
|
|
1941
2208
|
await action();
|
|
@@ -1992,12 +2259,24 @@ const LibraryTab = () => {
|
|
|
1992
2259
|
if (action === "rename") return setDialog({ kind: "renameFile", file });
|
|
1993
2260
|
if (action === "move") return setDialog({ kind: "moveFile", file });
|
|
1994
2261
|
if (action === "delete") return setDialog({ kind: "deleteFile", file });
|
|
2262
|
+
if (action === "syncDrive") return toggleDrive("mark", "file", file.id);
|
|
2263
|
+
if (action === "unsyncDrive") return toggleDrive("unmark", "file", file.id);
|
|
1995
2264
|
};
|
|
1996
2265
|
const validateFolder = (parent, except) => (name) => cleanFolderName(name) !== except && nameTaken(tree, parent, name) ? "Ya existe una carpeta con ese nombre aquí." : null;
|
|
2266
|
+
const pendingShown = (() => {
|
|
2267
|
+
const inside = current !== null && !!drive.links?.items[`folder:${current}`]?.inside;
|
|
2268
|
+
const isPending = (key) => driveView(drive.links, key, inside)?.label === "Drive: pendiente";
|
|
2269
|
+
return files.some((f) => isPending(`file:${f.id}`)) || childrenOf(tree, current).some((f) => isPending(`folder:${f.id}`));
|
|
2270
|
+
})();
|
|
2271
|
+
useEffect(() => {
|
|
2272
|
+
setDrivePending(pendingShown);
|
|
2273
|
+
}, [pendingShown]);
|
|
1997
2274
|
if (forbidden) {
|
|
1998
2275
|
return /* @__PURE__ */ jsx(EmptyNote, { children: "Para usar la biblioteca necesitas permiso de la Media Library de Strapi. Pídelo a un administrador." });
|
|
1999
2276
|
}
|
|
2000
2277
|
const subfolders = childrenOf(tree, current);
|
|
2278
|
+
const currentInside = current !== null && !!drive.links?.items[`folder:${current}`]?.inside;
|
|
2279
|
+
const folderDrive = current !== null ? driveView(drive.links, `folder:${current}`, !!(folder && path.length > 1 && drive.links?.items[`folder:${path[path.length - 2].id}`]?.inside)) : null;
|
|
2001
2280
|
return /* @__PURE__ */ jsxs(Flex, { gap: 4, alignItems: "stretch", children: [
|
|
2002
2281
|
/* @__PURE__ */ jsx(Box, { width: "280px", shrink: 0, background: "neutral0", padding: 3, hasRadius: true, style: { maxHeight: "calc(100vh - 220px)", overflowY: "auto" }, children: /* @__PURE__ */ jsx(
|
|
2003
2282
|
FolderTree,
|
|
@@ -2044,6 +2323,9 @@ const LibraryTab = () => {
|
|
|
2044
2323
|
] }, f.id))
|
|
2045
2324
|
] }),
|
|
2046
2325
|
/* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
|
|
2326
|
+
folder && folderDrive?.label && /* @__PURE__ */ jsx(Badge, { variant: folderDrive.tone, title: folderDrive.title, children: folderDrive.label }),
|
|
2327
|
+
folder && folderDrive?.action === "mark" && /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => toggleDrive("mark", "folder", folder.id), children: "Sincronizar con Drive" }),
|
|
2328
|
+
folder && folderDrive?.action === "unmark" && /* @__PURE__ */ jsx(Button, { variant: "tertiary", onClick: () => toggleDrive("unmark", "folder", folder.id), children: "Dejar de sincronizar" }),
|
|
2047
2329
|
folder && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2048
2330
|
/* @__PURE__ */ jsx(Button, { variant: "tertiary", startIcon: /* @__PURE__ */ jsx(Pencil, {}), onClick: () => setDialog({ kind: "renameFolder" }), children: "Renombrar carpeta" }),
|
|
2049
2331
|
/* @__PURE__ */ jsx(Button, { variant: "danger-light", startIcon: /* @__PURE__ */ jsx(Trash, {}), onClick: () => setDialog({ kind: "deleteFolder" }), children: "Eliminar carpeta" })
|
|
@@ -2068,11 +2350,15 @@ const LibraryTab = () => {
|
|
|
2068
2350
|
children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
|
|
2069
2351
|
/* @__PURE__ */ jsx(Folder, { width: 16, height: 16, "aria-hidden": true }),
|
|
2070
2352
|
/* @__PURE__ */ jsx(Typography, { ellipsis: true, style: { flex: 1 }, children: f.name }),
|
|
2353
|
+
(() => {
|
|
2354
|
+
const view = driveView(drive.links, `folder:${f.id}`, currentInside);
|
|
2355
|
+
return view?.label ? /* @__PURE__ */ jsx(Badge, { size: "S", variant: view.tone, title: view.title, children: view.label }) : null;
|
|
2356
|
+
})(),
|
|
2071
2357
|
/* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: counts[f.id]?.files ?? 0 })
|
|
2072
2358
|
] })
|
|
2073
2359
|
}
|
|
2074
2360
|
) }, f.id)) }),
|
|
2075
|
-
loading2 ? /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsx(Loader, { children: "Cargando archivos…" }) }) : files.length === 0 ? /* @__PURE__ */ jsx(EmptyNote, { children: subfolders.length ? "No hay archivos sueltos en esta carpeta." : "Carpeta vacía. Arrastra archivos aquí o usa «Subir archivos»." }) : /* @__PURE__ */ jsx(Grid.Root, { gap: 4, children: files.map((f) => /* @__PURE__ */ jsx(Grid.Item, { col: 3, m: 4, s: 6, xs: 12, children: /* @__PURE__ */ jsx(FileCard, { file: f, onAction: onFileAction, busy: bgBusy[f.id] }) }, f.id)) }),
|
|
2361
|
+
loading2 ? /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsx(Loader, { children: "Cargando archivos…" }) }) : files.length === 0 ? /* @__PURE__ */ jsx(EmptyNote, { children: subfolders.length ? "No hay archivos sueltos en esta carpeta." : "Carpeta vacía. Arrastra archivos aquí o usa «Subir archivos»." }) : /* @__PURE__ */ jsx(Grid.Root, { gap: 4, children: files.map((f) => /* @__PURE__ */ jsx(Grid.Item, { col: 3, m: 4, s: 6, xs: 12, children: /* @__PURE__ */ jsx(FileCard, { file: f, onAction: onFileAction, busy: bgBusy[f.id], drive: driveView(drive.links, `file:${f.id}`, currentInside) }) }, f.id)) }),
|
|
2076
2362
|
dragOver && /* @__PURE__ */ jsx(Flex, { position: "absolute", justifyContent: "center", background: "primary100", hasRadius: true, style: { inset: 0, border: `2px dashed ${c.primary600}`, opacity: 0.95 }, children: /* @__PURE__ */ jsxs(Typography, { variant: "beta", textColor: "primary700", children: [
|
|
2077
2363
|
"Suelta para subir a «",
|
|
2078
2364
|
folder?.name ?? ROOT_LABEL,
|
|
@@ -2184,17 +2470,6 @@ const LibraryTab = () => {
|
|
|
2184
2470
|
/* @__PURE__ */ jsx(RemoveBackgroundDialog, { open: !!bgTarget, src: bgTarget?.src ?? "", onClose: () => setBgTarget(null), onApply: saveNoBackground })
|
|
2185
2471
|
] });
|
|
2186
2472
|
};
|
|
2187
|
-
const PALETTE = [
|
|
2188
|
-
{ label: "Navy", value: "#1F2A44" },
|
|
2189
|
-
{ label: "Rojo", value: "#E4002B" },
|
|
2190
|
-
{ label: "Teal", value: "#00C7B1" },
|
|
2191
|
-
{ label: "Teal suave", value: "#92CFC7" },
|
|
2192
|
-
{ label: "Amarillo", value: "#FFC844" },
|
|
2193
|
-
{ label: "Amarillo suave", value: "#F8DF8D" },
|
|
2194
|
-
{ label: "Gris", value: "#5B6473" },
|
|
2195
|
-
{ label: "Papel", value: "#EEF1F5" },
|
|
2196
|
-
{ label: "Blanco", value: "#FFFFFF" }
|
|
2197
|
-
];
|
|
2198
2473
|
const NAVY = "#1F2A44";
|
|
2199
2474
|
const RED$1 = "#E4002B";
|
|
2200
2475
|
const TEAL$1 = "#00C7B1";
|
|
@@ -2765,6 +3040,15 @@ const editorReducer = (state, action) => {
|
|
|
2765
3040
|
}
|
|
2766
3041
|
}
|
|
2767
3042
|
};
|
|
3043
|
+
const ORIGIN_URL = "/strapi-content-generator-gemini/drive/origin";
|
|
3044
|
+
const recordTemplateOrigin = async (post, files, template) => {
|
|
3045
|
+
for (const file of files) {
|
|
3046
|
+
try {
|
|
3047
|
+
await post(ORIGIN_URL, { fileId: file.id, templateDocumentId: template.documentId ?? template.id, templateName: template.name });
|
|
3048
|
+
} catch {
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
};
|
|
2768
3052
|
const useEditorIO = () => {
|
|
2769
3053
|
const { post } = useFetchClient();
|
|
2770
3054
|
const { toggleNotification } = useNotification();
|
|
@@ -2797,7 +3081,8 @@ const useEditorIO = () => {
|
|
|
2797
3081
|
if (!node) return false;
|
|
2798
3082
|
try {
|
|
2799
3083
|
const blob = await renderPiece(node, template, "png");
|
|
2800
|
-
await api.saveBlob(blob, /\.png$/i.test(name) ? name : `${name}.png`, folderId);
|
|
3084
|
+
const saved = await api.saveBlob(blob, /\.png$/i.test(name) ? name : `${name}.png`, folderId);
|
|
3085
|
+
await recordTemplateOrigin((u, body) => post(u, body), saved, template);
|
|
2801
3086
|
toggleNotification({ type: "success", message: "Pieza guardada en la biblioteca." });
|
|
2802
3087
|
return true;
|
|
2803
3088
|
} catch {
|
|
@@ -2805,7 +3090,7 @@ const useEditorIO = () => {
|
|
|
2805
3090
|
return false;
|
|
2806
3091
|
}
|
|
2807
3092
|
},
|
|
2808
|
-
[api, toggleNotification]
|
|
3093
|
+
[api, post, toggleNotification]
|
|
2809
3094
|
);
|
|
2810
3095
|
return { loadImage: loadImage2, exportAs, saveToLibrary };
|
|
2811
3096
|
};
|
|
@@ -3934,44 +4219,63 @@ const SaveTemplateDialog = ({ open, defaultName, defaultFolder, folders, onClose
|
|
|
3934
4219
|
] })
|
|
3935
4220
|
] }) });
|
|
3936
4221
|
};
|
|
4222
|
+
const parseHex = (raw) => {
|
|
4223
|
+
const hex = raw.trim().replace(/^#/, "");
|
|
4224
|
+
if (/^[0-9a-f]{3}$/i.test(hex)) return `#${[...hex].map((c) => c + c).join("")}`.toUpperCase();
|
|
4225
|
+
if (/^[0-9a-f]{6}$/i.test(hex)) return `#${hex}`.toUpperCase();
|
|
4226
|
+
return null;
|
|
4227
|
+
};
|
|
3937
4228
|
const ColorField = ({ label, value, onChange }) => {
|
|
3938
4229
|
const tc = useThemeColors();
|
|
4230
|
+
const current = parseHex(value ?? "") ?? "#000000";
|
|
4231
|
+
const [draft, setDraft] = useState(current);
|
|
4232
|
+
useEffect(() => setDraft((d) => parseHex(d) === current ? d : current), [current]);
|
|
4233
|
+
const apply = (text) => {
|
|
4234
|
+
const hex = parseHex(text);
|
|
4235
|
+
if (hex && hex !== current) onChange(hex);
|
|
4236
|
+
};
|
|
4237
|
+
const typeHex = (text) => {
|
|
4238
|
+
setDraft(text);
|
|
4239
|
+
if (text.trim().replace(/^#/, "").length === 6) apply(text);
|
|
4240
|
+
};
|
|
4241
|
+
const leave = () => {
|
|
4242
|
+
apply(draft);
|
|
4243
|
+
setDraft(parseHex(draft) ?? current);
|
|
4244
|
+
};
|
|
3939
4245
|
return /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 1, children: [
|
|
3940
4246
|
/* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", children: label }),
|
|
3941
|
-
/* @__PURE__ */ jsxs(Flex, { gap:
|
|
3942
|
-
PALETTE.map((c) => {
|
|
3943
|
-
const active = value?.toUpperCase() === c.value;
|
|
3944
|
-
return /* @__PURE__ */ jsx(
|
|
3945
|
-
Box,
|
|
3946
|
-
{
|
|
3947
|
-
tag: "button",
|
|
3948
|
-
color: "neutral800",
|
|
3949
|
-
type: "button",
|
|
3950
|
-
"aria-label": `${label}: ${c.label}`,
|
|
3951
|
-
"aria-pressed": active,
|
|
3952
|
-
title: c.label,
|
|
3953
|
-
onClick: () => onChange(c.value),
|
|
3954
|
-
style: {
|
|
3955
|
-
width: 22,
|
|
3956
|
-
height: 22,
|
|
3957
|
-
borderRadius: 4,
|
|
3958
|
-
cursor: "pointer",
|
|
3959
|
-
background: c.value,
|
|
3960
|
-
border: active ? `2px solid ${tc.primary600}` : `1px solid ${tc.neutral200}`,
|
|
3961
|
-
padding: 0
|
|
3962
|
-
}
|
|
3963
|
-
},
|
|
3964
|
-
c.value
|
|
3965
|
-
);
|
|
3966
|
-
}),
|
|
4247
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
|
|
3967
4248
|
/* @__PURE__ */ jsx(
|
|
3968
4249
|
"input",
|
|
3969
4250
|
{
|
|
3970
4251
|
type: "color",
|
|
3971
|
-
"aria-label": `${label}
|
|
3972
|
-
|
|
4252
|
+
"aria-label": `${label}: selector`,
|
|
4253
|
+
title: "Abrir selector de color",
|
|
4254
|
+
value: current.toLowerCase(),
|
|
3973
4255
|
onChange: (e) => onChange(e.target.value.toUpperCase()),
|
|
3974
|
-
style: {
|
|
4256
|
+
style: {
|
|
4257
|
+
flexShrink: 0,
|
|
4258
|
+
width: 32,
|
|
4259
|
+
height: 32,
|
|
4260
|
+
padding: 2,
|
|
4261
|
+
borderRadius: 4,
|
|
4262
|
+
cursor: "pointer",
|
|
4263
|
+
border: `1px solid ${tc.neutral200}`,
|
|
4264
|
+
background: tc.neutral0
|
|
4265
|
+
}
|
|
4266
|
+
}
|
|
4267
|
+
),
|
|
4268
|
+
/* @__PURE__ */ jsx(
|
|
4269
|
+
TextInput,
|
|
4270
|
+
{
|
|
4271
|
+
"aria-label": `${label}: hexadecimal`,
|
|
4272
|
+
size: "S",
|
|
4273
|
+
value: draft,
|
|
4274
|
+
placeholder: "#1F2A44",
|
|
4275
|
+
maxLength: 7,
|
|
4276
|
+
spellCheck: false,
|
|
4277
|
+
onChange: (e) => typeHex(e.target.value),
|
|
4278
|
+
onBlur: leave
|
|
3975
4279
|
}
|
|
3976
4280
|
)
|
|
3977
4281
|
] })
|
|
@@ -5231,7 +5535,8 @@ const TemplatesTab = () => {
|
|
|
5231
5535
|
const STUDIO_TABS = [
|
|
5232
5536
|
{ value: "generator", label: "Generador" },
|
|
5233
5537
|
{ value: "templates", label: "Plantillas" },
|
|
5234
|
-
{ value: "library", label: "Imágenes y documentos" }
|
|
5538
|
+
{ value: "library", label: "Imágenes y documentos" },
|
|
5539
|
+
{ value: "drive", label: "Google Drive" }
|
|
5235
5540
|
];
|
|
5236
5541
|
const PANEL = { height: "100%", overflowY: "auto" };
|
|
5237
5542
|
const isStudioTab = (v) => STUDIO_TABS.some((t) => t.value === v);
|
|
@@ -5259,7 +5564,8 @@ const ImageStudioTabs = () => {
|
|
|
5259
5564
|
/* @__PURE__ */ jsxs(Box, { flex: "1", style: { minHeight: 0 }, children: [
|
|
5260
5565
|
/* @__PURE__ */ jsx(Tabs.Content, { value: "generator", style: PANEL, children: /* @__PURE__ */ jsx(Box, { padding: 6, children: /* @__PURE__ */ jsx(GeneratorTab, {}) }) }),
|
|
5261
5566
|
/* @__PURE__ */ jsx(Tabs.Content, { value: "templates", style: PANEL, children: /* @__PURE__ */ jsx(TemplatesTab, {}) }),
|
|
5262
|
-
/* @__PURE__ */ jsx(Tabs.Content, { value: "library", style: PANEL, children: /* @__PURE__ */ jsx(Box, { padding: 6, children: /* @__PURE__ */ jsx(LibraryTab, {}) }) })
|
|
5567
|
+
/* @__PURE__ */ jsx(Tabs.Content, { value: "library", style: PANEL, children: /* @__PURE__ */ jsx(Box, { padding: 6, children: /* @__PURE__ */ jsx(LibraryTab, {}) }) }),
|
|
5568
|
+
/* @__PURE__ */ jsx(Tabs.Content, { value: "drive", style: PANEL, children: /* @__PURE__ */ jsx(Box, { padding: 6, children: tab === "drive" && /* @__PURE__ */ jsx(DriveTab, {}) }) })
|
|
5263
5569
|
] })
|
|
5264
5570
|
]
|
|
5265
5571
|
}
|