@craftercms/studio-ui
Version:
Services, components, models & utils to build CrafterCMS authoring extensions.
415 lines (413 loc) • 12 kB
JavaScript
/*
* Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 3 as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useState } from 'react';
import Card from '@mui/material/Card';
import CardActionArea from '@mui/material/CardActionArea';
import CardContent from '@mui/material/CardContent';
import CardHeader from '@mui/material/CardHeader';
import Typography from '@mui/material/Typography';
import CardActions from '@mui/material/CardActions';
import SwipeableViews from 'react-swipeable-views';
// @ts-ignore
import { autoPlay } from 'react-swipeable-views-utils';
import { makeStyles } from 'tss-react/mui';
import { defineMessages, useIntl } from 'react-intl';
import MobileStepper from '../MobileStepper/MobileStepper';
import { backgroundColor } from '../../styles/theme';
import Button from '@mui/material/Button';
import Tooltip from '@mui/material/Tooltip';
import cardTitleStyles, { cardSubtitleStyles } from '../../styles/card';
import SecondaryButton from '../SecondaryButton';
const AutoPlaySwipeableViews = autoPlay(SwipeableViews);
const useStyles = makeStyles()((theme) => ({
root: {
flexGrow: 1
},
card: {
maxWidth: '100%',
minHeight: '339px',
'& .cardTitle': Object.assign({}, cardTitleStyles),
'& .cardContent': {
height: '13.26em',
padding: '12px 14px 5px 14px',
position: 'relative'
},
'& .cardActions': {
justifyContent: 'space-around'
},
'& .developer': Object.assign(Object.assign({}, cardSubtitleStyles), { WebkitLineClamp: 1, marginBottom: 0 })
},
gitCard: {
minHeight: 'unset'
},
gitCardActionArea: {
display: 'flex',
justifyContent: 'start'
},
gitCardContent: {
height: 'unset !important'
},
carouselImg: {
width: '100%',
height: '180px',
objectFit: 'cover',
'&.git': {
objectFit: 'fill',
height: 'unset',
width: '120px'
}
},
video: {
width: '100%',
height: '180px',
outline: 'none',
background: backgroundColor,
'&.git': {
height: 'unset'
}
},
chip: {
fontSize: '11px',
color: 'gray',
backgroundColor: '#f5f5f5',
padding: '2px 5px',
borderRadius: '5px',
display: 'inline-block',
whiteSpace: 'nowrap',
'& label': {
marginRight: '5px',
marginBottom: 0,
fontWeight: 400
},
'& span': {
color: '#2F2707'
}
},
options: {
marginLeft: 'auto'
},
dialogContent: {
display: 'flex'
},
imgWrapper: {
position: 'relative'
},
dots: {
background: 'none',
borderTop: '1px solid #e4e3e3',
height: '30px',
padding: '0',
cursor: 'pointer',
'& .MuiMobileStepper-dot': {
padding: '6px',
margin: '4px',
'&:hover': {
background: 'gray'
}
}
},
use: {
width: '50%'
},
more: {
width: '50%',
textAlign: 'center',
color: theme.palette.text.secondary,
flex: 1
},
background: {
background: backgroundColor,
height: '180px',
overflow: 'hidden',
'&.git': {
background: 'none',
height: 'unset'
}
},
subtitleContainer: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between'
}
}));
const messages = defineMessages({
version: {
id: 'plugin.version',
defaultMessage: 'Version'
},
license: {
id: 'plugin.license',
defaultMessage: 'License'
},
crafterCms: {
id: 'plugin.crafterCMS',
defaultMessage: 'CrafterCMS'
},
by: {
id: 'plugin.by',
defaultMessage: 'By'
},
noDev: {
id: 'plugin.noDev',
defaultMessage: 'No developer specified.'
},
use: {
id: 'plugin.use',
defaultMessage: 'Use'
},
more: {
id: 'plugin.more',
defaultMessage: 'More...'
},
licenseTooltip: {
id: 'plugin.licenseTooltip',
defaultMessage: '{license} license'
}
});
function PluginCard(props) {
const { classes, cx } = useStyles();
const [index, setIndex] = useState(0);
const [play, setPlay] = useState(false);
const {
onPluginSelected,
plugin,
changeImageSlideInterval = 5000,
isMarketplacePlugin = true,
onDetails,
inUse = false,
usePermission = true,
beingInstalled = false,
disableCardActionClick = false,
useLabel
} = props;
const { media, name, license, id, developer } = plugin;
const { formatMessage } = useIntl();
const isGitCard = id === 'GIT';
function handleChangeIndex(value) {
setIndex(value);
}
function onDotClick(e, step) {
e.stopPropagation();
setIndex(step);
}
function handlePlay() {
setPlay(true);
}
function handleEnded() {
setPlay(false);
}
function onImageClick(e, index = 0) {
if (plugin.id === 'GIT') return false;
e.stopPropagation();
e.preventDefault();
onDetails(plugin, index);
}
function renderLicense() {
return React.createElement(
Tooltip,
{ title: formatMessage(messages.licenseTooltip, { license: license.name }) },
React.createElement('div', { className: classes.chip }, React.createElement('span', null, license.name))
);
}
function renderSubtitle() {
if (developer) {
if (developer.company) {
return React.createElement(
'div',
{ className: classes.subtitleContainer },
React.createElement(
Typography,
{ gutterBottom: true, variant: 'subtitle2', className: 'developer', color: 'textSecondary' },
formatMessage(messages.by),
' ',
developer.company.name
),
renderLicense()
);
} else {
return developer.people.map((item) => item.name).join(',');
}
} else {
return React.createElement(
'div',
{ className: classes.subtitleContainer },
React.createElement(
Typography,
{ gutterBottom: true, variant: 'subtitle1', className: 'developer', color: 'textSecondary' },
formatMessage(messages.noDev)
),
renderLicense()
);
}
}
function renderMedias(id) {
let videos = media && media.videos ? Object.assign(Object.assign({}, media.videos), { type: 'video' }) : [];
videos = videos.length ? videos.map((obj) => Object.assign(Object.assign({}, obj), { type: 'video' })) : [];
let screenshots = media && media.screenshots ? media.screenshots : [];
const merged = [...videos, ...screenshots];
return merged.map((item, index) => {
if (item.type !== 'video') {
return React.createElement(
'div',
{
key: index,
className: cx(classes.background, id === 'GIT' && 'git'),
onClick: (event) => onImageClick(event, index)
},
React.createElement('img', {
className: cx(classes.carouselImg, id === 'GIT' && 'git'),
src: item.url,
alt: item.description
})
);
} else {
return React.createElement(
'video',
{
muted: true,
controls: true,
key: index,
autoPlay: play,
onEnded: handleEnded,
className: classes.video,
onPlaying: handlePlay
},
React.createElement('source', { src: item.url, type: 'video/mp4' }),
'Your browser does not support the video tag.'
);
}
});
}
let steps = 0;
plugin.media && plugin.media.screenshots ? (steps = plugin.media.screenshots.length) : (steps = 0);
plugin.media && plugin.media.videos ? (steps += plugin.media.videos.length) : (steps += 0);
return React.createElement(
Card,
{ className: cx(classes.card, id === 'GIT' ? classes.gitCard : null) },
id !== 'GIT' &&
React.createElement(
CardActionArea,
{
disabled: disableCardActionClick,
onClick: (e) => {
if (isMarketplacePlugin && !plugin.compatible) {
onImageClick(e);
} else {
onPluginSelected(plugin, 1);
}
}
},
React.createElement(CardHeader, {
title: name,
subheader: id !== 'GIT' ? renderSubtitle() : '',
titleTypographyProps: {
variant: 'subtitle2',
component: 'h2',
className: 'cardTitle'
},
subheaderTypographyProps: {
variant: 'subtitle2',
component: 'h2',
color: 'textSecondary'
}
})
),
React.createElement(
CardActionArea,
{
disabled: disableCardActionClick,
onClick: () => {
onPluginSelected(plugin, 1);
},
className: isGitCard ? classes.gitCardActionArea : null
},
React.createElement(
AutoPlaySwipeableViews,
{
index: index,
interval: changeImageSlideInterval,
autoplay: false,
onChangeIndex: handleChangeIndex,
enableMouseEvents: true
},
renderMedias(id)
),
id === 'GIT' &&
React.createElement(
CardContent,
{ className: cx('cardContent', isGitCard ? classes.gitCardContent : null) },
React.createElement(
Typography,
{ gutterBottom: true, variant: 'subtitle2', component: 'h2', className: 'cardTitle' },
name
),
React.createElement(
Typography,
{ gutterBottom: true, variant: 'subtitle2', component: 'h2', color: 'textSecondary' },
plugin.description
)
)
),
steps > 0 &&
id !== 'GIT' &&
React.createElement(MobileStepper, {
variant: 'dots',
steps: steps,
onDotClick: onDotClick,
className: classes.dots,
position: 'static',
activeStep: index
}),
id !== 'GIT' &&
React.createElement(
CardActions,
{ className: 'cardActions' },
((isMarketplacePlugin && plugin.compatible) || !isMarketplacePlugin) && // if it's from marketplace and compatible, or not from marketplace (private bps)
React.createElement(
SecondaryButton,
{
color: 'primary',
disabled: !usePermission || inUse || beingInstalled,
loading: beingInstalled,
onClick: () => onPluginSelected(plugin, 1),
className: classes.use
},
useLabel ? useLabel : formatMessage(messages.use)
),
React.createElement(
Button,
{ className: classes.more, onClick: () => onDetails(plugin) },
formatMessage(messages.more)
)
)
);
}
export default PluginCard;