@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
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
7
|
+
const index = require("./index-BudahdfX.js");
|
|
8
8
|
const designSystem = require("@strapi/design-system");
|
|
9
|
-
const ImageStudioTabs = require("./ImageStudioTabs-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
94
|
+
const component = await import("./Settings-C6TbUrgU.mjs");
|
|
95
95
|
return component.default;
|
|
96
96
|
},
|
|
97
97
|
to: `plugins/${PLUGIN_ID}/settings`,
|
package/dist/admin/index.js
CHANGED
package/dist/admin/index.mjs
CHANGED
|
@@ -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
|
-
|
|
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 {};
|