contentoh-components-library 21.6.55 → 21.6.56
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/.claude/launch.json +14 -0
- package/.env.test +1 -0
- package/dist/ai/prompts/attribute.prompt.js +28 -0
- package/dist/ai/prompts/description.prompt.js +33 -0
- package/dist/ai/schemas/attribute.schema.js +35 -0
- package/dist/ai/schemas/description.schema.js +27 -0
- package/dist/components/atoms/AssetPreview/AssetPreview.stories.js +118 -0
- package/dist/components/atoms/AssetPreview/index.js +150 -0
- package/dist/components/atoms/AssetPreview/styles.js +28 -0
- package/dist/components/atoms/IconFile/index.js +85 -2
- package/dist/components/atoms/IconFile/styles.js +1 -1
- package/dist/components/atoms/ProductImage/ProductImage.stories.js +70 -2
- package/dist/components/atoms/ProductImage/index.js +100 -27
- package/dist/components/atoms/ProductImage/styles.js +3 -1
- package/dist/components/molecules/EditionActiveImage/index.js +4 -2
- package/dist/components/molecules/GalleryAssetElement/GalleryAssetElement.stories.js +147 -0
- package/dist/components/molecules/GalleryAssetElement/index.js +178 -0
- package/dist/components/molecules/GalleryAssetElement/styles.js +26 -0
- package/dist/components/molecules/GalleryElement/index.js +13 -4
- package/dist/components/molecules/GalleryHeader/index.js +96 -24
- package/dist/components/molecules/GalleryHeader/styles.js +4 -1
- package/dist/components/molecules/GalleryTrayTabs/GalleryTrayTabs.stories.js +56 -0
- package/dist/components/molecules/GalleryTrayTabs/index.js +60 -0
- package/dist/components/molecules/GalleryTrayTabs/styles.js +20 -0
- package/dist/components/molecules/ImageSelector/index.js +1 -0
- package/dist/components/organisms/DigitalAssetsTray/DigitalAssetsTray.stories.js +171 -0
- package/dist/components/organisms/DigitalAssetsTray/index.js +211 -0
- package/dist/components/organisms/DigitalAssetsTray/styles.js +22 -0
- package/dist/components/organisms/ImagePreviewer/index.js +1 -0
- package/dist/components/organisms/ProductImageModal/index.js +3 -2
- package/dist/components/pages/RetailerProductEdition/context/provider-product-edition.context.js +330 -15
- package/dist/components/pages/RetailerProductEdition/context/provider-product-edition.reducer.js +12 -2
- package/dist/components/pages/RetailerProductEdition/index.js +606 -425
- package/dist/components/pages/RetailerProductEdition/stories/ImageEditor.stories.js +1 -0
- package/dist/global-files/data.js +16 -3
- package/dist/global-files/digital-assets/assetTypes.js +480 -0
- package/dist/global-files/digital-assets/assets.actions.js +64 -0
- package/dist/global-files/digital-assets/assets.adapter.js +304 -0
- package/dist/global-files/digital-assets/assets.mock.js +136 -0
- package/dist/global-files/digital-assets/assets.reducer.js +220 -0
- package/dist/global-files/digital-assets/uploadAssets.js +179 -0
- package/dist/global-files/digital-assets/useDigitalAssets.js +522 -0
- package/dist/index.js +142 -90
- package/docs/activos-digitales.md +273 -0
- package/package.json +1 -1
- package/src/components/atoms/AssetPreview/AssetPreview.stories.js +78 -0
- package/src/components/atoms/AssetPreview/index.js +119 -0
- package/src/components/atoms/AssetPreview/styles.js +190 -0
- package/src/components/atoms/IconFile/index.js +28 -1
- package/src/components/atoms/IconFile/styles.js +8 -0
- package/src/components/atoms/ProductImage/ProductImage.stories.js +38 -0
- package/src/components/atoms/ProductImage/index.js +95 -20
- package/src/components/atoms/ProductImage/styles.js +92 -7
- package/src/components/molecules/EditionActiveImage/index.js +2 -2
- package/src/components/molecules/GalleryAssetElement/GalleryAssetElement.stories.js +102 -0
- package/src/components/molecules/GalleryAssetElement/index.js +162 -0
- package/src/components/molecules/GalleryAssetElement/styles.js +159 -0
- package/src/components/molecules/GalleryElement/index.js +9 -0
- package/src/components/molecules/GalleryHeader/index.js +65 -13
- package/src/components/molecules/GalleryHeader/styles.js +2 -1
- package/src/components/molecules/GalleryTrayTabs/GalleryTrayTabs.stories.js +32 -0
- package/src/components/molecules/GalleryTrayTabs/index.js +41 -0
- package/src/components/molecules/GalleryTrayTabs/styles.js +52 -0
- package/src/components/molecules/ImageSelector/index.js +1 -0
- package/src/components/organisms/DigitalAssetsTray/DigitalAssetsTray.stories.js +101 -0
- package/src/components/organisms/DigitalAssetsTray/index.js +189 -0
- package/src/components/organisms/DigitalAssetsTray/styles.js +183 -0
- package/src/components/organisms/ImagePreviewer/index.js +1 -0
- package/src/components/organisms/ProductImageModal/index.js +1 -0
- package/src/components/pages/RetailerProductEdition/context/provider-product-edition.context.jsx +201 -0
- package/src/components/pages/RetailerProductEdition/context/provider-product-edition.reducer.js +12 -0
- package/src/components/pages/RetailerProductEdition/index.js +356 -139
- package/src/components/pages/RetailerProductEdition/stories/ImageEditor.stories.js +1 -0
- package/src/global-files/data.js +17 -2
- package/src/global-files/digital-assets/assetTypes.js +419 -0
- package/src/global-files/digital-assets/assets.actions.js +50 -0
- package/src/global-files/digital-assets/assets.adapter.js +229 -0
- package/src/global-files/digital-assets/assets.mock.js +122 -0
- package/src/global-files/digital-assets/assets.reducer.js +197 -0
- package/src/global-files/digital-assets/uploadAssets.js +109 -0
- package/src/global-files/digital-assets/useDigitalAssets.js +329 -0
- package/src/index.js +4 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
|
|
4
|
+
|
|
5
|
+
Object.defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports.uploadToS3 = exports.requestPresign = void 0;
|
|
9
|
+
|
|
10
|
+
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
|
|
11
|
+
|
|
12
|
+
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/slicedToArray"));
|
|
13
|
+
|
|
14
|
+
var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/objectSpread2"));
|
|
15
|
+
|
|
16
|
+
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/asyncToGenerator"));
|
|
17
|
+
|
|
18
|
+
var _axios = _interopRequireDefault(require("axios"));
|
|
19
|
+
|
|
20
|
+
var _assets = require("./assets.adapter");
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Subida de activos digitales a S3.
|
|
24
|
+
*
|
|
25
|
+
* El archivo NO pasa por ningún Lambda ni se convierte a base64. El backend
|
|
26
|
+
* firma la subida y el navegador manda el archivo directo al bucket. Es lo que
|
|
27
|
+
* permite subir un .psd de 300 MB: el camino que usan hoy las imágenes
|
|
28
|
+
* —`FileReader.readAsDataURL` y `putObject` con las llaves de AWS en el
|
|
29
|
+
* bundle— carga el archivo entero en memoria y lo infla un tercio.
|
|
30
|
+
*
|
|
31
|
+
* Es un POST multipart y no un PUT porque el backend firma con
|
|
32
|
+
* `createPresignedPost`, la única forma de que S3 aplique de verdad el tope de
|
|
33
|
+
* tamaño y el Content-Type. Con un PUT firmado, quien tenga la URL sube lo que
|
|
34
|
+
* quiera del tamaño que quiera.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Pide al backend que firme la subida de varios archivos a la vez.
|
|
39
|
+
*
|
|
40
|
+
* En una sola llamada y no una por archivo: soltar diez archivos son diez
|
|
41
|
+
* viajes de ida y vuelta que no hacen falta.
|
|
42
|
+
*
|
|
43
|
+
* Devuelve `uploads` (los que se firmaron) y `rejected` (los que el servidor no
|
|
44
|
+
* aceptó, con su motivo). El servidor valida por su cuenta aunque el front ya
|
|
45
|
+
* lo haya hecho: el front se puede saltar, y quien firma decide qué entra al
|
|
46
|
+
* bucket.
|
|
47
|
+
*/
|
|
48
|
+
var requestPresign = /*#__PURE__*/function () {
|
|
49
|
+
var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(_ref) {
|
|
50
|
+
var _data$body, _body$uploads, _body$rejected;
|
|
51
|
+
|
|
52
|
+
var articleId, assets, token, _yield$axios$post, data, body;
|
|
53
|
+
|
|
54
|
+
return _regenerator.default.wrap(function _callee$(_context) {
|
|
55
|
+
while (1) {
|
|
56
|
+
switch (_context.prev = _context.next) {
|
|
57
|
+
case 0:
|
|
58
|
+
articleId = _ref.articleId, assets = _ref.assets, token = _ref.token;
|
|
59
|
+
_context.next = 3;
|
|
60
|
+
return _axios.default.post(process.env.REACT_APP_ASSETS_PRESIGN_ENDPOINT, {
|
|
61
|
+
articleId: articleId,
|
|
62
|
+
files: assets.map(function (asset) {
|
|
63
|
+
return (0, _objectSpread2.default)({
|
|
64
|
+
// El `ref` vuelve tal cual en la respuesta. Emparejar por nombre sería
|
|
65
|
+
// frágil: dos archivos que se llamen igual acabarían con la clave del
|
|
66
|
+
// otro.
|
|
67
|
+
ref: asset.tempId
|
|
68
|
+
}, (0, _assets.toPayload)(asset));
|
|
69
|
+
})
|
|
70
|
+
}, {
|
|
71
|
+
headers: {
|
|
72
|
+
Authorization: token
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
case 3:
|
|
77
|
+
_yield$axios$post = _context.sent;
|
|
78
|
+
data = _yield$axios$post.data;
|
|
79
|
+
// `integration: lambda` de API Gateway devuelve el objeto del handler tal
|
|
80
|
+
// cual, con el cuerpo como cadena, en vez de traducirlo a una respuesta HTTP.
|
|
81
|
+
body = typeof (data === null || data === void 0 ? void 0 : data.body) === "string" ? JSON.parse(data.body) : (_data$body = data === null || data === void 0 ? void 0 : data.body) !== null && _data$body !== void 0 ? _data$body : data;
|
|
82
|
+
|
|
83
|
+
if (!(data !== null && data !== void 0 && data.statusCode && data.statusCode >= 400)) {
|
|
84
|
+
_context.next = 8;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
throw new Error((body === null || body === void 0 ? void 0 : body.message) || "No se pudo preparar la subida");
|
|
89
|
+
|
|
90
|
+
case 8:
|
|
91
|
+
return _context.abrupt("return", {
|
|
92
|
+
uploads: (_body$uploads = body === null || body === void 0 ? void 0 : body.uploads) !== null && _body$uploads !== void 0 ? _body$uploads : [],
|
|
93
|
+
rejected: (_body$rejected = body === null || body === void 0 ? void 0 : body.rejected) !== null && _body$rejected !== void 0 ? _body$rejected : []
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
case 9:
|
|
97
|
+
case "end":
|
|
98
|
+
return _context.stop();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}, _callee);
|
|
102
|
+
}));
|
|
103
|
+
|
|
104
|
+
return function requestPresign(_x) {
|
|
105
|
+
return _ref2.apply(this, arguments);
|
|
106
|
+
};
|
|
107
|
+
}();
|
|
108
|
+
/**
|
|
109
|
+
* Sube un archivo con los campos que firmó el backend.
|
|
110
|
+
*
|
|
111
|
+
* Se usa XMLHttpRequest y no fetch porque fetch todavía no sabe informar del
|
|
112
|
+
* progreso de subida. En un .psd de 300 MB la barra no es un adorno: sin ella
|
|
113
|
+
* la pantalla parece colgada durante minutos.
|
|
114
|
+
*
|
|
115
|
+
* El orden del FormData importa: S3 exige que el archivo vaya AL FINAL, después
|
|
116
|
+
* de todos los campos de la política. Si va antes, rechaza la petición.
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
exports.requestPresign = requestPresign;
|
|
121
|
+
|
|
122
|
+
var uploadToS3 = function uploadToS3(_ref3) {
|
|
123
|
+
var upload = _ref3.upload,
|
|
124
|
+
file = _ref3.file,
|
|
125
|
+
onProgress = _ref3.onProgress;
|
|
126
|
+
return new Promise(function (resolve, reject) {
|
|
127
|
+
var form = new FormData();
|
|
128
|
+
Object.entries(upload.fields).forEach(function (_ref4) {
|
|
129
|
+
var _ref5 = (0, _slicedToArray2.default)(_ref4, 2),
|
|
130
|
+
name = _ref5[0],
|
|
131
|
+
value = _ref5[1];
|
|
132
|
+
|
|
133
|
+
return form.append(name, value);
|
|
134
|
+
});
|
|
135
|
+
form.append("file", file);
|
|
136
|
+
var xhr = new XMLHttpRequest();
|
|
137
|
+
xhr.open("POST", upload.url, true);
|
|
138
|
+
|
|
139
|
+
xhr.upload.onprogress = function (event) {
|
|
140
|
+
if (!event.lengthComputable) return;
|
|
141
|
+
onProgress === null || onProgress === void 0 ? void 0 : onProgress(Math.round(event.loaded / event.total * 100));
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
xhr.onload = function () {
|
|
145
|
+
var _$exec;
|
|
146
|
+
|
|
147
|
+
// S3 responde 204 sin cuerpo cuando acepta el archivo.
|
|
148
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
149
|
+
resolve(upload.key);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
/*
|
|
153
|
+
Los errores de S3 vienen en XML, no en JSON. Los dos que importan:
|
|
154
|
+
EntityTooLarge = el archivo se pasó del tope que firmamos, y AccessDenied
|
|
155
|
+
= al rol del Lambda le falta s3:PutObject sobre el prefijo, o sea que se
|
|
156
|
+
desplegó solo la función y no el servicio completo.
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
var code = (_$exec = /<Code>(.*?)<\/Code>/.exec(xhr.responseText)) === null || _$exec === void 0 ? void 0 : _$exec[1];
|
|
161
|
+
reject(new Error(code === "EntityTooLarge" ? "El archivo supera el tamaño permitido." : "S3 rechaz\xF3 la subida (".concat(code || xhr.status, ").")));
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
xhr.onerror = function () {
|
|
165
|
+
return reject(new Error( // Un fallo de red sin estatus casi siempre es CORS: el bucket tiene que
|
|
166
|
+
// permitir POST desde el origen de la aplicación. El navegador corta la
|
|
167
|
+
// petición antes de mandarla y no deja rastro en ningún log del servidor.
|
|
168
|
+
"No se pudo conectar con el almacenamiento. Revisa la configuración CORS del bucket."));
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
xhr.onabort = function () {
|
|
172
|
+
return reject(new Error("Subida cancelada."));
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
xhr.send(form);
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
exports.uploadToS3 = uploadToS3;
|
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
|
|
4
|
+
|
|
5
|
+
Object.defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports.useDigitalAssets = exports.readVideoDuration = void 0;
|
|
9
|
+
|
|
10
|
+
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
|
|
11
|
+
|
|
12
|
+
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/toConsumableArray"));
|
|
13
|
+
|
|
14
|
+
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/asyncToGenerator"));
|
|
15
|
+
|
|
16
|
+
var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/objectSpread2"));
|
|
17
|
+
|
|
18
|
+
var _react = require("react");
|
|
19
|
+
|
|
20
|
+
var _assets = require("./assets.actions");
|
|
21
|
+
|
|
22
|
+
var _uploadAssets = require("./uploadAssets");
|
|
23
|
+
|
|
24
|
+
var _assetTypes = require("./assetTypes");
|
|
25
|
+
|
|
26
|
+
var _assets2 = require("./assets.adapter");
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Duración de un video sin leerlo a base64.
|
|
30
|
+
*
|
|
31
|
+
* `URL.createObjectURL` le da al navegador una referencia al archivo del disco;
|
|
32
|
+
* `preload="metadata"` hace que solo baje la cabecera. La alternativa que usa
|
|
33
|
+
* hoy la ficha —`FileReader.readAsDataURL`— carga el archivo entero en memoria
|
|
34
|
+
* y lo infla un tercio, que es justo lo que impide subir videos largos.
|
|
35
|
+
*/
|
|
36
|
+
var readVideoDuration = function readVideoDuration(objectUrl) {
|
|
37
|
+
return new Promise(function (resolve) {
|
|
38
|
+
var video = document.createElement("video");
|
|
39
|
+
video.preload = "metadata";
|
|
40
|
+
|
|
41
|
+
video.onloadedmetadata = function () {
|
|
42
|
+
return resolve(video.duration);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
video.onerror = function () {
|
|
46
|
+
return resolve(null);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
video.src = objectUrl;
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* La bandeja de activos digitales, sobre el reducer de la ficha.
|
|
54
|
+
*
|
|
55
|
+
* El hook NO guarda la lista: la fuente de verdad es `state.assets_values`, para
|
|
56
|
+
* que el guardado del contexto pueda leerla igual que lee `images_values`. Aquí
|
|
57
|
+
* vive lo que un reducer no puede tener: la validación, la lectura asíncrona de
|
|
58
|
+
* la duración, y la liberación de los object URL (efectos, no estado).
|
|
59
|
+
*
|
|
60
|
+
* La subida arranca sola al soltar los archivos, no al guardar. Un .psd de 300
|
|
61
|
+
* MB tarda; hacerlo al guardar dejaría al usuario mirando un botón bloqueado
|
|
62
|
+
* durante minutos, mientras que así sube en segundo plano con su barra de
|
|
63
|
+
* progreso y guardar es instantáneo. El precio es que un archivo que se sube y
|
|
64
|
+
* luego se quita deja un objeto huérfano en S3, que recoge una regla de ciclo
|
|
65
|
+
* de vida.
|
|
66
|
+
*
|
|
67
|
+
* Sin `articleId` o sin `token` no se sube nada: es lo que permite usar la
|
|
68
|
+
* bandeja en Storybook, donde no hay sesión ni producto.
|
|
69
|
+
*
|
|
70
|
+
* `onUploaded` recibe los activos que terminaron de subir, para registrarlos en
|
|
71
|
+
* la base sin esperar al botón de guardar. Al ver la barra llegar al final
|
|
72
|
+
* cualquiera da por hecho que el activo ya quedó ligado al producto; si no lo
|
|
73
|
+
* estuviera y cerrara la ficha, el archivo se quedaría en S3 sin fila.
|
|
74
|
+
*
|
|
75
|
+
* `notices` es lo único que sí vive aquí: mensajes efímeros sobre los archivos
|
|
76
|
+
* que se acaban de soltar —uno rechazado, otro aceptado pero que el navegador no
|
|
77
|
+
* podrá reproducir—. No son parte del producto y no tienen por qué ensuciar el
|
|
78
|
+
* estado de la ficha. Cada uno lleva su `level` para que la bandeja distinga lo
|
|
79
|
+
* que no entró de lo que entró con advertencia.
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
exports.readVideoDuration = readVideoDuration;
|
|
84
|
+
|
|
85
|
+
var useDigitalAssets = function useDigitalAssets() {
|
|
86
|
+
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
|
87
|
+
_ref$assets = _ref.assets,
|
|
88
|
+
assets = _ref$assets === void 0 ? [] : _ref$assets,
|
|
89
|
+
dispatch = _ref.dispatch,
|
|
90
|
+
_ref$notices = _ref.notices,
|
|
91
|
+
notices = _ref$notices === void 0 ? [] : _ref$notices,
|
|
92
|
+
_ref$setNotices = _ref.setNotices,
|
|
93
|
+
setNotices = _ref$setNotices === void 0 ? function () {} : _ref$setNotices,
|
|
94
|
+
_ref$articleId = _ref.articleId,
|
|
95
|
+
articleId = _ref$articleId === void 0 ? null : _ref$articleId,
|
|
96
|
+
_ref$token = _ref.token,
|
|
97
|
+
token = _ref$token === void 0 ? null : _ref$token,
|
|
98
|
+
_ref$onUploaded = _ref.onUploaded,
|
|
99
|
+
onUploaded = _ref$onUploaded === void 0 ? null : _ref$onUploaded;
|
|
100
|
+
|
|
101
|
+
/*
|
|
102
|
+
Copia sincrónica de la lista, solo para contar el cupo.
|
|
103
|
+
`assets` viene del render y `addFiles` es asíncrono: si el usuario suelta una
|
|
104
|
+
segunda tanda mientras la primera todavía está midiendo la duración de un
|
|
105
|
+
video, esa segunda llamada vería el conteo viejo y dejaría pasar de más.
|
|
106
|
+
Escribir la ref al despachar la mantiene exacta.
|
|
107
|
+
*/
|
|
108
|
+
var countRef = (0, _react.useRef)(assets.length);
|
|
109
|
+
(0, _react.useEffect)(function () {
|
|
110
|
+
countRef.current = assets.length;
|
|
111
|
+
}, [assets.length]);
|
|
112
|
+
var assetsRef = (0, _react.useRef)(assets);
|
|
113
|
+
(0, _react.useEffect)(function () {
|
|
114
|
+
assetsRef.current = assets;
|
|
115
|
+
}, [assets]); // Los object URL se liberan al desmontar. Sin esto, cada archivo que el
|
|
116
|
+
// usuario mira se queda en memoria hasta que recargue la pestaña.
|
|
117
|
+
|
|
118
|
+
(0, _react.useEffect)(function () {
|
|
119
|
+
return function () {
|
|
120
|
+
return assetsRef.current.forEach(_assets2.revokeAssetUrl);
|
|
121
|
+
};
|
|
122
|
+
}, []);
|
|
123
|
+
var replaceAll = (0, _react.useCallback)(function (rows) {
|
|
124
|
+
var list = (0, _assets2.fromBackendList)(rows);
|
|
125
|
+
countRef.current = list.length;
|
|
126
|
+
dispatch({
|
|
127
|
+
type: _assets.ASSET_ACTIONS.SET_ASSETS_VALUES,
|
|
128
|
+
payload: list
|
|
129
|
+
});
|
|
130
|
+
setNotices([]);
|
|
131
|
+
}, [dispatch, setNotices]);
|
|
132
|
+
/*
|
|
133
|
+
Ciclo de vida de una subida. Va antes de `uploadAssets` y de `addFiles`
|
|
134
|
+
porque los dos lo usan: leer el archivo de arriba abajo cuenta la historia
|
|
135
|
+
en orden —qué puede pasarle a un activo, quién lo provoca— en vez de
|
|
136
|
+
obligar a saltar al final para entender la mitad de arriba.
|
|
137
|
+
*/
|
|
138
|
+
|
|
139
|
+
var patch = (0, _react.useCallback)(function (key, changes) {
|
|
140
|
+
return dispatch({
|
|
141
|
+
type: _assets.ASSET_ACTIONS.PATCH_ASSET,
|
|
142
|
+
payload: {
|
|
143
|
+
key: key,
|
|
144
|
+
changes: changes
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}, [dispatch]);
|
|
148
|
+
var startUpload = (0, _react.useCallback)(function (key) {
|
|
149
|
+
return patch(key, {
|
|
150
|
+
status: _assets2.ASSET_STATUS.UPLOADING,
|
|
151
|
+
progress: 0,
|
|
152
|
+
error: null
|
|
153
|
+
});
|
|
154
|
+
}, [patch]);
|
|
155
|
+
var setProgress = (0, _react.useCallback)(function (key, progress) {
|
|
156
|
+
return patch(key, {
|
|
157
|
+
progress: progress
|
|
158
|
+
});
|
|
159
|
+
}, [patch]);
|
|
160
|
+
var completeUpload = (0, _react.useCallback)(function (key) {
|
|
161
|
+
var changes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
162
|
+
return patch(key, (0, _objectSpread2.default)({
|
|
163
|
+
status: _assets2.ASSET_STATUS.SAVED,
|
|
164
|
+
progress: 100,
|
|
165
|
+
error: null
|
|
166
|
+
}, changes));
|
|
167
|
+
}, [patch]);
|
|
168
|
+
var failUpload = (0, _react.useCallback)(function (key, message) {
|
|
169
|
+
return patch(key, {
|
|
170
|
+
status: _assets2.ASSET_STATUS.ERROR,
|
|
171
|
+
error: message
|
|
172
|
+
});
|
|
173
|
+
}, [patch]);
|
|
174
|
+
/**
|
|
175
|
+
* Firma y sube los archivos recién aceptados, en segundo plano.
|
|
176
|
+
*
|
|
177
|
+
* Las subidas van en paralelo a propósito: son transferencias a S3, no
|
|
178
|
+
* consultas a una base, y el cuello de botella es el ancho de banda del
|
|
179
|
+
* usuario. Ponerlas en fila haría que el último archivo de diez empezara
|
|
180
|
+
* cuando los otros nueve ya terminaron.
|
|
181
|
+
*
|
|
182
|
+
* Un fallo se marca en SU activo y no tumba a los demás: si un archivo se
|
|
183
|
+
* corta, los otros nueve siguen y el usuario reintenta solo ese.
|
|
184
|
+
*/
|
|
185
|
+
|
|
186
|
+
var uploadAssets = (0, _react.useCallback)( /*#__PURE__*/function () {
|
|
187
|
+
var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(pendingAssets) {
|
|
188
|
+
var presigned, sinEmparejar, emparejar, subidos, completados;
|
|
189
|
+
return _regenerator.default.wrap(function _callee2$(_context2) {
|
|
190
|
+
while (1) {
|
|
191
|
+
switch (_context2.prev = _context2.next) {
|
|
192
|
+
case 0:
|
|
193
|
+
if (!(!articleId || !token || pendingAssets.length === 0)) {
|
|
194
|
+
_context2.next = 2;
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return _context2.abrupt("return");
|
|
199
|
+
|
|
200
|
+
case 2:
|
|
201
|
+
_context2.prev = 2;
|
|
202
|
+
_context2.next = 5;
|
|
203
|
+
return (0, _uploadAssets.requestPresign)({
|
|
204
|
+
articleId: articleId,
|
|
205
|
+
assets: pendingAssets,
|
|
206
|
+
token: token
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
case 5:
|
|
210
|
+
presigned = _context2.sent;
|
|
211
|
+
_context2.next = 12;
|
|
212
|
+
break;
|
|
213
|
+
|
|
214
|
+
case 8:
|
|
215
|
+
_context2.prev = 8;
|
|
216
|
+
_context2.t0 = _context2["catch"](2);
|
|
217
|
+
// Si la firma falla, ninguno se puede subir: se marcan todos, porque
|
|
218
|
+
// dejarlos en "pendiente" haría creer que siguen en camino.
|
|
219
|
+
pendingAssets.forEach(function (asset) {
|
|
220
|
+
return failUpload((0, _assets2.assetKey)(asset), _context2.t0.message);
|
|
221
|
+
});
|
|
222
|
+
return _context2.abrupt("return");
|
|
223
|
+
|
|
224
|
+
case 12:
|
|
225
|
+
/*
|
|
226
|
+
Emparejar cada resultado con su activo.
|
|
227
|
+
Por `ref` cuando el servidor lo devuelve, que es lo fiable. Mientras no
|
|
228
|
+
se despliegue esa versión, `ref` llega vacío y se cae al nombre —con la
|
|
229
|
+
lista de candidatos menguando, para que dos archivos que se llamen igual
|
|
230
|
+
no acaben los dos apuntando al mismo—.
|
|
231
|
+
*/
|
|
232
|
+
sinEmparejar = (0, _toConsumableArray2.default)(pendingAssets);
|
|
233
|
+
|
|
234
|
+
emparejar = function emparejar(item) {
|
|
235
|
+
var index = sinEmparejar.findIndex(function (asset) {
|
|
236
|
+
return item.ref ? asset.tempId === item.ref : asset.name === item.name;
|
|
237
|
+
});
|
|
238
|
+
if (index === -1) return null;
|
|
239
|
+
return sinEmparejar.splice(index, 1)[0];
|
|
240
|
+
}; // El servidor valida por su cuenta: lo que rechace se marca con su motivo.
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
presigned.rejected.forEach(function (item) {
|
|
244
|
+
var target = emparejar(item);
|
|
245
|
+
if (target) failUpload((0, _assets2.assetKey)(target), item.reason);
|
|
246
|
+
});
|
|
247
|
+
_context2.next = 17;
|
|
248
|
+
return Promise.all(presigned.uploads.map( /*#__PURE__*/function () {
|
|
249
|
+
var _ref3 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(upload) {
|
|
250
|
+
var asset, key;
|
|
251
|
+
return _regenerator.default.wrap(function _callee$(_context) {
|
|
252
|
+
while (1) {
|
|
253
|
+
switch (_context.prev = _context.next) {
|
|
254
|
+
case 0:
|
|
255
|
+
asset = emparejar(upload);
|
|
256
|
+
|
|
257
|
+
if (asset !== null && asset !== void 0 && asset.file) {
|
|
258
|
+
_context.next = 3;
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return _context.abrupt("return", null);
|
|
263
|
+
|
|
264
|
+
case 3:
|
|
265
|
+
key = (0, _assets2.assetKey)(asset);
|
|
266
|
+
startUpload(key);
|
|
267
|
+
_context.prev = 5;
|
|
268
|
+
_context.next = 8;
|
|
269
|
+
return (0, _uploadAssets.uploadToS3)({
|
|
270
|
+
upload: upload,
|
|
271
|
+
file: asset.file,
|
|
272
|
+
onProgress: function onProgress(percent) {
|
|
273
|
+
return setProgress(key, percent);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
case 8:
|
|
278
|
+
// `src` es lo que convierte un activo local en guardable: el
|
|
279
|
+
// guardado se niega a registrar un activo sin clave en S3.
|
|
280
|
+
completeUpload(key, {
|
|
281
|
+
src: upload.key
|
|
282
|
+
});
|
|
283
|
+
return _context.abrupt("return", (0, _objectSpread2.default)((0, _objectSpread2.default)({}, asset), {}, {
|
|
284
|
+
src: upload.key
|
|
285
|
+
}));
|
|
286
|
+
|
|
287
|
+
case 12:
|
|
288
|
+
_context.prev = 12;
|
|
289
|
+
_context.t0 = _context["catch"](5);
|
|
290
|
+
failUpload(key, _context.t0.message);
|
|
291
|
+
return _context.abrupt("return", null);
|
|
292
|
+
|
|
293
|
+
case 16:
|
|
294
|
+
case "end":
|
|
295
|
+
return _context.stop();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}, _callee, null, [[5, 12]]);
|
|
299
|
+
}));
|
|
300
|
+
|
|
301
|
+
return function (_x2) {
|
|
302
|
+
return _ref3.apply(this, arguments);
|
|
303
|
+
};
|
|
304
|
+
}()));
|
|
305
|
+
|
|
306
|
+
case 17:
|
|
307
|
+
subidos = _context2.sent;
|
|
308
|
+
|
|
309
|
+
/*
|
|
310
|
+
Se avisa una sola vez, con todo lo que llegó completo, en lugar de una
|
|
311
|
+
llamada por archivo: soltar diez archivos haría diez escrituras a la
|
|
312
|
+
base para el mismo producto.
|
|
313
|
+
Solo lo que subió bien. Registrar un activo cuyo archivo no llegó a S3
|
|
314
|
+
crearía una fila apuntando a nada.
|
|
315
|
+
*/
|
|
316
|
+
completados = subidos.filter(Boolean);
|
|
317
|
+
|
|
318
|
+
if (!(completados.length > 0)) {
|
|
319
|
+
_context2.next = 22;
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
_context2.next = 22;
|
|
324
|
+
return onUploaded === null || onUploaded === void 0 ? void 0 : onUploaded(completados);
|
|
325
|
+
|
|
326
|
+
case 22:
|
|
327
|
+
case "end":
|
|
328
|
+
return _context2.stop();
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}, _callee2, null, [[2, 8]]);
|
|
332
|
+
}));
|
|
333
|
+
|
|
334
|
+
return function (_x) {
|
|
335
|
+
return _ref2.apply(this, arguments);
|
|
336
|
+
};
|
|
337
|
+
}(), [articleId, token, onUploaded, startUpload, setProgress, completeUpload, failUpload]);
|
|
338
|
+
/**
|
|
339
|
+
* Valida y agrega archivos. Devuelve lo aceptado y lo rechazado para que quien
|
|
340
|
+
* llama decida cómo avisar.
|
|
341
|
+
*
|
|
342
|
+
* El cupo se cuenta contra lo que ya hay MÁS lo aceptado en esta misma tanda:
|
|
343
|
+
* si no, soltar quince archivos de golpe los dejaría pasar todos, porque cada
|
|
344
|
+
* uno vería el mismo conteo inicial.
|
|
345
|
+
*/
|
|
346
|
+
|
|
347
|
+
var addFiles = (0, _react.useCallback)( /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee3() {
|
|
348
|
+
var files,
|
|
349
|
+
accepted,
|
|
350
|
+
avisos,
|
|
351
|
+
count,
|
|
352
|
+
_i,
|
|
353
|
+
_Array$from,
|
|
354
|
+
file,
|
|
355
|
+
check,
|
|
356
|
+
objectUrl,
|
|
357
|
+
asset,
|
|
358
|
+
duration,
|
|
359
|
+
durationCheck,
|
|
360
|
+
_args3 = arguments;
|
|
361
|
+
|
|
362
|
+
return _regenerator.default.wrap(function _callee3$(_context3) {
|
|
363
|
+
while (1) {
|
|
364
|
+
switch (_context3.prev = _context3.next) {
|
|
365
|
+
case 0:
|
|
366
|
+
files = _args3.length > 0 && _args3[0] !== undefined ? _args3[0] : [];
|
|
367
|
+
accepted = [];
|
|
368
|
+
avisos = [];
|
|
369
|
+
count = countRef.current;
|
|
370
|
+
_i = 0, _Array$from = Array.from(files);
|
|
371
|
+
|
|
372
|
+
case 5:
|
|
373
|
+
if (!(_i < _Array$from.length)) {
|
|
374
|
+
_context3.next = 29;
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
file = _Array$from[_i];
|
|
379
|
+
check = (0, _assetTypes.validateAssetFile)(file, {
|
|
380
|
+
currentCount: count
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
if (check.valid) {
|
|
384
|
+
_context3.next = 11;
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
avisos.push({
|
|
389
|
+
level: "error",
|
|
390
|
+
name: file.name,
|
|
391
|
+
code: check.code,
|
|
392
|
+
message: check.message
|
|
393
|
+
});
|
|
394
|
+
return _context3.abrupt("continue", 26);
|
|
395
|
+
|
|
396
|
+
case 11:
|
|
397
|
+
// Se acepta, pero hay algo que decirle al usuario sobre este archivo.
|
|
398
|
+
if (check.warning) {
|
|
399
|
+
avisos.push({
|
|
400
|
+
level: "warning",
|
|
401
|
+
name: file.name,
|
|
402
|
+
message: check.warning
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
objectUrl = URL.createObjectURL(file);
|
|
407
|
+
asset = (0, _assets2.fromFile)(file, {
|
|
408
|
+
assetType: check.assetType,
|
|
409
|
+
objectUrl: objectUrl
|
|
410
|
+
}); // La duración solo se mide en lo que la valida, y después del peso: no
|
|
411
|
+
// tiene sentido abrir un archivo que ya se rechazó.
|
|
412
|
+
|
|
413
|
+
if (!(check.assetType === "video" || check.assetType === "360")) {
|
|
414
|
+
_context3.next = 24;
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
_context3.next = 17;
|
|
419
|
+
return readVideoDuration(objectUrl);
|
|
420
|
+
|
|
421
|
+
case 17:
|
|
422
|
+
duration = _context3.sent;
|
|
423
|
+
durationCheck = (0, _assetTypes.validateAssetDuration)(duration, check.assetType);
|
|
424
|
+
|
|
425
|
+
if (durationCheck.valid) {
|
|
426
|
+
_context3.next = 23;
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
URL.revokeObjectURL(objectUrl);
|
|
431
|
+
avisos.push({
|
|
432
|
+
level: "error",
|
|
433
|
+
name: file.name,
|
|
434
|
+
code: durationCheck.code,
|
|
435
|
+
message: durationCheck.message
|
|
436
|
+
});
|
|
437
|
+
return _context3.abrupt("continue", 26);
|
|
438
|
+
|
|
439
|
+
case 23:
|
|
440
|
+
asset.duration = duration;
|
|
441
|
+
|
|
442
|
+
case 24:
|
|
443
|
+
accepted.push(asset);
|
|
444
|
+
count += 1;
|
|
445
|
+
|
|
446
|
+
case 26:
|
|
447
|
+
_i++;
|
|
448
|
+
_context3.next = 5;
|
|
449
|
+
break;
|
|
450
|
+
|
|
451
|
+
case 29:
|
|
452
|
+
if (accepted.length) {
|
|
453
|
+
countRef.current = count;
|
|
454
|
+
dispatch({
|
|
455
|
+
type: _assets.ASSET_ACTIONS.ADD_ASSETS,
|
|
456
|
+
payload: accepted
|
|
457
|
+
}); // Sin `await`: la subida corre en segundo plano y la bandeja ya muestra
|
|
458
|
+
// los archivos con su barra de progreso.
|
|
459
|
+
|
|
460
|
+
uploadAssets(accepted);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
setNotices(avisos);
|
|
464
|
+
return _context3.abrupt("return", {
|
|
465
|
+
accepted: accepted,
|
|
466
|
+
notices: avisos
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
case 32:
|
|
470
|
+
case "end":
|
|
471
|
+
return _context3.stop();
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}, _callee3);
|
|
475
|
+
})), [dispatch, setNotices, uploadAssets]);
|
|
476
|
+
var removeAsset = (0, _react.useCallback)(function (asset) {
|
|
477
|
+
// El reducer es puro, así que el object URL se libera aquí.
|
|
478
|
+
(0, _assets2.revokeAssetUrl)(asset);
|
|
479
|
+
countRef.current = Math.max(0, countRef.current - 1);
|
|
480
|
+
dispatch({
|
|
481
|
+
type: _assets.ASSET_ACTIONS.REMOVE_ASSET,
|
|
482
|
+
payload: {
|
|
483
|
+
key: (0, _assets2.assetKey)(asset)
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
}, [dispatch]);
|
|
487
|
+
var changeAssetType = (0, _react.useCallback)(function (index, assetType) {
|
|
488
|
+
var target = assetsRef.current[index];
|
|
489
|
+
if (!target) return;
|
|
490
|
+
dispatch({
|
|
491
|
+
type: _assets.ASSET_ACTIONS.UPDATE_ASSET,
|
|
492
|
+
payload: {
|
|
493
|
+
key: (0, _assets2.assetKey)(target),
|
|
494
|
+
changes: {
|
|
495
|
+
assetType: assetType
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
}, [dispatch]);
|
|
500
|
+
var clearNotices = (0, _react.useCallback)(function () {
|
|
501
|
+
return setNotices([]);
|
|
502
|
+
}, [setNotices]);
|
|
503
|
+
return {
|
|
504
|
+
assets: assets,
|
|
505
|
+
notices: notices,
|
|
506
|
+
remaining: Math.max(0, _assetTypes.MAX_ASSETS_PER_PRODUCT - assets.length),
|
|
507
|
+
pending: assets.filter(function (asset) {
|
|
508
|
+
return asset.status === _assets2.ASSET_STATUS.PENDING;
|
|
509
|
+
}),
|
|
510
|
+
addFiles: addFiles,
|
|
511
|
+
removeAsset: removeAsset,
|
|
512
|
+
changeAssetType: changeAssetType,
|
|
513
|
+
replaceAll: replaceAll,
|
|
514
|
+
startUpload: startUpload,
|
|
515
|
+
setProgress: setProgress,
|
|
516
|
+
completeUpload: completeUpload,
|
|
517
|
+
failUpload: failUpload,
|
|
518
|
+
clearNotices: clearNotices
|
|
519
|
+
};
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
exports.useDigitalAssets = useDigitalAssets;
|