@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.
Files changed (45) hide show
  1. package/dist/_chunks/{App-CTh6owSM.mjs → App-DD1MzaWm.mjs} +2 -2
  2. package/dist/_chunks/{App-ugsCmlsC.js → App-DspaMfyW.js} +2 -2
  3. package/dist/_chunks/{ImageStudioTabs-Umf3GODW.mjs → ImageStudioTabs-DwaLxRjl.mjs} +308 -16
  4. package/dist/_chunks/{ImageStudioTabs-BFEF9ggK.js → ImageStudioTabs-uXSBkFeu.js} +306 -14
  5. package/dist/_chunks/{Settings-CdGW2O8h.mjs → Settings-C6TbUrgU.mjs} +170 -2
  6. package/dist/_chunks/{Settings-Sk78JUBS.js → Settings-ps0o7-fC.js} +169 -1
  7. package/dist/_chunks/{StudioApp-CrGn-9Gg.mjs → StudioApp-B5I1OfGw.mjs} +2 -2
  8. package/dist/_chunks/{StudioApp-DoC0z4eh.js → StudioApp-B7kGpqqo.js} +2 -2
  9. package/dist/_chunks/{index-C2s9Jhcc.js → index-BudahdfX.js} +3 -3
  10. package/dist/_chunks/{index-D6UAvh53.mjs → index-zenHkX-p.mjs} +3 -3
  11. package/dist/admin/index.js +1 -1
  12. package/dist/admin/index.mjs +1 -1
  13. package/dist/admin/src/components/settings/DriveSettings.d.ts +77 -0
  14. package/dist/admin/src/features/drive/DriveTab.d.ts +42 -0
  15. package/dist/admin/src/features/image-studio/ImageStudioTabs.d.ts +3 -0
  16. package/dist/admin/src/features/library/FileCard.d.ts +5 -2
  17. package/dist/admin/src/features/library/driveSync.d.ts +41 -0
  18. package/dist/admin/src/features/templates/export/templateOrigin.d.ts +10 -0
  19. package/dist/admin/src/lib/use-cases/drive/index.d.ts +3 -0
  20. package/dist/server/index.js +1411 -49
  21. package/dist/server/index.mjs +1409 -47
  22. package/dist/server/src/bootstrap.d.ts +2 -0
  23. package/dist/server/src/content-types/drive-link/index.d.ts +62 -0
  24. package/dist/server/src/content-types/drive-link/schema.d.ts +68 -0
  25. package/dist/server/src/content-types/drive-op/index.d.ts +46 -0
  26. package/dist/server/src/content-types/drive-op/schema.d.ts +49 -0
  27. package/dist/server/src/content-types/index.d.ts +106 -0
  28. package/dist/server/src/controllers/drive.d.ts +30 -0
  29. package/dist/server/src/controllers/index.d.ts +130 -0
  30. package/dist/server/src/drive/client.d.ts +32 -0
  31. package/dist/server/src/drive/clientConfig.d.ts +27 -0
  32. package/dist/server/src/drive/common.d.ts +6 -0
  33. package/dist/server/src/drive/fakes.d.ts +104 -0
  34. package/dist/server/src/drive/files.d.ts +136 -0
  35. package/dist/server/src/drive/lifecycles.d.ts +7 -0
  36. package/dist/server/src/drive/links.d.ts +23 -0
  37. package/dist/server/src/drive/model.d.ts +99 -0
  38. package/dist/server/src/drive/oauth.d.ts +47 -0
  39. package/dist/server/src/drive/pull.d.ts +23 -0
  40. package/dist/server/src/drive/strapiPorts.d.ts +10 -0
  41. package/dist/server/src/drive/sync.d.ts +40 -0
  42. package/dist/server/src/index.d.ts +294 -0
  43. package/dist/server/src/services/drive.d.ts +141 -0
  44. package/dist/server/src/services/index.d.ts +55 -0
  45. package/package.json +1 -1
@@ -1,11 +1,11 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import { useFetchClient, useNotification, Page } from "@strapi/strapi/admin";
3
3
  import { useIntl } from "react-intl";
4
- import { useCallback, useState, useEffect } from "react";
4
+ import { useCallback, useState, useRef, useEffect, useMemo } from "react";
5
5
  import { Box, Grid, Field, Flex, Button, Typography, Textarea } from "@strapi/design-system";
6
6
  import { Formik } from "formik";
7
7
  import { P as PluginProviders } from "./PluginProviders-DWMpcAIh.mjs";
8
- import { P as PLUGIN_ID } from "./index-D6UAvh53.mjs";
8
+ import { P as PLUGIN_ID } from "./index-zenHkX-p.mjs";
9
9
  const useSettings = () => {
10
10
  const { get, post, del } = useFetchClient();
11
11
  const getSettings = useCallback(
@@ -135,6 +135,172 @@ const VertexSettings = ({ status, onSave, onRemove }) => {
135
135
  ] })
136
136
  ] }) });
137
137
  };
138
+ const windowLocation = {
139
+ href: () => window.location.href,
140
+ replace: (url) => window.history.replaceState(window.history.state, "", url),
141
+ assign: (url) => window.location.assign(url)
142
+ };
143
+ const GOOGLE_ERRORS = {
144
+ access_denied: "Se canceló la conexión con Google.",
145
+ admin_policy_enforced: "Google Workspace bloquea esta app. Un superadministrador debe marcarla como confiable en Controles de API."
146
+ };
147
+ const GB = 1024 ** 3;
148
+ const gb = (bytes) => `${(bytes / GB).toLocaleString("es-MX", { maximumFractionDigits: 1 })} GB`;
149
+ const formatQuota = (usage, limit) => usage === null ? "" : limit === null ? `${gb(usage)} usados` : `${gb(usage)} de ${gb(limit)}`;
150
+ const redirectUriOf = (href) => {
151
+ const url = new URL(href);
152
+ return `${url.origin}${url.pathname.replace(/\/+$/, "")}`;
153
+ };
154
+ const formatAgo = (at, now = Date.now()) => {
155
+ const min = Math.floor((now - at) / 6e4);
156
+ if (min < 1) return "hace un momento";
157
+ if (min < 60) return `hace ${min} min`;
158
+ if (min < 24 * 60) return `hace ${Math.round(min / 60)} h`;
159
+ return new Date(at).toLocaleString("es-MX", { dateStyle: "medium", timeStyle: "short" });
160
+ };
161
+ const summaryText = (s) => {
162
+ if (!s) return "Google Drive no está conectado.";
163
+ if (s.skipped) return "Ya hay una sincronización en curso; el resultado aparecerá aquí al terminar.";
164
+ const parts = [
165
+ `${s.pulled} ${s.pulled === 1 ? "cambio de Drive traído" : "cambios de Drive traídos"}`,
166
+ `${s.processed} ${s.processed === 1 ? "elemento subido o revisado" : "elementos subidos o revisados"}`
167
+ ];
168
+ if (s.errors) parts.push(`${s.errors} con error`);
169
+ if (s.pending) parts.push(`${s.pending} pendientes`);
170
+ return `Sincronización terminada: ${parts.join(", ")}.`;
171
+ };
172
+ const errorText = (err, fallback) => err instanceof Error && err.message ? err.message : fallback;
173
+ const DriveSettings = ({ api, location = windowLocation }) => {
174
+ const [status, setStatus] = useState(null);
175
+ const [error, setError] = useState(null);
176
+ const [busy, setBusy] = useState(false);
177
+ const [result, setResult] = useState(null);
178
+ const fileInput = useRef(null);
179
+ const redirectUri = redirectUriOf(location.href());
180
+ const refresh = useCallback(async () => {
181
+ try {
182
+ setStatus(await api.status());
183
+ } catch (err) {
184
+ setError(errorText(err, "No se pudo leer el estado de Google Drive."));
185
+ }
186
+ }, [api]);
187
+ const act = async (work) => {
188
+ setBusy(true);
189
+ setError(null);
190
+ try {
191
+ await work();
192
+ await refresh();
193
+ } catch (err) {
194
+ setError(errorText(err, "No se pudo completar la operación con Google Drive."));
195
+ } finally {
196
+ setBusy(false);
197
+ }
198
+ };
199
+ useEffect(() => {
200
+ const url = new URL(location.href());
201
+ const code = url.searchParams.get("code");
202
+ const state = url.searchParams.get("state");
203
+ const googleError = url.searchParams.get("error");
204
+ if (!code && !googleError) {
205
+ refresh();
206
+ return;
207
+ }
208
+ location.replace(redirectUri);
209
+ if (googleError) {
210
+ setError(GOOGLE_ERRORS[googleError] ?? `Google devolvió un error: ${googleError}.`);
211
+ refresh();
212
+ return;
213
+ }
214
+ act(() => api.exchange({ code, state: state ?? "", redirectUri }));
215
+ }, []);
216
+ const loadFile = (files) => {
217
+ const file = files?.[0];
218
+ if (!file) return;
219
+ act(async () => api.saveClient(await file.text(), redirectUri));
220
+ };
221
+ const connect = () => act(async () => {
222
+ location.assign(await api.authorizationUrl(redirectUri));
223
+ });
224
+ const syncNow = (full = false) => act(async () => {
225
+ setResult(null);
226
+ setResult(summaryText(await api.syncNow(full)));
227
+ });
228
+ const client = status?.client;
229
+ const connection = status?.connection;
230
+ const sync = connection?.connected ? status?.sync : null;
231
+ const syncLine = sync ? [
232
+ sync.lastRun ? `Última sincronización ${formatAgo(sync.lastRun.at)}` : "Todavía no ha corrido ninguna sincronización",
233
+ `${sync.pending} ${sync.pending === 1 ? "pendiente" : "pendientes"}`
234
+ ].join(" · ") : null;
235
+ const line = !status ? "Cargando…" : connection?.connected ? ["Conectada", connection.email, formatQuota(connection.usage, connection.limit)].filter(Boolean).join(" · ") : client?.configured ? `Sin conectar · cliente ${client.clientId} · secreto …${client.last4}` : "Sin configurar: carga el JSON del cliente OAuth que descargaste de Google Cloud.";
236
+ return /* @__PURE__ */ jsx(Box, { padding: 6, margin: 6, background: "neutral0", width: "100%", children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
237
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "start", gap: 1, children: [
238
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h2", children: "Google Drive" }),
239
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: line }),
240
+ connection?.error && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "danger600", children: connection.error }),
241
+ error && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "danger600", children: error }),
242
+ syncLine && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: syncLine }),
243
+ result && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "success600", children: result }),
244
+ sync && sync.errors.length > 0 && /* @__PURE__ */ jsx(Box, { tag: "ul", "aria-label": "Errores de la sincronización", paddingLeft: 4, children: sync.errors.map((e) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "danger600", children: [
245
+ e.name,
246
+ ": ",
247
+ e.error
248
+ ] }) }, e.key)) })
249
+ ] }),
250
+ /* @__PURE__ */ jsx(
251
+ "input",
252
+ {
253
+ ref: fileInput,
254
+ type: "file",
255
+ accept: "application/json,.json",
256
+ "aria-label": "JSON del cliente OAuth",
257
+ style: { display: "none" },
258
+ onChange: (e) => {
259
+ loadFile(e.target.files);
260
+ e.target.value = "";
261
+ }
262
+ }
263
+ ),
264
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "end", gap: 2, wrap: "wrap", children: [
265
+ client?.configured && !connection?.connected && /* @__PURE__ */ jsx(Button, { variant: "danger-light", onClick: () => act(api.removeClient), disabled: busy, children: "Quitar cliente" }),
266
+ connection?.connected && /* @__PURE__ */ jsx(Button, { variant: "tertiary", onClick: () => syncNow(true), disabled: busy, children: "Revisión completa" }),
267
+ connection?.connected && /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => syncNow(), disabled: busy, loading: busy, children: "Sincronizar ahora" }),
268
+ connection?.connected ? /* @__PURE__ */ jsx(Button, { variant: "danger-light", onClick: () => act(api.disconnect), disabled: busy, children: "Desconectar" }) : /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => fileInput.current?.click(), disabled: busy || !status, children: client?.configured ? "Reemplazar JSON" : "Cargar JSON del cliente" }),
269
+ client?.configured && !connection?.connected && /* @__PURE__ */ jsx(Button, { onClick: connect, disabled: busy, loading: busy, children: "Conectar con Google" })
270
+ ] })
271
+ ] }) });
272
+ };
273
+ const BASE = "/strapi-content-generator-gemini/drive";
274
+ const serverMessage$1 = (error, fallback) => error?.response?.data?.error?.message ?? fallback;
275
+ const useDriveApi = () => {
276
+ const { get, post, del } = useFetchClient();
277
+ return useMemo(() => {
278
+ const call = async (work, fallback) => {
279
+ try {
280
+ return (await work()).data;
281
+ } catch (error) {
282
+ throw new Error(serverMessage$1(error, fallback));
283
+ }
284
+ };
285
+ return {
286
+ status: () => call(() => get(`${BASE}/status`), "No se pudo leer el estado de Google Drive."),
287
+ saveClient: async (json, redirectUri) => {
288
+ await call(() => post(`${BASE}/client`, { json, redirectUri }), "No se pudo guardar el cliente OAuth.");
289
+ },
290
+ removeClient: async () => {
291
+ await call(() => del(`${BASE}/client`), "No se pudo quitar el cliente OAuth.");
292
+ },
293
+ authorizationUrl: async (redirectUri) => (await call(() => get(`${BASE}/oauth/url`, { params: { redirectUri } }), "No se pudo iniciar la conexión con Google.")).url,
294
+ exchange: async (payload) => {
295
+ await call(() => post(`${BASE}/oauth/exchange`, payload), "No se pudo conectar la cuenta.");
296
+ },
297
+ disconnect: async () => {
298
+ await call(() => del(`${BASE}/oauth`), "No se pudo desconectar la cuenta.");
299
+ },
300
+ syncNow: (full = false) => call(() => post(`${BASE}/sync`, { full }), "No se pudo sincronizar con Drive.")
301
+ };
302
+ }, [get, post, del]);
303
+ };
138
304
  const getTranslation = (id) => `${PLUGIN_ID}.${id}`;
139
305
  const NOT_CONFIGURED = { configured: false, last4: null };
140
306
  const VERTEX_OFF = { configured: false, projectId: null, clientEmail: null };
@@ -152,6 +318,7 @@ const SettingsContainer = () => {
152
318
  const { formatMessage } = useIntl();
153
319
  const { getSettings, setSettings, removeVertex } = useSettings();
154
320
  const { toggleNotification } = useNotification();
321
+ const driveApi = useDriveApi();
155
322
  const fetchSettings = useCallback(async () => {
156
323
  let isMounted = true;
157
324
  try {
@@ -227,6 +394,7 @@ const SettingsContainer = () => {
227
394
  /* @__PURE__ */ jsx(Typography, { variant: "pi", children: "Para uso exclusivo de InterWare de México" })
228
395
  ] }) }),
229
396
  /* @__PURE__ */ jsx(Grid.Item, { children: /* @__PURE__ */ jsx(VertexSettings, { status: vertex, onSave: saveVertex, onRemove: deleteVertex }) }),
397
+ /* @__PURE__ */ jsx(Grid.Item, { children: /* @__PURE__ */ jsx(DriveSettings, { api: driveApi }) }),
230
398
  /* @__PURE__ */ jsx(Grid.Item, { children: /* @__PURE__ */ jsx(Form, { status, handleSubmit }) })
231
399
  ] }) });
232
400
  };
@@ -7,7 +7,7 @@ const react = require("react");
7
7
  const designSystem = require("@strapi/design-system");
8
8
  const formik = require("formik");
9
9
  const PluginProviders = require("./PluginProviders-D79TeGSE.js");
10
- const index = require("./index-C2s9Jhcc.js");
10
+ const index = require("./index-BudahdfX.js");
11
11
  const useSettings = () => {
12
12
  const { get, post, del } = admin.useFetchClient();
13
13
  const getSettings = react.useCallback(
@@ -137,6 +137,172 @@ const VertexSettings = ({ status, onSave, onRemove }) => {
137
137
  ] })
138
138
  ] }) });
139
139
  };
140
+ const windowLocation = {
141
+ href: () => window.location.href,
142
+ replace: (url) => window.history.replaceState(window.history.state, "", url),
143
+ assign: (url) => window.location.assign(url)
144
+ };
145
+ const GOOGLE_ERRORS = {
146
+ access_denied: "Se canceló la conexión con Google.",
147
+ admin_policy_enforced: "Google Workspace bloquea esta app. Un superadministrador debe marcarla como confiable en Controles de API."
148
+ };
149
+ const GB = 1024 ** 3;
150
+ const gb = (bytes) => `${(bytes / GB).toLocaleString("es-MX", { maximumFractionDigits: 1 })} GB`;
151
+ const formatQuota = (usage, limit) => usage === null ? "" : limit === null ? `${gb(usage)} usados` : `${gb(usage)} de ${gb(limit)}`;
152
+ const redirectUriOf = (href) => {
153
+ const url = new URL(href);
154
+ return `${url.origin}${url.pathname.replace(/\/+$/, "")}`;
155
+ };
156
+ const formatAgo = (at, now = Date.now()) => {
157
+ const min = Math.floor((now - at) / 6e4);
158
+ if (min < 1) return "hace un momento";
159
+ if (min < 60) return `hace ${min} min`;
160
+ if (min < 24 * 60) return `hace ${Math.round(min / 60)} h`;
161
+ return new Date(at).toLocaleString("es-MX", { dateStyle: "medium", timeStyle: "short" });
162
+ };
163
+ const summaryText = (s) => {
164
+ if (!s) return "Google Drive no está conectado.";
165
+ if (s.skipped) return "Ya hay una sincronización en curso; el resultado aparecerá aquí al terminar.";
166
+ const parts = [
167
+ `${s.pulled} ${s.pulled === 1 ? "cambio de Drive traído" : "cambios de Drive traídos"}`,
168
+ `${s.processed} ${s.processed === 1 ? "elemento subido o revisado" : "elementos subidos o revisados"}`
169
+ ];
170
+ if (s.errors) parts.push(`${s.errors} con error`);
171
+ if (s.pending) parts.push(`${s.pending} pendientes`);
172
+ return `Sincronización terminada: ${parts.join(", ")}.`;
173
+ };
174
+ const errorText = (err, fallback) => err instanceof Error && err.message ? err.message : fallback;
175
+ const DriveSettings = ({ api, location = windowLocation }) => {
176
+ const [status, setStatus] = react.useState(null);
177
+ const [error, setError] = react.useState(null);
178
+ const [busy, setBusy] = react.useState(false);
179
+ const [result, setResult] = react.useState(null);
180
+ const fileInput = react.useRef(null);
181
+ const redirectUri = redirectUriOf(location.href());
182
+ const refresh = react.useCallback(async () => {
183
+ try {
184
+ setStatus(await api.status());
185
+ } catch (err) {
186
+ setError(errorText(err, "No se pudo leer el estado de Google Drive."));
187
+ }
188
+ }, [api]);
189
+ const act = async (work) => {
190
+ setBusy(true);
191
+ setError(null);
192
+ try {
193
+ await work();
194
+ await refresh();
195
+ } catch (err) {
196
+ setError(errorText(err, "No se pudo completar la operación con Google Drive."));
197
+ } finally {
198
+ setBusy(false);
199
+ }
200
+ };
201
+ react.useEffect(() => {
202
+ const url = new URL(location.href());
203
+ const code = url.searchParams.get("code");
204
+ const state = url.searchParams.get("state");
205
+ const googleError = url.searchParams.get("error");
206
+ if (!code && !googleError) {
207
+ refresh();
208
+ return;
209
+ }
210
+ location.replace(redirectUri);
211
+ if (googleError) {
212
+ setError(GOOGLE_ERRORS[googleError] ?? `Google devolvió un error: ${googleError}.`);
213
+ refresh();
214
+ return;
215
+ }
216
+ act(() => api.exchange({ code, state: state ?? "", redirectUri }));
217
+ }, []);
218
+ const loadFile = (files) => {
219
+ const file = files?.[0];
220
+ if (!file) return;
221
+ act(async () => api.saveClient(await file.text(), redirectUri));
222
+ };
223
+ const connect = () => act(async () => {
224
+ location.assign(await api.authorizationUrl(redirectUri));
225
+ });
226
+ const syncNow = (full = false) => act(async () => {
227
+ setResult(null);
228
+ setResult(summaryText(await api.syncNow(full)));
229
+ });
230
+ const client = status?.client;
231
+ const connection = status?.connection;
232
+ const sync = connection?.connected ? status?.sync : null;
233
+ const syncLine = sync ? [
234
+ sync.lastRun ? `Última sincronización ${formatAgo(sync.lastRun.at)}` : "Todavía no ha corrido ninguna sincronización",
235
+ `${sync.pending} ${sync.pending === 1 ? "pendiente" : "pendientes"}`
236
+ ].join(" · ") : null;
237
+ const line = !status ? "Cargando…" : connection?.connected ? ["Conectada", connection.email, formatQuota(connection.usage, connection.limit)].filter(Boolean).join(" · ") : client?.configured ? `Sin conectar · cliente ${client.clientId} · secreto …${client.last4}` : "Sin configurar: carga el JSON del cliente OAuth que descargaste de Google Cloud.";
238
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 6, margin: 6, background: "neutral0", width: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
239
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "start", gap: 1, children: [
240
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h2", children: "Google Drive" }),
241
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: line }),
242
+ connection?.error && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "danger600", children: connection.error }),
243
+ error && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "danger600", children: error }),
244
+ syncLine && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: syncLine }),
245
+ result && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "success600", children: result }),
246
+ sync && sync.errors.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { tag: "ul", "aria-label": "Errores de la sincronización", paddingLeft: 4, children: sync.errors.map((e) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "danger600", children: [
247
+ e.name,
248
+ ": ",
249
+ e.error
250
+ ] }) }, e.key)) })
251
+ ] }),
252
+ /* @__PURE__ */ jsxRuntime.jsx(
253
+ "input",
254
+ {
255
+ ref: fileInput,
256
+ type: "file",
257
+ accept: "application/json,.json",
258
+ "aria-label": "JSON del cliente OAuth",
259
+ style: { display: "none" },
260
+ onChange: (e) => {
261
+ loadFile(e.target.files);
262
+ e.target.value = "";
263
+ }
264
+ }
265
+ ),
266
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "end", gap: 2, wrap: "wrap", children: [
267
+ client?.configured && !connection?.connected && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "danger-light", onClick: () => act(api.removeClient), disabled: busy, children: "Quitar cliente" }),
268
+ connection?.connected && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", onClick: () => syncNow(true), disabled: busy, children: "Revisión completa" }),
269
+ connection?.connected && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "secondary", onClick: () => syncNow(), disabled: busy, loading: busy, children: "Sincronizar ahora" }),
270
+ connection?.connected ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "danger-light", onClick: () => act(api.disconnect), disabled: busy, children: "Desconectar" }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "secondary", onClick: () => fileInput.current?.click(), disabled: busy || !status, children: client?.configured ? "Reemplazar JSON" : "Cargar JSON del cliente" }),
271
+ client?.configured && !connection?.connected && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: connect, disabled: busy, loading: busy, children: "Conectar con Google" })
272
+ ] })
273
+ ] }) });
274
+ };
275
+ const BASE = "/strapi-content-generator-gemini/drive";
276
+ const serverMessage$1 = (error, fallback) => error?.response?.data?.error?.message ?? fallback;
277
+ const useDriveApi = () => {
278
+ const { get, post, del } = admin.useFetchClient();
279
+ return react.useMemo(() => {
280
+ const call = async (work, fallback) => {
281
+ try {
282
+ return (await work()).data;
283
+ } catch (error) {
284
+ throw new Error(serverMessage$1(error, fallback));
285
+ }
286
+ };
287
+ return {
288
+ status: () => call(() => get(`${BASE}/status`), "No se pudo leer el estado de Google Drive."),
289
+ saveClient: async (json, redirectUri) => {
290
+ await call(() => post(`${BASE}/client`, { json, redirectUri }), "No se pudo guardar el cliente OAuth.");
291
+ },
292
+ removeClient: async () => {
293
+ await call(() => del(`${BASE}/client`), "No se pudo quitar el cliente OAuth.");
294
+ },
295
+ authorizationUrl: async (redirectUri) => (await call(() => get(`${BASE}/oauth/url`, { params: { redirectUri } }), "No se pudo iniciar la conexión con Google.")).url,
296
+ exchange: async (payload) => {
297
+ await call(() => post(`${BASE}/oauth/exchange`, payload), "No se pudo conectar la cuenta.");
298
+ },
299
+ disconnect: async () => {
300
+ await call(() => del(`${BASE}/oauth`), "No se pudo desconectar la cuenta.");
301
+ },
302
+ syncNow: (full = false) => call(() => post(`${BASE}/sync`, { full }), "No se pudo sincronizar con Drive.")
303
+ };
304
+ }, [get, post, del]);
305
+ };
140
306
  const getTranslation = (id) => `${index.PLUGIN_ID}.${id}`;
141
307
  const NOT_CONFIGURED = { configured: false, last4: null };
142
308
  const VERTEX_OFF = { configured: false, projectId: null, clientEmail: null };
@@ -154,6 +320,7 @@ const SettingsContainer = () => {
154
320
  const { formatMessage } = reactIntl.useIntl();
155
321
  const { getSettings, setSettings, removeVertex } = useSettings();
156
322
  const { toggleNotification } = admin.useNotification();
323
+ const driveApi = useDriveApi();
157
324
  const fetchSettings = react.useCallback(async () => {
158
325
  let isMounted = true;
159
326
  try {
@@ -229,6 +396,7 @@ const SettingsContainer = () => {
229
396
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: "Para uso exclusivo de InterWare de México" })
230
397
  ] }) }),
231
398
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { children: /* @__PURE__ */ jsxRuntime.jsx(VertexSettings, { status: vertex, onSave: saveVertex, onRemove: deleteVertex }) }),
399
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { children: /* @__PURE__ */ jsxRuntime.jsx(DriveSettings, { api: driveApi }) }),
232
400
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { children: /* @__PURE__ */ jsxRuntime.jsx(Form, { status, handleSubmit }) })
233
401
  ] }) });
234
402
  };
@@ -2,9 +2,9 @@ import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { Page } from "@strapi/strapi/admin";
3
3
  import { Routes, Route } from "react-router-dom";
4
4
  import { P as PluginProviders } from "./PluginProviders-DWMpcAIh.mjs";
5
- import { p as pluginPermissions } from "./index-D6UAvh53.mjs";
5
+ import { p as pluginPermissions } from "./index-zenHkX-p.mjs";
6
6
  import { Box } from "@strapi/design-system";
7
- import { I as ImageStudioTabs } from "./ImageStudioTabs-Umf3GODW.mjs";
7
+ import { I as ImageStudioTabs } from "./ImageStudioTabs-DwaLxRjl.mjs";
8
8
  const ImageStudio = () => /* @__PURE__ */ jsxs(Page.Main, { children: [
9
9
  /* @__PURE__ */ jsx(Page.Title, { children: "Estudio de imágenes" }),
10
10
  /* @__PURE__ */ jsx(Box, { style: { height: "100vh" }, children: /* @__PURE__ */ jsx(ImageStudioTabs, {}) })
@@ -4,9 +4,9 @@ const jsxRuntime = require("react/jsx-runtime");
4
4
  const admin = require("@strapi/strapi/admin");
5
5
  const reactRouterDom = require("react-router-dom");
6
6
  const PluginProviders = require("./PluginProviders-D79TeGSE.js");
7
- const index = require("./index-C2s9Jhcc.js");
7
+ const index = require("./index-BudahdfX.js");
8
8
  const designSystem = require("@strapi/design-system");
9
- const ImageStudioTabs = require("./ImageStudioTabs-BFEF9ggK.js");
9
+ const ImageStudioTabs = require("./ImageStudioTabs-uXSBkFeu.js");
10
10
  const ImageStudio = () => /* @__PURE__ */ jsxRuntime.jsxs(admin.Page.Main, { children: [
11
11
  /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Title, { children: "Estudio de imágenes" }),
12
12
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { height: "100vh" }, children: /* @__PURE__ */ jsxRuntime.jsx(ImageStudioTabs.ImageStudioTabs, {}) })
@@ -57,7 +57,7 @@ const index = {
57
57
  defaultMessage: "Generador de contenido"
58
58
  },
59
59
  Component: async () => {
60
- const { App } = await Promise.resolve().then(() => require("./App-ugsCmlsC.js"));
60
+ const { App } = await Promise.resolve().then(() => require("./App-DspaMfyW.js"));
61
61
  return App;
62
62
  },
63
63
  permissions: [
@@ -72,7 +72,7 @@ const index = {
72
72
  defaultMessage: "Estudio de imágenes"
73
73
  },
74
74
  Component: async () => {
75
- const { StudioApp } = await Promise.resolve().then(() => require("./StudioApp-DoC0z4eh.js"));
75
+ const { StudioApp } = await Promise.resolve().then(() => require("./StudioApp-B7kGpqqo.js"));
76
76
  return StudioApp;
77
77
  },
78
78
  permissions: [
@@ -92,7 +92,7 @@ const index = {
92
92
  defaultMessage: PLUGIN_ID
93
93
  },
94
94
  Component: async () => {
95
- const component = await Promise.resolve().then(() => require("./Settings-Sk78JUBS.js"));
95
+ const component = await Promise.resolve().then(() => require("./Settings-ps0o7-fC.js"));
96
96
  return component.default;
97
97
  },
98
98
  to: `plugins/${PLUGIN_ID}/settings`,
@@ -56,7 +56,7 @@ const index = {
56
56
  defaultMessage: "Generador de contenido"
57
57
  },
58
58
  Component: async () => {
59
- const { App } = await import("./App-CTh6owSM.mjs");
59
+ const { App } = await import("./App-DD1MzaWm.mjs");
60
60
  return App;
61
61
  },
62
62
  permissions: [
@@ -71,7 +71,7 @@ const index = {
71
71
  defaultMessage: "Estudio de imágenes"
72
72
  },
73
73
  Component: async () => {
74
- const { StudioApp } = await import("./StudioApp-CrGn-9Gg.mjs");
74
+ const { StudioApp } = await import("./StudioApp-B5I1OfGw.mjs");
75
75
  return StudioApp;
76
76
  },
77
77
  permissions: [
@@ -91,7 +91,7 @@ const index = {
91
91
  defaultMessage: PLUGIN_ID
92
92
  },
93
93
  Component: async () => {
94
- const component = await import("./Settings-CdGW2O8h.mjs");
94
+ const component = await import("./Settings-C6TbUrgU.mjs");
95
95
  return component.default;
96
96
  },
97
97
  to: `plugins/${PLUGIN_ID}/settings`,
@@ -1,3 +1,3 @@
1
1
  "use strict";
2
- const index = require("../_chunks/index-C2s9Jhcc.js");
2
+ const index = require("../_chunks/index-BudahdfX.js");
3
3
  module.exports = index.index;
@@ -1,4 +1,4 @@
1
- import { i } from "../_chunks/index-D6UAvh53.mjs";
1
+ import { i } from "../_chunks/index-zenHkX-p.mjs";
2
2
  export {
3
3
  i as default
4
4
  };
@@ -0,0 +1,77 @@
1
+ /** Estado que devuelve `GET /drive/status`: nunca el secreto ni los tokens (S7.1). */
2
+ export interface DriveStatus {
3
+ client: {
4
+ configured: boolean;
5
+ clientId: string | null;
6
+ last4: string | null;
7
+ };
8
+ connection: {
9
+ connected: boolean;
10
+ email: string | null;
11
+ usage: number | null;
12
+ limit: number | null;
13
+ error: string | null;
14
+ };
15
+ /** Estado de la sincronización (S7.4); `null` sin conexión. */
16
+ sync?: {
17
+ lastRun: {
18
+ at: number;
19
+ processed: number;
20
+ errors: number;
21
+ } | null;
22
+ pending: number;
23
+ errors: {
24
+ key: string;
25
+ name: string;
26
+ error: string;
27
+ }[];
28
+ } | null;
29
+ }
30
+ /** Resumen de «Sincronizar ahora»; `null` si Drive no está conectado. */
31
+ export interface SyncSummary {
32
+ skipped: boolean;
33
+ /** Cambios de Drive aplicados al CMS. */
34
+ pulled: number;
35
+ processed: number;
36
+ errors: number;
37
+ pending: number;
38
+ }
39
+ /** Rutas `/drive/*` del plugin; rechazan con el mensaje del servidor. */
40
+ export interface DriveApi {
41
+ status: () => Promise<DriveStatus>;
42
+ saveClient: (json: string, redirectUri: string) => Promise<void>;
43
+ removeClient: () => Promise<void>;
44
+ authorizationUrl: (redirectUri: string) => Promise<string>;
45
+ exchange: (p: {
46
+ code: string;
47
+ state: string;
48
+ redirectUri: string;
49
+ }) => Promise<void>;
50
+ disconnect: () => Promise<void>;
51
+ /** Con `full`, compara todo lo sincronizado contra Drive aunque ya se haya hecho. */
52
+ syncNow: (full?: boolean) => Promise<SyncSummary | null>;
53
+ }
54
+ /** Acceso a la URL del navegador, inyectado para probar. */
55
+ export interface BrowserLocation {
56
+ href: () => string;
57
+ /** Quita `code` y `state` de la barra de direcciones sin recargar. */
58
+ replace: (url: string) => void;
59
+ /** Lleva a la pantalla de consentimiento de Google. */
60
+ assign: (url: string) => void;
61
+ }
62
+ /** «1.2 GB de 30 GB», o «1.2 GB usados» si la cuenta no tiene tope. */
63
+ export declare const formatQuota: (usage: number | null, limit: number | null) => string;
64
+ /** La URL de Ajustes sin query ni `/` final: es la URI de redirección registrada en Google Cloud. */
65
+ export declare const redirectUriOf: (href: string) => string;
66
+ /** «hace 3 min», «hace 2 h» o la fecha: cuándo corrió el último ciclo. */
67
+ export declare const formatAgo: (at: number, now?: number) => string;
68
+ /** Texto del resultado de «Sincronizar ahora». */
69
+ export declare const summaryText: (s: SyncSummary | null) => string;
70
+ /**
71
+ * Conexión con Google Drive en Ajustes (S7.1, ADR-008). Se carga el JSON del cliente OAuth que descarga Google
72
+ * Cloud, se conecta la cuenta con «Conectar con Google» y, al regresar de Google, se canjea el código.
73
+ */
74
+ export declare const DriveSettings: ({ api, location }: {
75
+ api: DriveApi;
76
+ location?: BrowserLocation;
77
+ }) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,42 @@
1
+ export interface DriveItem {
2
+ id: string;
3
+ name: string;
4
+ mimeType: string;
5
+ size: number | null;
6
+ modifiedTime: string | null;
7
+ webViewLink: string | null;
8
+ synced: boolean;
9
+ }
10
+ export interface DrivePage {
11
+ path: {
12
+ id: string;
13
+ name: string;
14
+ }[];
15
+ items: DriveItem[];
16
+ nextPageToken: string | null;
17
+ }
18
+ export interface DriveCount {
19
+ files: number;
20
+ folders: number;
21
+ truncated: boolean;
22
+ synced: boolean;
23
+ }
24
+ /** Rutas de la vista de Drive (S7.2); rechazan con el mensaje del servidor. */
25
+ export interface DriveBrowserApi {
26
+ browse: (folderId: string, pageToken?: string) => Promise<DrivePage>;
27
+ count: (id: string) => Promise<DriveCount>;
28
+ trash: (id: string) => Promise<void>;
29
+ }
30
+ /** Nombre legible del tipo de archivo. */
31
+ export declare const typeLabel: (mime: string) => string;
32
+ /** Texto de la confirmación de eliminar, con el contenido de la carpeta y el aviso de lo sincronizado. */
33
+ export declare const confirmText: (item: DriveItem, count: DriveCount | null) => string;
34
+ export declare const useDriveBrowserApi: () => DriveBrowserApi;
35
+ /**
36
+ * Pestaña «Google Drive» del Estudio (S7.2, ADR-010): todo el Drive de la cuenta conectada, en vivo. Se ve
37
+ * qué existe (incluidos Docs, Sheets y Slides), se abre en Drive y se manda a la papelera. No se edita aquí.
38
+ */
39
+ export declare const DriveTab: ({ api: injected, notify }: {
40
+ api?: DriveBrowserApi;
41
+ notify?: (type: 'success' | 'danger', message: string) => void;
42
+ }) => import("react/jsx-runtime").JSX.Element;
@@ -7,6 +7,9 @@ export declare const STUDIO_TABS: readonly [{
7
7
  }, {
8
8
  readonly value: "library";
9
9
  readonly label: "Imágenes y documentos";
10
+ }, {
11
+ readonly value: "drive";
12
+ readonly label: "Google Drive";
10
13
  }];
11
14
  export type StudioTab = (typeof STUDIO_TABS)[number]['value'];
12
15
  /**
@@ -1,5 +1,6 @@
1
1
  import type { LibraryFile } from './uploadApi';
2
- export type FileAction = 'preview' | 'download' | 'rename' | 'replace' | 'move' | 'removeBackground' | 'delete';
2
+ import type { DriveView } from './driveSync';
3
+ export type FileAction = 'preview' | 'download' | 'rename' | 'replace' | 'move' | 'removeBackground' | 'syncDrive' | 'unsyncDrive' | 'delete';
3
4
  /** Quitar fondo aplica a mapas de bits: un SVG puede no declarar tamaño y es vectorial. */
4
5
  export declare const canRemoveBackground: (f: LibraryFile) => boolean;
5
6
  /** Piezas que el plugin generó con IA: el servidor les pone ese caption al subirlas. */
@@ -11,7 +12,9 @@ interface FileCardProps {
11
12
  onAction: (action: FileAction, file: LibraryFile) => void;
12
13
  /** Avance de «Quitar fondo» sobre este archivo, si está en curso. */
13
14
  busy?: string;
15
+ /** Estado respecto a Google Drive (E7); sin conexión no hay insignia ni acciones. */
16
+ drive?: DriveView | null;
14
17
  }
15
18
  /** Tarjeta de archivo arrastrable a una carpeta del árbol. */
16
- export declare const FileCard: ({ file, onAction, busy }: FileCardProps) => import("react/jsx-runtime").JSX.Element;
19
+ export declare const FileCard: ({ file, onAction, busy, drive }: FileCardProps) => import("react/jsx-runtime").JSX.Element;
17
20
  export {};