@vixoniccom/news-internal 0.4.19 → 0.4.20-dev.1
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/CHANGELOG.md +11 -0
- package/build/index.html +1 -0
- package/build/main.js +2 -0
- package/build/main.js.LICENSE.txt +39 -0
- package/build/test/downloads/cat-2.jpg +0 -0
- package/build/test/downloads/cat.jpg +0 -0
- package/build/test/downloads/colabra.jpg +0 -0
- package/build/test/downloads/custom-template/assets/background.png +0 -0
- package/build/test/downloads/custom-template/assets/image-error.png +0 -0
- package/build/test/downloads/custom-template/assets/image-loading.gif +0 -0
- package/build/test/downloads/custom-template/assets/main-font.ttf +0 -0
- package/build/test/downloads/custom-template/main.html +62 -0
- package/build/test/downloads/dog.jpg +0 -0
- package/build/test/downloads/fondo-noticiario-vitacura.jpg +0 -0
- package/build/test/downloads/font-title.otf +0 -0
- package/build/test/parameters.json +96 -0
- package/build.zip +0 -0
- package/configuration/index.ts +3 -109
- package/configuration/newsGroup/index.ts +16 -0
- package/configuration/newsGroup/newsInputs.ts +103 -0
- package/configuration.json +17 -11
- package/package.json +14 -12
- package/src/components/App.tsx +80 -0
- package/src/components/FontLoader.tsx +49 -0
- package/src/components/FormattedHtmlText/index.tsx +70 -0
- package/src/components/FormattedText/index.tsx +82 -0
- package/src/components/GalleryItem/index.tsx +85 -0
- package/src/components/GradientImage/index.tsx +18 -0
- package/src/components/GradientItem/index.tsx +49 -0
- package/src/components/GradientTitle/index.tsx +19 -0
- package/src/components/ImageContainer/index.tsx +77 -0
- package/src/components/NewsContainer/index.tsx +71 -0
- package/src/components/NewsContainer/styles.css +45 -0
- package/src/components/QrCodeContainer/index.tsx +38 -0
- package/src/components/StandardItem/index.tsx +45 -0
- package/src/components/TextContainer/index.tsx +35 -0
- package/src/global.d.ts +9 -0
- package/src/index.html +11 -0
- package/src/main.ts +28 -0
- package/src/parameters.d.ts +80 -0
- package/src/test/downloads/cat-2.jpg +0 -0
- package/src/test/downloads/cat.jpg +0 -0
- package/src/test/downloads/colabra.jpg +0 -0
- package/src/test/downloads/custom-template/assets/background.png +0 -0
- package/src/test/downloads/custom-template/assets/image-error.png +0 -0
- package/src/test/downloads/custom-template/assets/image-loading.gif +0 -0
- package/src/test/downloads/custom-template/assets/main-font.ttf +0 -0
- package/src/test/downloads/custom-template/main.html +62 -0
- package/src/test/downloads/dog.jpg +0 -0
- package/src/test/downloads/fondo-noticiario-vitacura.jpg +0 -0
- package/src/test/downloads/font-title.otf +0 -0
- package/src/test/parameters.json +96 -0
- 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
|
+
}
|