@interware/content-generator 0.7.2 → 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-CTh6owSM.mjs → App-DD1MzaWm.mjs} +2 -2
- package/dist/_chunks/{App-ugsCmlsC.js → App-DspaMfyW.js} +2 -2
- package/dist/_chunks/{ImageStudioTabs-Umf3GODW.mjs → ImageStudioTabs-DwaLxRjl.mjs} +308 -16
- package/dist/_chunks/{ImageStudioTabs-BFEF9ggK.js → ImageStudioTabs-uXSBkFeu.js} +306 -14
- package/dist/_chunks/{Settings-CdGW2O8h.mjs → Settings-C6TbUrgU.mjs} +170 -2
- package/dist/_chunks/{Settings-Sk78JUBS.js → Settings-ps0o7-fC.js} +169 -1
- package/dist/_chunks/{StudioApp-CrGn-9Gg.mjs → StudioApp-B5I1OfGw.mjs} +2 -2
- package/dist/_chunks/{StudioApp-DoC0z4eh.js → StudioApp-B7kGpqqo.js} +2 -2
- package/dist/_chunks/{index-C2s9Jhcc.js → index-BudahdfX.js} +3 -3
- package/dist/_chunks/{index-D6UAvh53.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/export/templateOrigin.d.ts +10 -0
- package/dist/admin/src/lib/use-cases/drive/index.d.ts +3 -0
- package/dist/server/index.js +1411 -49
- package/dist/server/index.mjs +1409 -47
- 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";
|
|
@@ -1171,7 +1171,7 @@ const closestRatio = (width, height, ratios) => {
|
|
|
1171
1171
|
};
|
|
1172
1172
|
return ratios.reduce((best, r) => Math.abs(value(r) - target) < Math.abs(value(best) - target) ? r : best);
|
|
1173
1173
|
};
|
|
1174
|
-
const BASE = "/strapi-content-generator-gemini/image-studio";
|
|
1174
|
+
const BASE$2 = "/strapi-content-generator-gemini/image-studio";
|
|
1175
1175
|
const toResult = ({ image, mimeType, model, grounding }) => ({
|
|
1176
1176
|
src: `data:${mimeType};base64,${image}`,
|
|
1177
1177
|
model,
|
|
@@ -1183,7 +1183,7 @@ const errorMessage = (err, fallback) => {
|
|
|
1183
1183
|
};
|
|
1184
1184
|
const createGeneratorApi = (client) => ({
|
|
1185
1185
|
async models() {
|
|
1186
|
-
const { data } = await client.get(`${BASE}/models`);
|
|
1186
|
+
const { data } = await client.get(`${BASE$2}/models`);
|
|
1187
1187
|
return data;
|
|
1188
1188
|
},
|
|
1189
1189
|
async generate(p) {
|
|
@@ -1196,7 +1196,7 @@ const createGeneratorApi = (client) => ({
|
|
|
1196
1196
|
form.append("allowPeople", String(p.allowPeople));
|
|
1197
1197
|
form.append("grounding", String(p.grounding));
|
|
1198
1198
|
p.references.forEach((f) => form.append("references", f, f.name));
|
|
1199
|
-
const { data } = await client.post(`${BASE}/generate`, form);
|
|
1199
|
+
const { data } = await client.post(`${BASE$2}/generate`, form);
|
|
1200
1200
|
return toResult(data);
|
|
1201
1201
|
},
|
|
1202
1202
|
async edit(p) {
|
|
@@ -1209,11 +1209,11 @@ const createGeneratorApi = (client) => ({
|
|
|
1209
1209
|
p.references.forEach((f) => form.append("references", f, f.name));
|
|
1210
1210
|
if (p.aspectRatio) form.append("aspectRatio", p.aspectRatio);
|
|
1211
1211
|
form.append("image", new File([blob], `base.${blob.type.split("/")[1] ?? "png"}`, { type: blob.type }));
|
|
1212
|
-
const { data } = await client.post(`${BASE}/edit`, form);
|
|
1212
|
+
const { data } = await client.post(`${BASE$2}/edit`, form);
|
|
1213
1213
|
return toResult(data);
|
|
1214
1214
|
},
|
|
1215
1215
|
async expand(prompt) {
|
|
1216
|
-
const { data } = await client.post(`${BASE}/expand`, { prompt });
|
|
1216
|
+
const { data } = await client.post(`${BASE$2}/expand`, { prompt });
|
|
1217
1217
|
return data.prompt;
|
|
1218
1218
|
}
|
|
1219
1219
|
});
|
|
@@ -1835,7 +1835,7 @@ const isImage = (f) => (f.mime ?? "").startsWith("image/");
|
|
|
1835
1835
|
const canRemoveBackground = (f) => isImage(f) && f.mime !== "image/svg+xml";
|
|
1836
1836
|
const isGenerated = (f) => (f.caption ?? "").startsWith("AI generated content");
|
|
1837
1837
|
const formatSize = (kb = 0) => kb >= 1024 ? `${(kb / 1024).toFixed(1)} MB` : `${Math.round(kb)} KB`;
|
|
1838
|
-
const FileCard = ({ file, onAction, busy }) => {
|
|
1838
|
+
const FileCard = ({ file, onAction, busy, drive }) => {
|
|
1839
1839
|
const c = useThemeColors();
|
|
1840
1840
|
const thumb2 = file.formats?.thumbnail?.url ?? file.formats?.small?.url ?? file.url;
|
|
1841
1841
|
return /* @__PURE__ */ jsxs(
|
|
@@ -1874,7 +1874,8 @@ const FileCard = ({ file, onAction, busy }) => {
|
|
|
1874
1874
|
/* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
|
|
1875
1875
|
/* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: formatSize(file.size) }),
|
|
1876
1876
|
isGenerated(file) && /* @__PURE__ */ jsx(Badge, { children: "Generada" }),
|
|
1877
|
-
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 })
|
|
1878
1879
|
] })
|
|
1879
1880
|
] }),
|
|
1880
1881
|
/* @__PURE__ */ jsxs(Menu.Root, { children: [
|
|
@@ -1886,6 +1887,8 @@ const FileCard = ({ file, onAction, busy }) => {
|
|
|
1886
1887
|
/* @__PURE__ */ jsx(Menu.Item, { onSelect: () => onAction("replace", file), children: "Reemplazar" }),
|
|
1887
1888
|
/* @__PURE__ */ jsx(Menu.Item, { onSelect: () => onAction("move", file), children: "Mover a…" }),
|
|
1888
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" }),
|
|
1889
1892
|
/* @__PURE__ */ jsx(Menu.Item, { onSelect: () => onAction("delete", file), variant: "danger", children: "Eliminar" })
|
|
1890
1893
|
] })
|
|
1891
1894
|
] })
|
|
@@ -1894,10 +1897,255 @@ const FileCard = ({ file, onAction, busy }) => {
|
|
|
1894
1897
|
}
|
|
1895
1898
|
);
|
|
1896
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
|
+
};
|
|
1897
2143
|
const status = (err) => err?.response?.status ?? err?.status;
|
|
1898
2144
|
const LibraryTab = () => {
|
|
1899
2145
|
const c = useThemeColors();
|
|
1900
2146
|
const { api, notify } = useLibraryApi();
|
|
2147
|
+
const [drivePending, setDrivePending] = useState(false);
|
|
2148
|
+
const drive = useDriveSync(drivePending);
|
|
1901
2149
|
const [tree, setTree] = useState([]);
|
|
1902
2150
|
const [counts, setCounts] = useState({});
|
|
1903
2151
|
const [current, setCurrent] = useState(null);
|
|
@@ -1941,7 +2189,20 @@ const LibraryTab = () => {
|
|
|
1941
2189
|
useEffect(() => {
|
|
1942
2190
|
loadFiles();
|
|
1943
2191
|
}, [loadFiles]);
|
|
1944
|
-
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
|
+
};
|
|
1945
2206
|
const run = async (action, ok) => {
|
|
1946
2207
|
try {
|
|
1947
2208
|
await action();
|
|
@@ -1998,12 +2259,24 @@ const LibraryTab = () => {
|
|
|
1998
2259
|
if (action === "rename") return setDialog({ kind: "renameFile", file });
|
|
1999
2260
|
if (action === "move") return setDialog({ kind: "moveFile", file });
|
|
2000
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);
|
|
2001
2264
|
};
|
|
2002
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]);
|
|
2003
2274
|
if (forbidden) {
|
|
2004
2275
|
return /* @__PURE__ */ jsx(EmptyNote, { children: "Para usar la biblioteca necesitas permiso de la Media Library de Strapi. Pídelo a un administrador." });
|
|
2005
2276
|
}
|
|
2006
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;
|
|
2007
2280
|
return /* @__PURE__ */ jsxs(Flex, { gap: 4, alignItems: "stretch", children: [
|
|
2008
2281
|
/* @__PURE__ */ jsx(Box, { width: "280px", shrink: 0, background: "neutral0", padding: 3, hasRadius: true, style: { maxHeight: "calc(100vh - 220px)", overflowY: "auto" }, children: /* @__PURE__ */ jsx(
|
|
2009
2282
|
FolderTree,
|
|
@@ -2050,6 +2323,9 @@ const LibraryTab = () => {
|
|
|
2050
2323
|
] }, f.id))
|
|
2051
2324
|
] }),
|
|
2052
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" }),
|
|
2053
2329
|
folder && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2054
2330
|
/* @__PURE__ */ jsx(Button, { variant: "tertiary", startIcon: /* @__PURE__ */ jsx(Pencil, {}), onClick: () => setDialog({ kind: "renameFolder" }), children: "Renombrar carpeta" }),
|
|
2055
2331
|
/* @__PURE__ */ jsx(Button, { variant: "danger-light", startIcon: /* @__PURE__ */ jsx(Trash, {}), onClick: () => setDialog({ kind: "deleteFolder" }), children: "Eliminar carpeta" })
|
|
@@ -2074,11 +2350,15 @@ const LibraryTab = () => {
|
|
|
2074
2350
|
children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
|
|
2075
2351
|
/* @__PURE__ */ jsx(Folder, { width: 16, height: 16, "aria-hidden": true }),
|
|
2076
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
|
+
})(),
|
|
2077
2357
|
/* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: counts[f.id]?.files ?? 0 })
|
|
2078
2358
|
] })
|
|
2079
2359
|
}
|
|
2080
2360
|
) }, f.id)) }),
|
|
2081
|
-
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)) }),
|
|
2082
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: [
|
|
2083
2363
|
"Suelta para subir a «",
|
|
2084
2364
|
folder?.name ?? ROOT_LABEL,
|
|
@@ -2760,6 +3040,15 @@ const editorReducer = (state, action) => {
|
|
|
2760
3040
|
}
|
|
2761
3041
|
}
|
|
2762
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
|
+
};
|
|
2763
3052
|
const useEditorIO = () => {
|
|
2764
3053
|
const { post } = useFetchClient();
|
|
2765
3054
|
const { toggleNotification } = useNotification();
|
|
@@ -2792,7 +3081,8 @@ const useEditorIO = () => {
|
|
|
2792
3081
|
if (!node) return false;
|
|
2793
3082
|
try {
|
|
2794
3083
|
const blob = await renderPiece(node, template, "png");
|
|
2795
|
-
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);
|
|
2796
3086
|
toggleNotification({ type: "success", message: "Pieza guardada en la biblioteca." });
|
|
2797
3087
|
return true;
|
|
2798
3088
|
} catch {
|
|
@@ -2800,7 +3090,7 @@ const useEditorIO = () => {
|
|
|
2800
3090
|
return false;
|
|
2801
3091
|
}
|
|
2802
3092
|
},
|
|
2803
|
-
[api, toggleNotification]
|
|
3093
|
+
[api, post, toggleNotification]
|
|
2804
3094
|
);
|
|
2805
3095
|
return { loadImage: loadImage2, exportAs, saveToLibrary };
|
|
2806
3096
|
};
|
|
@@ -5245,7 +5535,8 @@ const TemplatesTab = () => {
|
|
|
5245
5535
|
const STUDIO_TABS = [
|
|
5246
5536
|
{ value: "generator", label: "Generador" },
|
|
5247
5537
|
{ value: "templates", label: "Plantillas" },
|
|
5248
|
-
{ value: "library", label: "Imágenes y documentos" }
|
|
5538
|
+
{ value: "library", label: "Imágenes y documentos" },
|
|
5539
|
+
{ value: "drive", label: "Google Drive" }
|
|
5249
5540
|
];
|
|
5250
5541
|
const PANEL = { height: "100%", overflowY: "auto" };
|
|
5251
5542
|
const isStudioTab = (v) => STUDIO_TABS.some((t) => t.value === v);
|
|
@@ -5273,7 +5564,8 @@ const ImageStudioTabs = () => {
|
|
|
5273
5564
|
/* @__PURE__ */ jsxs(Box, { flex: "1", style: { minHeight: 0 }, children: [
|
|
5274
5565
|
/* @__PURE__ */ jsx(Tabs.Content, { value: "generator", style: PANEL, children: /* @__PURE__ */ jsx(Box, { padding: 6, children: /* @__PURE__ */ jsx(GeneratorTab, {}) }) }),
|
|
5275
5566
|
/* @__PURE__ */ jsx(Tabs.Content, { value: "templates", style: PANEL, children: /* @__PURE__ */ jsx(TemplatesTab, {}) }),
|
|
5276
|
-
/* @__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, {}) }) })
|
|
5277
5569
|
] })
|
|
5278
5570
|
]
|
|
5279
5571
|
}
|