@vixoniccom/news-internal 0.4.19 → 0.4.20-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/build/index.html +1 -0
  3. package/build/main.js +2 -0
  4. package/build/main.js.LICENSE.txt +39 -0
  5. package/build/test/downloads/cat-2.jpg +0 -0
  6. package/build/test/downloads/cat.jpg +0 -0
  7. package/build/test/downloads/colabra.jpg +0 -0
  8. package/build/test/downloads/custom-template/assets/background.png +0 -0
  9. package/build/test/downloads/custom-template/assets/image-error.png +0 -0
  10. package/build/test/downloads/custom-template/assets/image-loading.gif +0 -0
  11. package/build/test/downloads/custom-template/assets/main-font.ttf +0 -0
  12. package/build/test/downloads/custom-template/main.html +62 -0
  13. package/build/test/downloads/dog.jpg +0 -0
  14. package/build/test/downloads/fondo-noticiario-vitacura.jpg +0 -0
  15. package/build/test/downloads/font-title.otf +0 -0
  16. package/build/test/parameters.json +96 -0
  17. package/build.zip +0 -0
  18. package/configuration/index.ts +3 -109
  19. package/configuration/newsGroup/index.ts +16 -0
  20. package/configuration/newsGroup/newsInputs.ts +107 -0
  21. package/configuration.json +21 -11
  22. package/package.json +14 -12
  23. package/src/components/App.tsx +80 -0
  24. package/src/components/FontLoader.tsx +49 -0
  25. package/src/components/FormattedHtmlText/index.tsx +70 -0
  26. package/src/components/FormattedText/index.tsx +82 -0
  27. package/src/components/GalleryItem/index.tsx +85 -0
  28. package/src/components/GradientImage/index.tsx +18 -0
  29. package/src/components/GradientItem/index.tsx +49 -0
  30. package/src/components/GradientTitle/index.tsx +19 -0
  31. package/src/components/ImageContainer/index.tsx +77 -0
  32. package/src/components/NewsContainer/index.tsx +71 -0
  33. package/src/components/NewsContainer/styles.css +45 -0
  34. package/src/components/QrCodeContainer/index.tsx +38 -0
  35. package/src/components/StandardItem/index.tsx +45 -0
  36. package/src/components/TextContainer/index.tsx +35 -0
  37. package/src/global.d.ts +9 -0
  38. package/src/index.html +11 -0
  39. package/src/main.ts +28 -0
  40. package/src/parameters.d.ts +80 -0
  41. package/src/test/downloads/cat-2.jpg +0 -0
  42. package/src/test/downloads/cat.jpg +0 -0
  43. package/src/test/downloads/colabra.jpg +0 -0
  44. package/src/test/downloads/custom-template/assets/background.png +0 -0
  45. package/src/test/downloads/custom-template/assets/image-error.png +0 -0
  46. package/src/test/downloads/custom-template/assets/image-loading.gif +0 -0
  47. package/src/test/downloads/custom-template/assets/main-font.ttf +0 -0
  48. package/src/test/downloads/custom-template/main.html +62 -0
  49. package/src/test/downloads/dog.jpg +0 -0
  50. package/src/test/downloads/fondo-noticiario-vitacura.jpg +0 -0
  51. package/src/test/downloads/font-title.otf +0 -0
  52. package/src/test/parameters.json +96 -0
  53. package/tsconfig.json +4 -3
@@ -0,0 +1,80 @@
1
+ import React from 'react'
2
+ import { v4 as uuid } from 'uuid'
3
+ import { NewsContainer } from './NewsContainer'
4
+ import { FontLoader } from './FontLoader'
5
+ import dayjs from 'dayjs'
6
+
7
+ export type Props = {
8
+ data?: VixonicData
9
+ start: boolean
10
+ }
11
+
12
+ type State = {
13
+ currentIndex: number
14
+ news: NewsItem[]
15
+ }
16
+
17
+ export class App extends React.Component<Props, State> {
18
+ state: State = { currentIndex: 0, news: [] }
19
+
20
+ constructor(props: Props) {
21
+ super(props)
22
+ this.state = { currentIndex: 0, news: this.parseNews(props.data) }
23
+ this.updateCssVariables(props)
24
+ }
25
+
26
+ componentWillReceiveProps(nextProps: Props) {
27
+ const currentNews = this?.props?.data?.parameters?.news
28
+ const nextNews = nextProps?.data?.parameters?.news
29
+ if ((JSON.stringify(currentNews) !== JSON.stringify(nextNews)) || (JSON.stringify(this.props.data) !== JSON.stringify(nextProps.data))) {
30
+ this.setState({ news: this.parseNews(nextProps.data) })
31
+ }
32
+ this.updateCssVariables(nextProps)
33
+ }
34
+
35
+ updateCssVariables(props: Props) {
36
+ if (!props.data) return
37
+ const { animationSpeed, animationType, template } = props.data.parameters
38
+ const speed = 1750 / (animationSpeed || 1)
39
+ document?.documentElement?.style?.setProperty('--speed', `${speed}ms`)
40
+ document?.documentElement?.style?.setProperty('--enterDelay', `${animationType === 'crossFade' ? 0 : speed}ms`)
41
+ // If gallery make a cross fade with delay to prevent background.
42
+ document?.documentElement?.style?.setProperty('--exitDelay', `${(animationType === 'crossFade' && template === 'gallery') ? speed : 0}ms`)
43
+ }
44
+
45
+ parseNews(data?: VixonicData): NewsItem[] {
46
+ if (!data) return []
47
+ const { news } = data.parameters
48
+ const parsedNews = news?.map((n) => {
49
+ const { image, ...rest } = n
50
+ let parsedImage
51
+ const qrUrl = n.qrCodeUrl
52
+ if (image?.filename) parsedImage = `${data.downloadsPath}/${image.filename}`
53
+ return { ...rest, image: parsedImage, id: uuid(), qrCodeUrl: qrUrl }
54
+ }).filter((n) => {
55
+ const { expirationDate } = n
56
+ const today = dayjs()
57
+ if (!expirationDate) return true
58
+ const expiration = dayjs(expirationDate)
59
+ return today.isBefore(expiration)
60
+ })
61
+ return parsedNews || []
62
+ }
63
+
64
+ render() {
65
+ const { data } = this.props
66
+ if (!data) return null
67
+ return <div style={{
68
+ position: 'absolute',
69
+ top: 0,
70
+ left: 0,
71
+ right: 0,
72
+ bottom: 0,
73
+ overflow: 'hidden',
74
+ backgroundSize: '100% 100%',
75
+ }}>
76
+ <NewsContainer data={data} items={this.state.news} />
77
+ <FontLoader downloadPath={data.downloadsPath} parameters={data.parameters} />
78
+ </div >
79
+ }
80
+ }
@@ -0,0 +1,49 @@
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom'
3
+ import { TextFormat } from '@vixoniccom/modules'
4
+
5
+ type Props<T> = {
6
+ parameters: T
7
+ downloadPath: string
8
+ }
9
+
10
+ export const FontLoader: React.FunctionComponent<Props<any>> = (props) => {
11
+ const fonts = fontParser(props.parameters)
12
+ const fontStyles = fonts.filter((el, pos, arr) => (arr.indexOf(el) === pos)).map((font) => {
13
+ try {
14
+ return `
15
+ @font-face {
16
+ font-family: "${parseFontName(font)}";
17
+ src: url("${parseFontUrl(props.downloadPath, font)}");
18
+ }`
19
+ } catch (err) {
20
+ return ''
21
+ }
22
+ })
23
+ return ReactDOM.createPortal(<style type='text/css'>{fontStyles}</style>, document.getElementsByTagName('head')[0])
24
+ }
25
+
26
+ export function fontParser(parameters: any): string[] {
27
+ const initArr: string[] = []
28
+ return Object.keys(parameters).reduce(
29
+ (fonts, param) => {
30
+ const paramObj = parameters[param]
31
+ if (paramObj && isFont(paramObj)) {
32
+ fonts.push((paramObj.font!.filename || ''))
33
+ }
34
+ return fonts
35
+ }, initArr
36
+ )
37
+ }
38
+
39
+ function isFont(arg: any): arg is TextFormat.Value {
40
+ return arg?.font?.filename !== undefined
41
+ }
42
+
43
+ export const parseFontName = (filename: string): string => {
44
+ return filename.replace('.', '-')
45
+ }
46
+
47
+ export const parseFontUrl = (path: string, filename: string): string => {
48
+ return path + '/' + filename
49
+ }
@@ -0,0 +1,70 @@
1
+ import React from 'react'
2
+ import { TextFormat } from '@vixoniccom/modules'
3
+ import { parseFontName } from '../FontLoader'
4
+ import DOMPurify from 'dompurify'
5
+
6
+ const sanitizeHtml = (html?: string): string => {
7
+ return html ? DOMPurify.sanitize(html, {
8
+ ALLOWED_TAGS: ['span', 'p', 'strong', 'b', 'em', 'i', 'u', 'br'],
9
+ ALLOWED_ATTR: ['class'],
10
+ }) : ''
11
+ }
12
+
13
+ const alignments = {
14
+ center: 'center',
15
+ left: 'flex-start',
16
+ right: 'flex-end'
17
+ }
18
+
19
+ type Props = {
20
+ html: string
21
+ unit: 'px' | '%' | 'em' | 'vmin' | 'vh'
22
+ defaults: {
23
+ fontSize: number,
24
+ fontColor: string
25
+ alignment: 'left' | 'center' | 'right'
26
+ }
27
+ format?: TextFormat.Value
28
+ multiline?: boolean
29
+ lineHeight?: number
30
+ style?: React.CSSProperties
31
+ }
32
+
33
+ const getHorizontalAlignment = (alignment: any) => {
34
+ if (alignment) {
35
+ return alignments.hasOwnProperty(alignment) ? (alignments as any)[alignment] : 'flex-start'
36
+ }
37
+ return 'flex-start'
38
+ }
39
+
40
+ export const FormattedText: React.FunctionComponent<Props> = (props) => {
41
+ const { format, defaults, html } = props
42
+ const font = format?.font
43
+ const alignment = format?.alignment
44
+ const fontColor = format?.fontColor
45
+ const fontSize = format?.fontSize
46
+ const containerStyle = Object.assign({
47
+ display: 'inline-flex',
48
+ justifyContent: getHorizontalAlignment(alignment?.horizontal || defaults.alignment)
49
+ }, props.style)
50
+
51
+ return <div style={containerStyle}>
52
+ <div style={{
53
+ color: fontColor || defaults.fontColor,
54
+ width: '100%',
55
+ fontFamily: font?.filename && `"${parseFontName(font.filename)}"` || undefined,
56
+ fontSize: `${fontSize || defaults.fontSize}${props.unit}`,
57
+ textAlignLast: alignment?.horizontal || defaults.alignment,
58
+ lineHeight: props.lineHeight,
59
+ overflow: 'hidden',
60
+ whiteSpace: props.multiline ? 'pre-wrap' : 'nowrap'
61
+ }}>
62
+ <div className='ql-editor'
63
+ style={{ padding: 0, overflow: 'hidden', textAlign: alignment?.horizontal || defaults.alignment }}
64
+ dangerouslySetInnerHTML={{ __html: sanitizeHtml(html) }}
65
+ />
66
+ </div>
67
+ </div>
68
+ }
69
+
70
+ export default FormattedText
@@ -0,0 +1,82 @@
1
+ import React from 'react'
2
+
3
+ const alignments = {
4
+ center: 'center',
5
+ left: 'flex-start',
6
+ right: 'flex-end'
7
+ }
8
+
9
+ type Aligment = {
10
+ horizontal: keyof typeof alignments,
11
+ vertical: keyof typeof alignments
12
+ }
13
+
14
+ interface Props {
15
+ format?: any
16
+ lineHeight?: number
17
+ maxChar?: number
18
+ style?: React.CSSProperties
19
+ text: string
20
+ unit?: string
21
+ paddingBottom?: string
22
+ paddingTop?: string
23
+ multiline?: boolean
24
+ }
25
+
26
+ export const FormattedText: React.FunctionComponent<Props> = ({ text, format, maxChar, lineHeight, style, unit, paddingBottom, paddingTop, multiline }) => {
27
+ const trimText = (text: any, maxChar: any) => {
28
+ const isValid = maxChar && maxChar >= 3
29
+ if (isValid && (text && text.length > maxChar) || false) {
30
+ const returnText = text.substring(0, maxChar - 3)
31
+ return `${returnText.trim()}...`
32
+ }
33
+ return text
34
+ }
35
+
36
+ const checkNested = (obj: any, path: string): boolean => {
37
+ return path.split('.').every(segment => {
38
+ if (obj && obj.hasOwnProperty(segment)) {
39
+ obj = obj[segment]
40
+ return true
41
+ }
42
+ return false
43
+ })
44
+ }
45
+
46
+ const getHorizontalAlignment = (alignment: Aligment) => {
47
+ if (alignment) {
48
+ const hA = alignment.horizontal
49
+ return alignments.hasOwnProperty(hA) ? alignments[alignment.horizontal] : 'flex-start'
50
+ }
51
+ return 'flex-start'
52
+ }
53
+
54
+ const renderText = maxChar ? trimText(text, maxChar) : text
55
+ const containerStyle = Object.assign({
56
+ display: 'inline-flex',
57
+ justifyContent: getHorizontalAlignment(format.alignment)
58
+ }, style)
59
+
60
+ return (
61
+ <div style={containerStyle}>
62
+ <span style={{
63
+ color: format?.fontColor || '#ffffff',
64
+ fontFamily: checkNested(format, 'font.filename') ? `'${format?.font?.filename?.replace('.', '-')}'` : '',
65
+ fontSize: `${format?.fontSize || 72}${unit}`,
66
+ textAlign: checkNested(format, 'alignment.horizontal') ? format?.alignment?.horizontal : 'left',
67
+ lineHeight: lineHeight,
68
+ paddingBottom: paddingBottom,
69
+ paddingTop: paddingTop,
70
+ display: 'inline-flex',
71
+ whiteSpace: multiline ? 'pre-wrap' : 'nowrap'
72
+ }}>{renderText}</span>
73
+ </div>
74
+ )
75
+ }
76
+
77
+ FormattedText.defaultProps = {
78
+ format: {},
79
+ lineHeight: 1,
80
+ unit: 'vh',
81
+ maxChar: undefined
82
+ }
@@ -0,0 +1,85 @@
1
+ import React from 'react'
2
+ import { ImageContainer } from '../ImageContainer'
3
+ import { TextContainer } from '../TextContainer'
4
+ import { QrCodeContainer } from '../QrCodeContainer'
5
+
6
+ const imageContainerStyle: React.CSSProperties = {
7
+ display: 'flex',
8
+ alignItems: 'center',
9
+ justifyContent: 'center',
10
+ overflow: 'hidden',
11
+ flexShrink: 0
12
+ }
13
+
14
+ type Props = {
15
+ item: NewsItem
16
+ parameters: VixonicParameters
17
+ }
18
+
19
+ const direction: { [key: string]: 'column' | 'row-reverse' | 'column-reverse' | 'row' } = {
20
+ 'top': 'column',
21
+ 'right': 'row-reverse',
22
+ 'bottom': 'column-reverse',
23
+ 'left': 'row'
24
+ }
25
+
26
+ const barSize = {
27
+ small: '10%',
28
+ medium: '15%',
29
+ big: '20%'
30
+ }
31
+
32
+ const hexToRgbA = (hex: string, opacity?: number) => {
33
+ let c: any
34
+ if (/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)) {
35
+ c = hex.substring(1).split('')
36
+ if (c.length === 3) c = [c[0], c[0], c[1], c[1], c[2], c[2]]
37
+ c = '0x' + c.join('')
38
+ return 'rgba(' + [(c >> 16) & 255, (c >> 8) & 255, c & 255].join(',') + ',' + (opacity || 1) + ')'
39
+ }
40
+ throw new Error('Bad Hex')
41
+ }
42
+
43
+ export const GalleryItem: React.FunctionComponent<Props> = (props) => {
44
+ const { item, parameters } = props
45
+ const flexDirection = direction[parameters.imagePosition || 'left']
46
+ return <div style={{
47
+ display: 'flex',
48
+ position: 'absolute',
49
+ top: 0,
50
+ left: 0,
51
+ bottom: 0,
52
+ right: 0,
53
+ flexDirection,
54
+ overflow: 'hidden',
55
+ padding: 'inherit'
56
+ }}>
57
+ <ImageContainer parameters={parameters} style={imageContainerStyle} src={item.image} />
58
+ {parameters.qrCodeEnabled && <QrCodeContainer parameters={parameters} item={item} />}
59
+ <div style={{
60
+ display: 'flex',
61
+ position: 'absolute',
62
+ bottom: '5vmin',
63
+ left: '5vmin',
64
+ right: '5vmin',
65
+ height: parameters.barSize && barSize[parameters.barSize] || '15%',
66
+ backgroundColor: parameters.barColor && hexToRgbA(parameters.barColor, parameters.barOpacity)
67
+ }}>
68
+ {parameters.iconVisible &&
69
+ <svg height='100%' viewBox='0 0 100 100'>
70
+ <rect width='100' height='100' fill={parameters.iconColor} />
71
+ <svg viewBox='0 0 100 100' x='10%' y='10%' width='80%' height='80%'>
72
+ <g>
73
+ <polygon fill='#fff' points='91.025,38.018 90.362,54.736 97.891,42.112 ' />
74
+ <polygon fill='#fff' points='65.395,96.6 71.269,86.753 47.289,85.802 ' />
75
+ <polygon fill='#fff' points='73.18,22.143 73.8,24.422 77.233,24.559 81.498,24.728 81.404,27.044 81.201,32.159 80.95,38.474 80.747,43.589 80.528,49.133 80.991,50.836 82.629,56.861 84.657,64.304 85.375,66.948 85.543,62.824 86,51.208 86.38,41.712 86.629,35.397 86.837,30.282 87.267,19.4 72.275,18.804 ' />
76
+ <polygon fill='#fff' points='74.287,70.849 68.038,72.551 62.763,73.985 56.736,75.628 38.535,80.584 37.339,80.908 39.203,80.983 73.888,82.361 79,82.561 84.746,82.789 85.164,72.229 85.286,69.227 85.343,67.84 82.005,68.751 ' />
77
+ <path fill='#fff' d='M57.398,70.785l8.567-2.327l5.389-1.471l6.254-1.699l2.305-0.629l0.167-0.047l-0.146-0.536l-0.503-1.851 l-1.641-6.028l-1.275-4.686l-0.507-1.865l-2.889-10.61l-1.701-6.247l-1.089-4.007l-1.01-3.71l-0.231-0.837l-1.472-5.413 l-0.056-0.208l-1.235-4.549L63.42,3.4L48.546,7.447l-10.987,2.99l-7.965,2.167l-10.259,2.796l-17.225,4.69l9.304,34.194 l1.639,6.024l1.649,6.062l1.701,6.249l0.571,2.101l1.793,6.584l3.634-0.988l4.739-1.293l6.031-1.644l18.203-4.948L57.398,70.785z M15.364,47.656L8.922,23.987l10.181-2.775l4.547-1.232l9.812-2.673l0.156-0.041l6.095-1.663l8.333-2.262l1.096-0.301l6.031-1.641 l4.35-1.184l0.983,3.618l0.243,0.891l0.996,3.66l0.705,2.59L63.273,24l1.239,4.551l-0.217-0.011l0.252,0.155l1.703,6.245 l4.907,18.029l-29.962,8.159l-5.917,1.605l-0.171-0.01l0.062,0.042l-7.748,2.106l-5.618,1.531l-1.248,0.337l-2.849-10.471 l-0.704-2.583L15.364,47.656z' />
78
+ </g>
79
+ </svg>
80
+ </svg>
81
+ }
82
+ <TextContainer style={{ margin: '1vmin 2.5vmin' }} parameters={parameters} item={item} />
83
+ </div>
84
+ </div>
85
+ }
@@ -0,0 +1,18 @@
1
+ import React from 'react'
2
+
3
+ type Props = {
4
+ src: string
5
+ }
6
+
7
+ export const GradientImage: React.FunctionComponent<Props> = (props) => {
8
+ const { src } = props
9
+
10
+ return <svg style={{ flexShrink: 0, height: '100%', zIndex: -1 }} viewBox='0 0 100 100'>
11
+ <defs>
12
+ <clipPath id='gradientClip'>
13
+ <rect width='100' height='100' />
14
+ </clipPath>
15
+ </defs>
16
+ <image width='100' height='100' xlinkHref={src} clipPath='url(#gradientClip)' preserveAspectRatio='xMidYMid slice' />
17
+ </svg>
18
+ }
@@ -0,0 +1,49 @@
1
+ import React from 'react'
2
+ import { GradientImage } from '../GradientImage'
3
+ import { TextContainer } from '../TextContainer'
4
+ import { QrCodeContainer } from '../QrCodeContainer'
5
+ import { GradientTitle } from '../GradientTitle'
6
+
7
+ type Props = {
8
+ item: NewsItem
9
+ parameters: VixonicParameters
10
+ }
11
+
12
+ const direction: { [key: string]: 'row-reverse' | 'row' } = {
13
+ 'right': 'row-reverse',
14
+ 'left': 'row'
15
+ }
16
+
17
+ export const GradientItem: React.FunctionComponent<Props> = (props) => {
18
+ const { item, parameters } = props
19
+ const flexDirection = direction[parameters.gradientOrientation || 'left']
20
+ const gradientDirection = flexDirection === 'row' ?
21
+ `linear-gradient(to right, ${parameters.gradientBackgroundColor || 'red'} ${parameters.gradientRate || 50}%, rgba(0, 0, 0, 0))` :
22
+ `linear-gradient(to left, ${parameters.gradientBackgroundColor || 'red'} ${parameters.gradientRate || 50}%, rgba(0, 0, 0, 0))`
23
+
24
+ return <div style={{
25
+ display: 'flex',
26
+ height: '100%',
27
+ width: '100%',
28
+ overflow: 'hidden',
29
+ alignItems: 'center',
30
+ background: gradientDirection,
31
+ flexDirection,
32
+ backgroundSize: 'cover'
33
+ }}>
34
+ <div style={{
35
+ display: 'flex',
36
+ flex: 1,
37
+ flexDirection: 'column',
38
+ justifyContent: 'center',
39
+ height: '100%',
40
+ overflow: 'hidden',
41
+ padding: parameters.padding || '',
42
+ }}>
43
+ <TextContainer parameters={parameters} item={item} />
44
+ </div>
45
+ <GradientImage src={item.image || ''} />
46
+ {parameters.qrCodeEnabled && <QrCodeContainer parameters={parameters} item={item} />}
47
+ {parameters.gradientTitleEnabled && <GradientTitle parameters={parameters} />}
48
+ </div>
49
+ }
@@ -0,0 +1,19 @@
1
+ import React from 'react'
2
+ import { FormattedText } from '../FormattedText'
3
+
4
+ type Props = {
5
+ parameters: VixonicParameters
6
+ }
7
+
8
+ export const GradientTitle: React.FunctionComponent<Props> = (props) => {
9
+ const { parameters } = props
10
+
11
+ return <div style={{
12
+ display: 'block',
13
+ position: 'absolute',
14
+ top: `${parameters.gradientTitlePositionY || 0}px`,
15
+ left: `${parameters.gradientTitlePositionX || 0}px`
16
+ }}>
17
+ <FormattedText text={parameters.gradientTitle || ''} unit={'px'} format={parameters.gradientTitleFormat} />
18
+ </div>
19
+ }
@@ -0,0 +1,77 @@
1
+ import React from 'react'
2
+
3
+ type Props = {
4
+ src?: string
5
+ parameters: VixonicParameters
6
+ style?: React.CSSProperties
7
+ }
8
+
9
+ const imageRadius = {
10
+ rounded: '25',
11
+ circle: '100'
12
+ }
13
+
14
+ const imageModes = {
15
+ contain: 'xMidYMid meet',
16
+ cover: 'xMidYMid slice',
17
+ fill: 'none',
18
+ none: 'xMinYMin slice'
19
+ }
20
+
21
+ export const ImageContainer: React.FunctionComponent<Props> = (props) => {
22
+ const { src, style, parameters } = props
23
+ const { imagePosition, imageMode, imageStyle, imageEnabled, imageSize } = parameters
24
+ if (imageEnabled === false) return null
25
+
26
+ const getSize = (): string => {
27
+ const { imageSize, imageCustomSize, template } = props.parameters
28
+ if (template === 'gallery') return '100%'
29
+ if (imageSize === 'custom') {
30
+ return `${imageCustomSize}%`
31
+ } else {
32
+ return imageSize === 'large' ? '100%' : imageSize === 'medium' ? '70%' : '50%'
33
+ }
34
+ }
35
+
36
+ const getImagePadding = (): string => {
37
+ const { template, imageSeparation, imagePosition } = props.parameters
38
+ if (template === 'gallery') return ''
39
+ const padding = `${(imageSeparation !== undefined && imageSeparation) || 10}px`
40
+ const imagePos = imagePosition || 'left'
41
+ const getPadding = (position: string) => imagePos === position ? padding : '0px'
42
+ return `${getPadding('bottom')} ${getPadding('left')} ${getPadding('top')} ${getPadding('right')}`
43
+ }
44
+
45
+ const sizeAttr = imagePosition === 'top' || imagePosition === 'bottom' ? 'height' : 'width'
46
+ const opositeAttr = sizeAttr === 'height' ? 'width' : 'height'
47
+ const size: string = getSize()
48
+
49
+ const shouldBeSquare = imageStyle !== undefined && imageStyle !== 'normal'
50
+ const preserveAspectRatio = imageMode && imageModes[imageMode] || imageModes.contain
51
+ const radius = (imageStyle === 'rounded' || imageStyle === 'circle') ? imageRadius[imageStyle] : '0'
52
+
53
+ const commonImageProps = {
54
+ width: '100%',
55
+ height: '100%',
56
+ clipPath: imageStyle && imageStyle !== 'normal' && 'url(#clip)' || undefined
57
+ }
58
+
59
+ return <div style={{ ...style, [sizeAttr]: `${imageSize === 'large' ? '80%' : size}`, [opositeAttr]: '100%', margin: getImagePadding() }}>
60
+ <svg viewBox={shouldBeSquare ? '0 0 100 100' : undefined} style={{ width: `${imageSize === 'large' ? size : '100%'}`, height: `${imageSize === 'large' ? size : '100%'}`, display: 'flex' }} className={'news-item__image-svg'}>
61
+ <defs>
62
+ {imageStyle !== 'hex' ?
63
+ <clipPath id='clip'>
64
+ <rect x='0' y='0' rx={radius} ry={radius} width='100%' height='100%' className='news-item__square-image-mask' />
65
+ </clipPath>
66
+ : <clipPath id='clip'>
67
+ <polygon className='news-item__hex-image-mask' points='93.3 75 93.3 25 50 0 6.7 25 6.7 75 50 100 93.3 75' />
68
+ </clipPath>
69
+ }
70
+ </defs>
71
+ <image {...commonImageProps}
72
+ xlinkHref={src}
73
+ preserveAspectRatio={preserveAspectRatio} className='news-item__image'
74
+ />
75
+ </svg>
76
+ </div>
77
+ }
@@ -0,0 +1,71 @@
1
+ import React, { useEffect, useRef, useState } from 'react'
2
+ import { TransitionGroup, CSSTransition } from 'react-transition-group'
3
+ import { StandardItem } from '../StandardItem'
4
+ import { GalleryItem } from '../GalleryItem'
5
+ import { GradientItem } from '../GradientItem'
6
+ import './styles.css'
7
+
8
+ type Props = {
9
+ data: VixonicData
10
+ items: NewsItem[]
11
+ }
12
+
13
+ export const NewsContainer: React.FunctionComponent<Props> = (props) => {
14
+ const { parameters, downloadsPath } = props.data
15
+ const { animationSpeed } = parameters
16
+ const timerRef = useRef<number | undefined>(undefined)
17
+ const [currentIndex, setCurrentIndex] = useState(0)
18
+ const item = props.items.length > currentIndex ? props.items[currentIndex] : undefined
19
+
20
+ useEffect(() => {
21
+ startTimeout()
22
+ return () => {
23
+ if (timerRef.current) clearTimeout(timerRef.current)
24
+ }
25
+ }, [currentIndex, props.items])
26
+
27
+ const startTimeout = () => {
28
+ const showTime = props.data?.parameters?.showTime || 10
29
+ const time = showTime * 1000
30
+ timerRef.current = window.setTimeout(nextItem, time)
31
+ }
32
+
33
+ const nextItem = () => {
34
+ let newIndex = currentIndex + 1
35
+ if (newIndex >= props.items.length) newIndex = 0
36
+ setCurrentIndex(newIndex)
37
+ }
38
+
39
+ if (parameters.template === 'gradient') {
40
+ return <TransitionGroup className='main-container' style={{
41
+ backgroundColor: parameters.gradientBackgroundColor || 'red'
42
+ }}>
43
+ {item &&
44
+ <CSSTransition key={item.id} classNames={parameters.animationType || 'fade'} timeout={(1750 / (animationSpeed || 1)) * 2}>
45
+ <div className={'news-item-container'}>
46
+ {
47
+ <GradientItem parameters={parameters} item={item} />
48
+ }
49
+ </div>
50
+ </CSSTransition>
51
+ }
52
+ </TransitionGroup>
53
+
54
+ } else {
55
+ return <TransitionGroup className='main-container' style={{
56
+ padding: parameters.padding,
57
+ backgroundImage: parameters.background && `url("${downloadsPath}/${parameters.background.filename}")`
58
+ }}>
59
+ {item &&
60
+ <CSSTransition key={item.id} classNames={parameters.animationType || 'fade'} timeout={(1750 / (animationSpeed || 1)) * 2}>
61
+ <div className={'news-item-container'}>
62
+ {parameters.template !== 'gallery'
63
+ ? <StandardItem parameters={parameters} item={item} />
64
+ : <GalleryItem parameters={parameters} item={item} />
65
+ }
66
+ </div>
67
+ </CSSTransition>
68
+ }
69
+ </TransitionGroup>
70
+ }
71
+ }
@@ -0,0 +1,45 @@
1
+ .main-container {
2
+ position: absolute;
3
+ top: 0;
4
+ left: 0;
5
+ right: 0;
6
+ bottom: 0;
7
+ overflow: hidden;
8
+ z-index: -1;
9
+ background-size: 100% 100%;
10
+ }
11
+
12
+ .news-item-container {
13
+ position: absolute;
14
+ top: 0;
15
+ left: 0;
16
+ right: 0;
17
+ bottom: 0;
18
+ justify-content: center;
19
+ align-items: center;
20
+ display: flex;
21
+ overflow: hidden;
22
+ padding: inherit;
23
+ }
24
+
25
+ .fade-enter {
26
+ opacity: 0.01;
27
+ }
28
+
29
+ .fade-enter-active {
30
+ opacity: 1;
31
+ transition: opacity var(--speed);
32
+ transition-timing-function: linear;
33
+ transition-delay: var(--enterDelay);
34
+ }
35
+
36
+ .fade-exit {
37
+ opacity: 1;
38
+ }
39
+
40
+ .fade-exit-active {
41
+ opacity: 0.01;
42
+ transition: opacity var(--speed);
43
+ transition-timing-function: linear;
44
+ transition-delay: var(--exitDelay);
45
+ }