contentoh-components-library 21.6.49 → 21.6.50
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/organisms/PropagateChangesModal/index.js +12 -1
- package/dist/components/organisms/PropagateChangesModal/non-propagable.js +45 -0
- package/dist/components/pages/ProviderProductEdition/index.js +11 -1
- 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 +605 -414
- package/dist/components/pages/RetailerProductEdition/stories/ImageEditor.stories.js +1 -0
- package/dist/components/pages/RetailerProductEdition/stories/TextEditor.stories.js +45 -145
- 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/organisms/PropagateChangesModal/index.js +11 -1
- package/src/components/organisms/PropagateChangesModal/non-propagable.js +29 -0
- package/src/components/pages/ProviderProductEdition/index.js +10 -1
- 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 +366 -140
- package/src/components/pages/RetailerProductEdition/stories/ImageEditor.stories.js +1 -0
- package/src/components/pages/RetailerProductEdition/stories/TextEditor.stories.js +51 -104
- 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
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
faFileAudio as IconFileAudio,
|
|
12
12
|
faFileLines as IconFileDefault,
|
|
13
13
|
faFileVideo as IconFileVideo,
|
|
14
|
+
faSwatchbook as IconFileDesign,
|
|
15
|
+
faFilm as IconFileMotion,
|
|
14
16
|
} from "@fortawesome/free-solid-svg-icons";
|
|
15
17
|
import React, { useRef } from "react";
|
|
16
18
|
import { CustomIcon } from "../CustomIcon";
|
|
@@ -41,8 +43,30 @@ export const IconFile = (props) => {
|
|
|
41
43
|
// file csv
|
|
42
44
|
csv: { icon: IconFileCsv, type: "csv" },
|
|
43
45
|
// file image
|
|
44
|
-
svg: { icon: IconFileImg, type: "image" },
|
|
45
46
|
gif: { icon: IconFileImg, type: "image" },
|
|
47
|
+
jpg: { icon: IconFileImg, type: "image" },
|
|
48
|
+
jpeg: { icon: IconFileImg, type: "image" },
|
|
49
|
+
png: { icon: IconFileImg, type: "image" },
|
|
50
|
+
webp: { icon: IconFileImg, type: "image" },
|
|
51
|
+
|
|
52
|
+
// archivos de diseño editables: no se pueden previsualizar en el navegador,
|
|
53
|
+
// así que el icono es lo único que distingue uno de otro en la bandeja.
|
|
54
|
+
psd: { icon: IconFileDesign, type: "design" },
|
|
55
|
+
psb: { icon: IconFileDesign, type: "design" },
|
|
56
|
+
ai: { icon: IconFileDesign, type: "design" },
|
|
57
|
+
eps: { icon: IconFileDesign, type: "design" },
|
|
58
|
+
indd: { icon: IconFileDesign, type: "design" },
|
|
59
|
+
idml: { icon: IconFileDesign, type: "design" },
|
|
60
|
+
sketch: { icon: IconFileDesign, type: "design" },
|
|
61
|
+
fig: { icon: IconFileDesign, type: "design" },
|
|
62
|
+
xd: { icon: IconFileDesign, type: "design" },
|
|
63
|
+
afphoto: { icon: IconFileDesign, type: "design" },
|
|
64
|
+
afdesign: { icon: IconFileDesign, type: "design" },
|
|
65
|
+
svg: { icon: IconFileDesign, type: "design" },
|
|
66
|
+
|
|
67
|
+
// proyectos de animación y edición de video
|
|
68
|
+
aep: { icon: IconFileMotion, type: "motion" },
|
|
69
|
+
prproj: { icon: IconFileMotion, type: "motion" },
|
|
46
70
|
// file pdf
|
|
47
71
|
pdf: { icon: IconFilePdf, type: "pdf" },
|
|
48
72
|
// file power point
|
|
@@ -60,6 +84,7 @@ export const IconFile = (props) => {
|
|
|
60
84
|
rar: { icon: IconFileZip, type: "zip" },
|
|
61
85
|
zip: { icon: IconFileZip, type: "zip" },
|
|
62
86
|
tar: { icon: IconFileZip, type: "zip" },
|
|
87
|
+
"7z": { icon: IconFileZip, type: "zip" },
|
|
63
88
|
|
|
64
89
|
// file code
|
|
65
90
|
xml: { icon: IconFileCode, type: "code" },
|
|
@@ -80,6 +105,8 @@ export const IconFile = (props) => {
|
|
|
80
105
|
// file video
|
|
81
106
|
avi: { icon: IconFileVideo, type: "video" },
|
|
82
107
|
mp4: { icon: IconFileVideo, type: "video" },
|
|
108
|
+
mov: { icon: IconFileVideo, type: "video" },
|
|
109
|
+
webm: { icon: IconFileVideo, type: "video" },
|
|
83
110
|
mpeg: { icon: IconFileVideo, type: "video" },
|
|
84
111
|
mpg: { icon: IconFileVideo, type: "video" },
|
|
85
112
|
wmv: { icon: IconFileVideo, type: "video" },
|
|
@@ -52,6 +52,14 @@ export const Container = styled.div`
|
|
|
52
52
|
color: #1c778e;
|
|
53
53
|
border-color: #1c778e;
|
|
54
54
|
}
|
|
55
|
+
&.icon-design .icon {
|
|
56
|
+
color: #2a5cb8;
|
|
57
|
+
border-color: #2a5cb8;
|
|
58
|
+
}
|
|
59
|
+
&.icon-motion .icon {
|
|
60
|
+
color: #6d3fc0;
|
|
61
|
+
border-color: #6d3fc0;
|
|
62
|
+
}
|
|
55
63
|
&.icon-video .icon {
|
|
56
64
|
color: #a15bb1;
|
|
57
65
|
border-color: #a15bb1;
|
|
@@ -26,3 +26,41 @@ ProductImageDefault.args = {
|
|
|
26
26
|
imageType: "main-image",
|
|
27
27
|
altText: "default image",
|
|
28
28
|
};
|
|
29
|
+
|
|
30
|
+
/*
|
|
31
|
+
Las escalas de video, lado a lado. Las cuatro primeras son miniaturas: sin
|
|
32
|
+
controles, con su duración y un botón de play, y todo el recuadro clicable.
|
|
33
|
+
Las dos últimas son reproductores de verdad.
|
|
34
|
+
*/
|
|
35
|
+
const VIDEO =
|
|
36
|
+
"https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4";
|
|
37
|
+
|
|
38
|
+
const Escala = ({ imageType }) => (
|
|
39
|
+
<figure style={{ margin: 0 }}>
|
|
40
|
+
<ProductImage
|
|
41
|
+
img={VIDEO}
|
|
42
|
+
ext="mp4"
|
|
43
|
+
imageType={imageType}
|
|
44
|
+
altText="video de producto"
|
|
45
|
+
onClick={() => console.log("clic en", imageType)}
|
|
46
|
+
/>
|
|
47
|
+
<figcaption style={{ fontFamily: "Lato", fontSize: 11, marginTop: 4 }}>
|
|
48
|
+
{imageType}
|
|
49
|
+
</figcaption>
|
|
50
|
+
</figure>
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
export const VideoMiniaturas = () => (
|
|
54
|
+
<div style={{ display: "flex", gap: 24, alignItems: "flex-start" }}>
|
|
55
|
+
<Escala imageType="carousel-image" />
|
|
56
|
+
<Escala imageType="catalogue-image-small-size" />
|
|
57
|
+
<Escala imageType="catalogue-modal-image" />
|
|
58
|
+
<Escala imageType="catalogue-image-big-size" />
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
export const VideoReproductor = () => (
|
|
63
|
+
<div style={{ display: "flex", gap: 24, alignItems: "flex-start" }}>
|
|
64
|
+
<Escala imageType="main-image" />
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
@@ -1,29 +1,104 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useState } from "react";
|
|
2
2
|
import ReactImageFallback from "react-image-fallback";
|
|
3
|
+
|
|
4
|
+
import { Container } from "./styles";
|
|
5
|
+
import {
|
|
6
|
+
ASSET_PREVIEW,
|
|
7
|
+
formatDuration,
|
|
8
|
+
resolvePreviewKind,
|
|
9
|
+
} from "../../../global-files/digital-assets/assetTypes";
|
|
3
10
|
import defaultProductImage from "../../../assets/images/defaultImages/defaultProductImage.png";
|
|
4
11
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
/*
|
|
13
|
+
Tamaños en los que un video se muestra como miniatura y no como reproductor.
|
|
14
|
+
|
|
15
|
+
Los controles nativos del navegador necesitan alrededor de 150px de ancho
|
|
16
|
+
para ser usables; en un recuadro de 60 o de 85 se pintan encimados y no se
|
|
17
|
+
puede ni dar play. Peor aún, un <video> con controles se traga el clic, así
|
|
18
|
+
que un video en el carrusel no se podía ni seleccionar: `onClick` nunca
|
|
19
|
+
llegaba a correr.
|
|
20
|
+
|
|
21
|
+
En estos tamaños se muestra el primer fotograma con su duración y un botón de
|
|
22
|
+
play, y el recuadro completo lleva al visor grande, que es donde el video sí
|
|
23
|
+
se ve y se controla.
|
|
24
|
+
*/
|
|
25
|
+
const THUMBNAIL_TYPES = [
|
|
26
|
+
"carousel-image",
|
|
27
|
+
"catalogue-image-small-size",
|
|
28
|
+
"catalogue-image-big-size",
|
|
29
|
+
"catalogue-modal-image",
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const PlayBadge = () => (
|
|
33
|
+
<span className="play-badge" aria-hidden="true">
|
|
34
|
+
<svg viewBox="0 0 24 24">
|
|
35
|
+
<circle cx="12" cy="12" r="11" />
|
|
36
|
+
<polygon points="9.5,7.5 17,12 9.5,16.5" />
|
|
37
|
+
</svg>
|
|
38
|
+
</span>
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Imagen —o video— de producto.
|
|
43
|
+
*
|
|
44
|
+
* El tipo de archivo se decide por `ext`/`mime` cuando quien llama los conoce.
|
|
45
|
+
* Antes se comprobaba con `/\.mp4$/` sobre la URL, y nunca acertaba: la URL
|
|
46
|
+
* guardada apunta al redimensionador de CloudFront y no conserva la extensión.
|
|
47
|
+
* El regex queda solo como respaldo para los llamadores que aún mandan `img`
|
|
48
|
+
* a secas.
|
|
49
|
+
*/
|
|
50
|
+
export const ProductImage = ({ img, imageType, altText, onClick, ext, mime, poster }) => {
|
|
51
|
+
// La duración no está en la base: se toma de los metadatos, que el navegador
|
|
52
|
+
// ya descarga por el `preload="metadata"`. No cuesta una petición extra.
|
|
53
|
+
const [duration, setDuration] = useState(null);
|
|
54
|
+
|
|
55
|
+
const kind =
|
|
56
|
+
ext || mime
|
|
57
|
+
? resolvePreviewKind({ ext, mime })
|
|
58
|
+
: /\.mp4($|[?#])/i.test(img || "")
|
|
59
|
+
? ASSET_PREVIEW.VIDEO
|
|
60
|
+
: ASSET_PREVIEW.IMAGE;
|
|
61
|
+
|
|
62
|
+
if (kind === ASSET_PREVIEW.VIDEO) {
|
|
63
|
+
const isThumbnail = THUMBNAIL_TYPES.includes(imageType);
|
|
64
|
+
|
|
18
65
|
return (
|
|
19
|
-
<Container
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
66
|
+
<Container
|
|
67
|
+
className={`${imageType} ${isThumbnail ? "video-thumbnail" : "video-player"}`}
|
|
68
|
+
onClick={isThumbnail ? onClick : undefined}
|
|
69
|
+
>
|
|
70
|
+
<video
|
|
71
|
+
// `#t=0.5` hace que el navegador muestre el fotograma del medio
|
|
72
|
+
// segundo en lugar de un cuadro negro, sin descargar el archivo
|
|
73
|
+
// completo.
|
|
74
|
+
src={poster ? img : `${img}#t=0.5`}
|
|
75
|
+
poster={poster || undefined}
|
|
76
|
+
controls={!isThumbnail}
|
|
77
|
+
muted={isThumbnail}
|
|
78
|
+
playsInline
|
|
79
|
+
preload="metadata"
|
|
80
|
+
onLoadedMetadata={(e) => setDuration(e.target.duration)}
|
|
25
81
|
/>
|
|
82
|
+
{isThumbnail && (
|
|
83
|
+
<>
|
|
84
|
+
<PlayBadge />
|
|
85
|
+
{duration ? (
|
|
86
|
+
<span className="video-duration">{formatDuration(duration)}</span>
|
|
87
|
+
) : null}
|
|
88
|
+
</>
|
|
89
|
+
)}
|
|
26
90
|
</Container>
|
|
27
91
|
);
|
|
28
92
|
}
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<Container className={imageType}>
|
|
96
|
+
<ReactImageFallback
|
|
97
|
+
src={img}
|
|
98
|
+
fallbackImage={defaultProductImage}
|
|
99
|
+
alt={altText}
|
|
100
|
+
onClick={onClick}
|
|
101
|
+
/>
|
|
102
|
+
</Container>
|
|
103
|
+
);
|
|
29
104
|
};
|
|
@@ -1,14 +1,92 @@
|
|
|
1
1
|
import styled from "styled-components";
|
|
2
|
+
import { FontFamily } from "../../../global-files/variables";
|
|
2
3
|
|
|
3
4
|
export const Container = styled.div`
|
|
4
5
|
width: fit-content;
|
|
5
6
|
|
|
6
|
-
img
|
|
7
|
+
img,
|
|
8
|
+
video {
|
|
7
9
|
object-fit: contain;
|
|
8
10
|
}
|
|
9
11
|
|
|
12
|
+
/*
|
|
13
|
+
Miniatura de video: fotograma quieto, con su duración y un botón de play.
|
|
14
|
+
El clic lo recibe el contenedor, no el <video>, para que lleve al visor
|
|
15
|
+
grande en vez de quedarse peleando con los controles.
|
|
16
|
+
*/
|
|
17
|
+
&.video-thumbnail {
|
|
18
|
+
position: relative;
|
|
19
|
+
cursor: pointer;
|
|
20
|
+
|
|
21
|
+
video {
|
|
22
|
+
pointer-events: none;
|
|
23
|
+
background: #000;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.play-badge {
|
|
27
|
+
position: absolute;
|
|
28
|
+
top: 50%;
|
|
29
|
+
left: 50%;
|
|
30
|
+
transform: translate(-50%, -50%);
|
|
31
|
+
width: 32px;
|
|
32
|
+
height: 32px;
|
|
33
|
+
pointer-events: none;
|
|
34
|
+
|
|
35
|
+
svg {
|
|
36
|
+
width: 100%;
|
|
37
|
+
height: 100%;
|
|
38
|
+
display: block;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
circle {
|
|
42
|
+
fill: rgba(0, 0, 0, 0.55);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
polygon {
|
|
46
|
+
fill: #ffffff;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
&:hover .play-badge circle {
|
|
51
|
+
fill: rgba(0, 0, 0, 0.75);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.video-duration {
|
|
55
|
+
position: absolute;
|
|
56
|
+
right: 4px;
|
|
57
|
+
bottom: 4px;
|
|
58
|
+
padding: 0 4px;
|
|
59
|
+
border-radius: 3px;
|
|
60
|
+
background: rgba(0, 0, 0, 0.75);
|
|
61
|
+
color: #ffffff;
|
|
62
|
+
font-family: ${FontFamily.Lato};
|
|
63
|
+
font-size: 10px;
|
|
64
|
+
line-height: 15px;
|
|
65
|
+
pointer-events: none;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* En 60px un badge de 32 tapa el fotograma completo. */
|
|
70
|
+
&.catalogue-image-small-size.video-thumbnail .play-badge {
|
|
71
|
+
width: 20px;
|
|
72
|
+
height: 20px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
&.carousel-image.video-thumbnail {
|
|
76
|
+
.play-badge {
|
|
77
|
+
width: 22px;
|
|
78
|
+
height: 22px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.video-duration {
|
|
82
|
+
font-size: 9px;
|
|
83
|
+
line-height: 13px;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
10
87
|
&.carousel-image {
|
|
11
|
-
img
|
|
88
|
+
img,
|
|
89
|
+
video {
|
|
12
90
|
width: 85px;
|
|
13
91
|
height: 55px;
|
|
14
92
|
cursor: pointer;
|
|
@@ -20,7 +98,8 @@ export const Container = styled.div`
|
|
|
20
98
|
}
|
|
21
99
|
|
|
22
100
|
&.main-image {
|
|
23
|
-
img
|
|
101
|
+
img,
|
|
102
|
+
video {
|
|
24
103
|
width: 340px;
|
|
25
104
|
height: 295px;
|
|
26
105
|
}
|
|
@@ -30,14 +109,16 @@ export const Container = styled.div`
|
|
|
30
109
|
display: flex;
|
|
31
110
|
justify-content: center;
|
|
32
111
|
align-items: center;
|
|
33
|
-
img
|
|
112
|
+
img,
|
|
113
|
+
video {
|
|
34
114
|
width: 179px;
|
|
35
115
|
height: 179px;
|
|
36
116
|
}
|
|
37
117
|
}
|
|
38
118
|
|
|
39
119
|
&.catalogue-image-small-size {
|
|
40
|
-
img
|
|
120
|
+
img,
|
|
121
|
+
video {
|
|
41
122
|
width: 60px;
|
|
42
123
|
height: 60px;
|
|
43
124
|
}
|
|
@@ -47,7 +128,9 @@ export const Container = styled.div`
|
|
|
47
128
|
flex: 1 0 50%;
|
|
48
129
|
cursor: pointer;
|
|
49
130
|
|
|
50
|
-
img
|
|
131
|
+
img,
|
|
132
|
+
|
|
133
|
+
video {
|
|
51
134
|
width: 168px;
|
|
52
135
|
height: 108px;
|
|
53
136
|
}
|
|
@@ -60,7 +143,9 @@ export const Container = styled.div`
|
|
|
60
143
|
justify-content: center;
|
|
61
144
|
align-items: center;
|
|
62
145
|
|
|
63
|
-
img
|
|
146
|
+
img,
|
|
147
|
+
|
|
148
|
+
video {
|
|
64
149
|
width: 710px;
|
|
65
150
|
height: 710px;
|
|
66
151
|
}
|
|
@@ -2,10 +2,10 @@ import { Container } from "./styles";
|
|
|
2
2
|
import { ProductImage } from "../../atoms/ProductImage/index";
|
|
3
3
|
import { Button } from "../../atoms/GeneralButton/index";
|
|
4
4
|
|
|
5
|
-
export const EditionActiveImage = ({ img, altText, setShowModal }) => {
|
|
5
|
+
export const EditionActiveImage = ({ img, altText, setShowModal, ext }) => {
|
|
6
6
|
return (
|
|
7
7
|
<Container>
|
|
8
|
-
<ProductImage img={img} imageType="main-image" altText={altText} />
|
|
8
|
+
<ProductImage img={img} imageType="main-image" altText={altText} ext={ext} />
|
|
9
9
|
<Button
|
|
10
10
|
buttonType="open-modal-button"
|
|
11
11
|
onClick={() => setShowModal(true)}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
|
|
3
|
+
import { GalleryAssetElement } from "./index";
|
|
4
|
+
import { GalleryHeader } from "../GalleryHeader";
|
|
5
|
+
import {
|
|
6
|
+
fromBackendList,
|
|
7
|
+
ASSET_STATUS,
|
|
8
|
+
} from "../../../global-files/digital-assets/assets.adapter";
|
|
9
|
+
import { backendAssetsMock } from "../../../global-files/digital-assets/assets.mock";
|
|
10
|
+
|
|
11
|
+
const SAMPLES = {
|
|
12
|
+
mp4: "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4",
|
|
13
|
+
gif: "https://upload.wikimedia.org/wikipedia/commons/2/2c/Rotating_earth_%28large%29.gif",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/* Las claves del mock apuntan a S3, que todavía no tiene nada: se sustituyen
|
|
17
|
+
por archivos públicos para poder ver el componente. */
|
|
18
|
+
const assets = fromBackendList(backendAssetsMock).map((asset) => ({
|
|
19
|
+
...asset,
|
|
20
|
+
url:
|
|
21
|
+
asset.ext === "mp4" ? SAMPLES.mp4 : asset.ext === "gif" ? SAMPLES.gif : "",
|
|
22
|
+
poster: "",
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
export default {
|
|
26
|
+
title: "Components/molecules/GalleryAssetElement",
|
|
27
|
+
component: GalleryAssetElement,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const Template = (args) => <GalleryAssetElement {...args} />;
|
|
31
|
+
|
|
32
|
+
export const Fila = Template.bind({});
|
|
33
|
+
Fila.args = { asset: assets[0], index: 0 };
|
|
34
|
+
|
|
35
|
+
export const Cuadricula = Template.bind({});
|
|
36
|
+
Cuadricula.args = { asset: assets[0], index: 0, gridLayout: true };
|
|
37
|
+
|
|
38
|
+
export const Subiendo = Template.bind({});
|
|
39
|
+
Subiendo.args = {
|
|
40
|
+
asset: { ...assets[2], status: ASSET_STATUS.UPLOADING, progress: 42 },
|
|
41
|
+
index: 0,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const ConError = Template.bind({});
|
|
45
|
+
ConError.args = {
|
|
46
|
+
asset: {
|
|
47
|
+
...assets[3],
|
|
48
|
+
status: ASSET_STATUS.ERROR,
|
|
49
|
+
error: "No se pudo subir el archivo. Intenta de nuevo.",
|
|
50
|
+
},
|
|
51
|
+
index: 0,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** La bandeja completa con su encabezado: es el uso real del componente. */
|
|
55
|
+
export const BandejaCompleta = () => {
|
|
56
|
+
const [selected, setSelected] = useState([]);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div style={{ width: 900 }}>
|
|
60
|
+
<GalleryHeader
|
|
61
|
+
variant="asset"
|
|
62
|
+
checkAllId="check-all-assets"
|
|
63
|
+
itemSelector="[id^='gallery-asset-']"
|
|
64
|
+
setCheckAll={() => {}}
|
|
65
|
+
setSelectedImages={setSelected}
|
|
66
|
+
/>
|
|
67
|
+
{assets.map((asset, index) => (
|
|
68
|
+
<GalleryAssetElement
|
|
69
|
+
key={asset.id}
|
|
70
|
+
asset={asset}
|
|
71
|
+
index={index}
|
|
72
|
+
selectedAssets={selected}
|
|
73
|
+
setSelectedAssets={setSelected}
|
|
74
|
+
setCheckAll={() => {}}
|
|
75
|
+
changeAsset={({ value }) => console.log("nuevo tipo:", value)}
|
|
76
|
+
onRemove={(item) => console.log("quitar:", item.name)}
|
|
77
|
+
/>
|
|
78
|
+
))}
|
|
79
|
+
<p style={{ fontFamily: "Lato", fontSize: 12, marginTop: 12 }}>
|
|
80
|
+
Seleccionados: {selected.length}
|
|
81
|
+
</p>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/** Comparación de los dos encabezados, para verificar que no se movió el de imágenes. */
|
|
87
|
+
export const EncabezadosComparados = () => (
|
|
88
|
+
<div style={{ width: 900 }}>
|
|
89
|
+
<GalleryHeader
|
|
90
|
+
variant="image"
|
|
91
|
+
setCheckAll={() => {}}
|
|
92
|
+
setSelectedImages={() => {}}
|
|
93
|
+
/>
|
|
94
|
+
<GalleryHeader
|
|
95
|
+
variant="asset"
|
|
96
|
+
checkAllId="check-all-assets-demo"
|
|
97
|
+
itemSelector="[id^='gallery-asset-demo-']"
|
|
98
|
+
setCheckAll={() => {}}
|
|
99
|
+
setSelectedImages={() => {}}
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { Container } from "./styles";
|
|
2
|
+
import { AssetPreview } from "../../atoms/AssetPreview";
|
|
3
|
+
import { CheckBox } from "../../atoms/CheckBox";
|
|
4
|
+
import Select from "../../atoms/Select";
|
|
5
|
+
import {
|
|
6
|
+
ASSET_PREVIEW,
|
|
7
|
+
assetTypeOptions,
|
|
8
|
+
formatBytes,
|
|
9
|
+
resolvePreviewKind,
|
|
10
|
+
} from "../../../global-files/digital-assets/assetTypes";
|
|
11
|
+
import {
|
|
12
|
+
ASSET_STATUS,
|
|
13
|
+
assetKey,
|
|
14
|
+
} from "../../../global-files/digital-assets/assets.adapter";
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
Fila de un activo digital en la bandeja.
|
|
18
|
+
|
|
19
|
+
Es un componente aparte de `GalleryElement` a propósito, no una bifurcación
|
|
20
|
+
dentro de él. Un activo no comparte casi nada con una imagen de producto: no
|
|
21
|
+
tiene tipo de toma, ni leyenda, ni panel de validación, ni comparación contra
|
|
22
|
+
la versión auditable. Meterlo en el mismo archivo habría partido su render en
|
|
23
|
+
dos mitades y puesto en riesgo la bandeja de imágenes, que ya funciona.
|
|
24
|
+
|
|
25
|
+
Lo que sí comparte —rejilla, tamaños, colores— vive en los estilos, calcados
|
|
26
|
+
de `GalleryElement` para que las dos listas se vean como una sola tabla.
|
|
27
|
+
*/
|
|
28
|
+
export const GalleryAssetElement = ({
|
|
29
|
+
asset = {},
|
|
30
|
+
index = 0,
|
|
31
|
+
gridLayout = false,
|
|
32
|
+
id,
|
|
33
|
+
label,
|
|
34
|
+
selectedAssets = [],
|
|
35
|
+
setSelectedAssets,
|
|
36
|
+
setCheckAll,
|
|
37
|
+
changeAsset,
|
|
38
|
+
onRemove,
|
|
39
|
+
readOnly = false,
|
|
40
|
+
checkAllId = "check-all-assets",
|
|
41
|
+
itemSelector = "[id^='gallery-asset-']",
|
|
42
|
+
}) => {
|
|
43
|
+
const elementId = id || `gallery-asset-${index}`;
|
|
44
|
+
const key = assetKey(asset);
|
|
45
|
+
const isUploading = asset.status === ASSET_STATUS.UPLOADING;
|
|
46
|
+
const hasError = asset.status === ASSET_STATUS.ERROR;
|
|
47
|
+
|
|
48
|
+
const checkOnChange = (e) => {
|
|
49
|
+
const chkGlobal = document.getElementById(checkAllId);
|
|
50
|
+
const checkboxList = document.querySelectorAll(itemSelector);
|
|
51
|
+
let temp = selectedAssets.slice();
|
|
52
|
+
|
|
53
|
+
// Se compara por `assetKey` y no por `id`: un activo recién soltado todavía
|
|
54
|
+
// no tiene id del backend, y comparando por id todos los pendientes serían
|
|
55
|
+
// "el mismo" y se quitarían juntos de la selección.
|
|
56
|
+
temp = e.target.checked
|
|
57
|
+
? [...temp, asset]
|
|
58
|
+
: temp.filter((item) => assetKey(item) !== key);
|
|
59
|
+
|
|
60
|
+
let counter = 0;
|
|
61
|
+
checkboxList?.forEach((chk) => chk.checked && counter++);
|
|
62
|
+
if (chkGlobal) chkGlobal.checked = checkboxList.length === counter;
|
|
63
|
+
setCheckAll?.(checkboxList.length === counter);
|
|
64
|
+
setSelectedAssets?.(temp);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/*
|
|
68
|
+
Abrir el activo en otra pestaña.
|
|
69
|
+
|
|
70
|
+
Sirve para TODOS los tipos y por eso se prefiere a un visor propio: un video
|
|
71
|
+
se reproduce con el reproductor del navegador, un PDF se abre, y un .psd se
|
|
72
|
+
descarga. Sin esto, la bandeja mostraba el archivo pero no había forma de
|
|
73
|
+
verlo — un video quedaba como un fotograma fijo sin botón de reproducir.
|
|
74
|
+
|
|
75
|
+
Solo cuando ya está en S3: un activo recién soltado apunta al archivo del
|
|
76
|
+
disco del usuario y abrirlo no le diría nada nuevo.
|
|
77
|
+
*/
|
|
78
|
+
const abrir = () => {
|
|
79
|
+
if (!asset.id || !asset.url) return;
|
|
80
|
+
window.open(asset.url, "_blank", "noopener,noreferrer");
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const onChangeType = (e) => {
|
|
84
|
+
changeAsset?.({
|
|
85
|
+
action: "changeAssetInfo",
|
|
86
|
+
attribute: "assetType",
|
|
87
|
+
value: e.target.value,
|
|
88
|
+
index,
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<Container
|
|
94
|
+
className={`${gridLayout ? "grid-gallery" : "row-gallery"} ${
|
|
95
|
+
hasError ? "asset-error" : ""
|
|
96
|
+
}`.trim()}
|
|
97
|
+
>
|
|
98
|
+
<div className="buttons-container">
|
|
99
|
+
<CheckBox id={elementId} label={label} onChange={checkOnChange} />
|
|
100
|
+
</div>
|
|
101
|
+
<div className="asset-container">
|
|
102
|
+
<AssetPreview
|
|
103
|
+
asset={asset}
|
|
104
|
+
size={`catalogue-image-${gridLayout ? "big" : "small"}-size`}
|
|
105
|
+
/*
|
|
106
|
+
En cuadrícula hay sitio para el reproductor; en la fila, de 60px,
|
|
107
|
+
los controles no caben y estorban al clic.
|
|
108
|
+
*/
|
|
109
|
+
controls={gridLayout && resolvePreviewKind(asset) === ASSET_PREVIEW.VIDEO}
|
|
110
|
+
onClick={abrir}
|
|
111
|
+
/>
|
|
112
|
+
<div className="information-container">
|
|
113
|
+
<p
|
|
114
|
+
className={asset.id ? "asset-name asset-link" : "asset-name"}
|
|
115
|
+
title={asset.id ? `Abrir ${asset.name}` : asset.name}
|
|
116
|
+
onClick={abrir}
|
|
117
|
+
>
|
|
118
|
+
{asset.name}
|
|
119
|
+
</p>
|
|
120
|
+
{!gridLayout && (
|
|
121
|
+
<>
|
|
122
|
+
<p className="asset-weight">{formatBytes(asset.sizeBytes)}</p>
|
|
123
|
+
<p className="asset-format">{(asset.ext || "").toUpperCase()}</p>
|
|
124
|
+
<Select
|
|
125
|
+
width="100%"
|
|
126
|
+
placeholder="Tipo de activo"
|
|
127
|
+
options={assetTypeOptions()}
|
|
128
|
+
valueSelected={asset.assetType}
|
|
129
|
+
onChange={onChangeType}
|
|
130
|
+
className={readOnly ? "disabled" : ""}
|
|
131
|
+
/>
|
|
132
|
+
</>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
{isUploading && (
|
|
138
|
+
<div className="asset-progress" aria-label="Subiendo activo">
|
|
139
|
+
<div
|
|
140
|
+
className="asset-progress-fill"
|
|
141
|
+
style={{
|
|
142
|
+
width: `${Math.min(100, Math.max(0, asset.progress ?? 0))}%`,
|
|
143
|
+
}}
|
|
144
|
+
/>
|
|
145
|
+
</div>
|
|
146
|
+
)}
|
|
147
|
+
|
|
148
|
+
{hasError && <p className="asset-error-message">{asset.error}</p>}
|
|
149
|
+
|
|
150
|
+
{onRemove && !readOnly && (
|
|
151
|
+
<button
|
|
152
|
+
type="button"
|
|
153
|
+
className="asset-remove"
|
|
154
|
+
onClick={() => onRemove(asset)}
|
|
155
|
+
title="Quitar activo"
|
|
156
|
+
>
|
|
157
|
+
×
|
|
158
|
+
</button>
|
|
159
|
+
)}
|
|
160
|
+
</Container>
|
|
161
|
+
);
|
|
162
|
+
};
|