WIP - 21.08.31

This commit is contained in:
Daniel Garcia 2021-08-31 22:31:08 +02:00
parent d4e422354d
commit 0386054842
23 changed files with 8646 additions and 5523 deletions

View File

@ -1,9 +1,3 @@
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": [
[
"@babel/plugin-proposal-class-properties",
{"loose": true}
]
]
"presets": ["@babel/preset-env", "@babel/preset-react"]
}

55
.eslintrc.js Normal file
View File

@ -0,0 +1,55 @@
module.exports = {
// 'plugins': ['jest'],
'env': {
'commonjs': true,
'es6': true,
'node': true,
// 'jest/globals': true,
},
'extends': [
'eslint:recommended',
'plugin:react/recommended',
],
'globals': {
'Atomics': 'readonly',
'SharedArrayBuffer': 'readonly',
},
'parserOptions': {
'ecmaVersion': 2018,
'sourceType': 'module',
},
'rules': {
'indent': [
'error',
2,
],
'linebreak-style': [
'error',
'unix',
],
'quotes': [
'error',
'single',
{ 'avoidEscape': true },
],
'semi': [
'error',
'never',
],
'comma-dangle': [
'error',
'always-multiline',
],
'arrow-parens': [
'error',
'as-needed',
],
'no-var': [
'error',
],
'prefer-const': ['error', {
'destructuring': 'all',
'ignoreReadBeforeAssign': true,
}],
},
}

View File

@ -1,6 +1,6 @@
# MCF-Components
Se trata de una biblioteca de componentes de basada en `react` y `material-ui`. Con ella se pueden usar estas librerías definiendo los componentes mediante objetos javascript planos.
Se trata de una biblioteca de componentes de basada en `react` y `material-ui`. Con ella se pueden usar estas bibliotecas definiendo los componentes mediante objetos javascript planos.
## Entorno de desarrollo
@ -8,21 +8,14 @@ La biblioteca está escrita usando `react` y `material-ui`.
Se transpila usando `babel` y se compila con `webpack`.
El entorno de desarrollo se levanta ejecutando `npm run start` y se puede acceder a él en http://localhost:9000 para ver el funcionamiento durante el desarrollo.
## Despliegue
Para el despliege a producción, debe subirse el proyecto a un repositorio `git`. Para poder trabajar con diferentes versiones de la biblioteca se emplean tags de git, por lo que una vez que se considera terminada una nueva versión de la misma sería necesario generar un nuevo tag de git con el número de la versión:
```sh
v0.1.3
```
## Arquitectura
El código de la aplicación se encuentra condensado en el archivo `index.js`, el cual exporta los métodos `render` y `remove`.
El método `render` genera toda la estructura de componentes react a partir de la definición que se le pasa como primer argumento. Hace falta un segundo argumento: el ID del nodo HTML dentro del cual se quieren generar los componentes de `react`.
El método `render` genera toda la estructura de componentes react a partir de la definición que se le pasa como primer argumento.
Hace falta un segundo argumento: el ID del nodo HTML dentro del cual se quiere generar los componentes.
El objeto que define la estructura a construir es pasado a una factoría de componentes que es quien se encarga de formar de forma recursiva todos los elementos.
La definición es pasada a una factoría de componentes que es quien se encarga de formar de manera recursiva todos los elementos.
El método remove permite eliminar la estructura de componentes creada en un nodo HTML.
El método `remove` permite eliminar la estructura de componentes creada en un nodo HTML.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

12387
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,34 +1,34 @@
{
"name": "mcf-components",
"version": "0.1.3",
"description": "Library to render component templates pre-created using React and Material-UI",
"version": "0.2.0",
"description": "Library to render component templates created using React and Material-UI",
"main": "dist/mcf-components.js",
"scripts": {
"start": "webpack-dev-server --open --config webpack.dev.js",
"dist": "webpack --config webpack.prod.js",
"test": "jest",
"test:watch": "jest --watch"
},
"devDependencies": {
"@babel/core": "^7.7.5",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/preset-env": "^7.7.6",
"@babel/preset-react": "^7.7.4",
"babel-loader": "^8.0.6",
"css-loader": "^3.4.0",
"jest": "^24.9.0",
"mini-css-extract-plugin": "^0.8.1",
"style-loader": "^1.0.2",
"webpack": "^4.41.3",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.9.0",
"webpack-merge": "^4.2.2"
"@babel/core": "^7.15.0",
"@babel/plugin-proposal-class-properties": "^7.14.5",
"@babel/preset-env": "^7.15.0",
"@babel/preset-react": "^7.14.5",
"@babel/runtime": "^7.14.8",
"babel-loader": "^8.2.2",
"eslint": "^7.32.0",
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-react-hooks": "^4.2.0",
"jest": "^27.0.6",
"webpack": "^5.49.0",
"webpack-cli": "^4.7.2",
"webpack-dev-server": "^3.11.2",
"webpack-merge": "^5.8.0"
},
"dependencies": {
"@material-ui/core": "4.3.3",
"prop-types": "15.7.2",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-select": "^3.0.8"
"@material-ui/core": "^4.12.3",
"@material-ui/lab": "^4.0.0-alpha.60",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-select": "^4.3.1"
}
}

48
src/ComponentFactory.js Normal file
View File

@ -0,0 +1,48 @@
import React from 'react'
import MCF_Autocomplete from './components/MCF_AutoComplete'
import MCF_Button from './components/MCF_Button'
import MCF_Card from './components/MCF_Card'
import MCF_Checkbox from './components/MCF_Checkbox'
import MCF_CheckboxesGroup from './components/MCF_CheckboxesGroup'
import MCF_Dialog from './components/MCF_Dialog'
import MCF_Divider from './components/MCF_Divider'
import MCF_MediaCard from './components/MCF_MediaCard'
import MCF_Select from './components/MCF_Select'
import MCF_Text from './components/MCF_Text'
import MCF_FileUpload from './components/MCF_FileUpload'
export default class ComponentFactory {
static createComponent(componentConfig) {
const params = componentConfig.params
params.key = params.parentKey !== undefined ? params.parentKey + params.id : params.id
switch (componentConfig.type) {
case 'autocomplete':
return <MCF_Autocomplete key={params.key} params={params}/>
case 'button':
return <MCF_Button key={params.key} params={params}/>
case 'card':
return <MCF_Card key={params.key} params={params}/>
case 'checkbox':
return <MCF_Checkbox key={params.key} params={params}/>
case 'checkboxesGroup':
return <MCF_CheckboxesGroup key={params.key} params={params}/>
case 'dialog':
return <MCF_Dialog key={params.key} params={params}/>
case 'divider':
return <MCF_Divider key={params.key} params={params}/>
case 'fileUpload':
return <MCF_FileUpload key={params.key} params={params}/>
case 'mediaCard':
return <MCF_MediaCard key={params.key} params={params}/>
case 'select':
return <MCF_Select key={params.key} params={params}/>
case 'text':
return <MCF_Text key={params.key} params={params}/>
default:
throw new Error(`MCF-Components ==> The component type ${componentConfig.type} does not exist. Check the component config with id ${componentConfig.params.id}`)
}
}
}

View File

@ -0,0 +1,35 @@
/* eslint-disable react/prop-types */
import React, { useState } from 'react'
import TextField from '@material-ui/core/TextField'
import Autocomplete from '@material-ui/lab/Autocomplete'
export default function MCF_Autocomplete (props) {
const [params, setParams] = useState(props.params)
const handleChange = (event, newValue) => {
setParams({...params, value: newValue})
if (params.onChangeListener) {
params.onChangeListener(newValue)
}
}
return (
<Autocomplete
id={params.id}
value={params.value}
style={{width: params.width}}
options={params.options}
getOptionLabel={option => option.key}
onChange={handleChange}
renderInput={parameters => <TextField
{...parameters}
label={params.label}
variant={params.variant} // 'filled' | 'outlined' | 'standard'
margin={params.margin} // 'dense' | 'none' | 'normal'
/>}
/>
)
}

View File

@ -0,0 +1,26 @@
/* eslint-disable react/prop-types */
import React, {useState} from 'react'
import Button from '@material-ui/core/Button'
import {useStyles} from '../index'
export default function MCF_Button (props) {
const [params, setParams] = useState(props.params)
const classes = useStyles()
return (
<Button
className={classes.button}
variant={params.variant}
color={params.color}
disabled={params.disabled}
size={params.size}
value={params.value}
onClick={params.onClickListener}
>
{params.text}
</Button>
)
}

View File

@ -0,0 +1,35 @@
/* eslint-disable react/prop-types */
import React, {useState} from 'react'
import Card from '@material-ui/core/Card'
import CardContent from '@material-ui/core/CardContent'
import CardHeader from '@material-ui/core/CardHeader'
import {useStyles} from '../index'
import ComponentFactory from '../ComponentFactory'
export default function MCF_Card (props) {
const [params, setParams] = useState(props.params)
const classes = useStyles()
const renderContents = () => {
return params.contents.map(contentConfig => {
contentConfig.params.parentKey = params.key
return ComponentFactory.createComponent(contentConfig)
})
}
return (
<Card className={classes.card}>
<CardHeader
title={params.mainText}
subheader={params.secondaryText}
/>
<CardContent>
{renderContents()}
</CardContent>
</Card>
)
}

View File

@ -0,0 +1,30 @@
/* eslint-disable react/prop-types */
import React, {useState} from 'react'
import Checkbox from '@material-ui/core/Checkbox'
import FormControlLabel from '@material-ui/core/FormControlLabel'
export default function MCF_Checkbox (props) {
const [params, setParams] = useState(props.params)
const handleChange = event => {
params.onChangeListener(params.id, event.target.checked)
setParams({...params, checked: event.target.checked})
}
return (
<FormControlLabel
id={params.id}
key={params.id}
control={
<Checkbox
checked={params.checked}
onChange={handleChange}
name={params.id}
/>
}
label={params.label}
/>
)
}

View File

@ -0,0 +1,57 @@
/* eslint-disable react/prop-types */
import React, {useState} from 'react'
import FormControl from '@material-ui/core/FormControl'
import FormGroup from '@material-ui/core/FormGroup'
import FormHelperText from '@material-ui/core/FormHelperText'
import FormLabel from '@material-ui/core/FormLabel'
import ComponentFactory from '../ComponentFactory'
import {useStyles} from '../index'
export default function MCF_CheckboxesGroup (props) {
const [params, setParams] = useState(props.params)
const classes = useStyles()
params.checkboxesState = {}
params.checkboxes.map(checkbox => {
params.checkboxesState[checkbox.id] = checkbox.isChecked
})
const handleChange = (checkboxID, checkboxValue) => {
params.onChangeListener(checkboxID, checkboxValue)
params.checkboxesState[checkboxID] = checkboxValue
setParams(params)
}
const createCheckboxes = () => {
return (
params.checkboxes.map(checkbox => {
const checkboxConfig = {
type: 'checkbox',
params: {
id: checkbox.id,
checked: checkbox.checked,
label: checkbox.label,
onChangeListener: handleChange,
},
}
return ComponentFactory.createComponent(checkboxConfig)
})
)
}
return (
<FormControl component="fieldset" className={classes.formControl}>
<FormLabel component="legend">{params.label}</FormLabel>
<FormHelperText>{params.helperText}</FormHelperText>
<FormGroup
id={params.id}
row={params.isRow}
>
{createCheckboxes()}
</FormGroup>
</FormControl>
)
}

View File

@ -0,0 +1,86 @@
/* eslint-disable react/prop-types */
import React, {useState} from 'react'
import Button from '@material-ui/core/Button'
import Dialog from '@material-ui/core/Dialog'
import DialogActions from '@material-ui/core/DialogActions'
import DialogContent from '@material-ui/core/DialogContent'
import DialogContentText from '@material-ui/core/DialogContentText'
import DialogTitle from '@material-ui/core/DialogTitle'
import ComponentFactory from '../ComponentFactory'
export default function MCF_Dialog (props) {
const [params, setParams] = useState(props.params)
const handleOpen = () => {
setParams({...params, open: true})
if (params.openListener) {
params.openListener()
}
}
const handleClose = () => {
setParams({...params, open: false})
if (params.closeListener) {
params.closeListener()
}
}
const renderContents = () => {
return params.contents.map(contentParams => {
contentParams.params.parentParams = params
contentParams.params.parentParams.handleClose = handleClose
contentParams.params.parentParams.handleClickOpen = handleOpen
contentParams.params.parentKey = params.key
contentParams.setParentParams = setParams
return ComponentFactory.createComponent(contentParams)
})
}
const renderActionContents = () => {
return params.actionContents.map(contentParams => {
contentParams.params.parentParams = params
contentParams.params.parentParams.handleClose = handleClose
contentParams.params.parentParams.handleClickOpen = handleOpen
contentParams.params.parentKey = params.key
contentParams.setParentParams = setParams
return ComponentFactory.createComponent(contentParams)
})
}
return (
<div>
<Button
variant={params.openButtonVariant}
color={params.openButtonColor}
onClick={handleOpen}>
{params.openButtonText}
</Button>
<Dialog
open={params.open}
onClose={handleClose}
aria-labelledby={params.id}
>
<DialogTitle id={params.id}>
{params.titleText}
</DialogTitle>
<DialogContent>
<DialogContentText>
{params.contentText}
</DialogContentText>
{renderContents()}
</DialogContent>
<DialogActions>
{renderActionContents()}
<Button
variant={params.closeButtonVariant}
color={params.closeButtonColor}
onClick={handleClose}>
{params.closeButtonText}
</Button>
</DialogActions>
</Dialog>
</div>
)
}

View File

@ -0,0 +1,12 @@
import React from 'react'
import {useStyles} from '../index'
export default function MCF_Divider () {
const classes = useStyles()
return (
<div className={classes.divider}/>
)
}

View File

@ -0,0 +1,73 @@
import React, {useState, useEffect} from 'react'
import Button from '@material-ui/core/Button'
import {useStyles} from '../index'
export default function MCF_FileUpload (props) {
const [params, setParams] = useState(props.params)
const classes = useStyles()
if (params.acceptedFormats === undefined) params.acceptedFormats = '*!/!*'
useEffect(() => {
const input = document.getElementById(params.id)
input.addEventListener('change', event => {
event.preventDefault()
const files = document.querySelector('input[type=file]').files
if (files.length > 0) {
const formData = new FormData()
formData.append('tkn', params.token)
if (params.storagePath) formData.append('path', params.storagePath)
for (let i = 0; i < files.length; i++) {
formData.append('files[]', files[i])
}
if (params.beforeFetchListener) params.beforeFetchListener(files)
fetch(params.url, {
method: 'POST',
body: formData,
}).then(response => {
if (response) params.files = files
handleUploadSuccess(response)
})
.catch(error => handleUploadError(error))
}
})
}, [])
const handleUploadSuccess = response => {
if (params.onUpload) params.onUpload(response, params.files)
setParams(params)
}
const handleUploadError = error => {
if (params.onError) params.onError(error)
params.files = null
}
return (
<div>
<input
id={params.id}
className={classes.input}
accept={params.acceptedFormats}
multiple
type="file"
/>
<label htmlFor={params.id}>
<Button
className={classes.button}
variant={params.variant}
color={params.color}
size={params.size}
component="span">
{params.text}
</Button>
</label>
</div>
)
}

View File

@ -0,0 +1,42 @@
/* eslint-disable react/prop-types */
import React, {useState} from 'react'
import {useStyles} from '../index'
import Card from '@material-ui/core/Card'
import CardContent from '@material-ui/core/CardContent'
import CardHeader from '@material-ui/core/CardHeader'
import CardMedia from '@material-ui/core/CardMedia'
import ComponentFactory from '../ComponentFactory'
export default function MCF_MediaCard (props) {
const [params, setParams] = useState(props.params)
const classes = useStyles()
const renderContents = () => {
return params.contents.map(contentConfig => {
contentConfig.params.parentKey = params.key
return ComponentFactory.createComponent(contentConfig)
})
}
return (
<Card className={classes.card}>
<CardHeader
title={params.mainText}
subheader={params.secondaryText}
/>
<CardMedia
className={classes.media}
image={params.imageURL}
title={params.imageTitle}
alt={params.imageAlt}
/>
<CardContent>
{renderContents()}
</CardContent>
</Card>
)
}

View File

@ -0,0 +1,70 @@
/* eslint-disable react/prop-types */
import React, {useState} from 'react'
import FormControl from '@material-ui/core/FormControl'
import FormHelperText from '@material-ui/core/FormHelperText'
import Input from '@material-ui/core/Input'
import InputLabel from '@material-ui/core/InputLabel'
import MenuItem from '@material-ui/core/MenuItem'
import Select from '@material-ui/core/Select'
import {useStyles} from '../index'
export default function MCF_Select (props) {
const [params, setParams] = useState(props.params)
const classes = useStyles()
const handleInteraction = event => {
const option = params.options.find(option => option.value === event.target.value)
params.value = option.value
if (params.onSelectListener) params.onSelectListener(option.id, option.value)
setParams(params)
}
const createList = () => {
return (
params.options.map(option => {
return (
<MenuItem
key={option.id}
value={option.value}
>
{option.label}
</MenuItem>
)
})
)
}
return (
<FormControl
className={classes.formControl}
disabled={params.disabled}
error={params.error}
required={params.required}
>
<InputLabel
shrink={params.shrink}
htmlFor={params.id}
>
{params.label}
</InputLabel>
<Select
value={params.value}
displayEmpty={params.displayEmpty}
autoWidth={params.autoWidth}
onChange={handleInteraction}
input={<Input
name={params.id}
id={params.id}
readOnly={params.readOnly}
/>}
>
{createList()}
</Select>
<FormHelperText>
{params.helperText}
</FormHelperText>
</FormControl>
)
}

View File

@ -0,0 +1,48 @@
/* eslint-disable react/prop-types */
import React, {useState} from 'react'
import TextField from '@material-ui/core/TextField'
import {useStyles} from '../index'
export default function MCF_Text (props) {
const [params, setParams] = useState(props.params)
const classes = useStyles()
const handleChange = event => {
const setError = isError => {
setParams({...params, value: event.target.value, error: isError})
}
setParams({...params, value: event.target.value})
if (params.onChangeListener){
params.onChangeListener(event.target.value, setError)
}
}
return (
<form className={classes.container} noValidate autoComplete="off">
<TextField
id={params.id}
label={params.label}
className={classes.textField}
value={params.value}
onChange={handleChange}
margin={params.margin} // 'dense' | 'none' | 'normal'
required={params.required}
error={params.error}
disabled={params.disabled}
placeholder={params.placeholder}
helperText={params.helperText}
fullWidth={params.fullWidth}
variant={params.variant} // 'filled' | 'outlined' | 'standard'
type={params.type} // HTML input type
InputLabelProps={{
shrink: params.shrink,
}}
/>
</form>
)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
const path = require('path');
const path = require('path')
module.exports = {
entry: './src/index.js',
@ -6,7 +6,7 @@ module.exports = {
path: path.resolve(__dirname, 'dist'),
filename: 'mcf-components.js',
library: 'mcfComponents',
libraryTarget: 'umd'
libraryTarget: 'umd',
},
module: {
rules: [
@ -19,4 +19,4 @@ module.exports = {
},
],
},
};
}

View File

@ -1,5 +1,5 @@
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const {merge} = require('webpack-merge')
const common = require('./webpack.common.js')
module.exports = merge(common, {
mode: 'development',
@ -7,4 +7,4 @@ module.exports = merge(common, {
devServer: {
contentBase: './dist',
},
});
})

View File

@ -1,7 +1,7 @@
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const {merge} = require('webpack-merge')
const common = require('./webpack.common.js')
module.exports = merge(common, {
mode: 'production',
devtool: 'source-map',
});
})