Simplify React components loader

This commit is contained in:
Paul Chavard 2019-08-14 12:11:36 +01:00
parent 39afa6659c
commit 2cf415dc41
10 changed files with 107 additions and 147 deletions

View file

@ -9,13 +9,19 @@
text-indent: -9999em;
animation: load4 1.3s infinite linear;
transform: translateZ(0);
}
.right-spinner {
position: absolute;
top: 3.7em;
right: 1.2em;
transform: scale(0.3);
&.right {
position: absolute;
top: 3.7em;
right: 1.2em;
transform: scale(0.3);
}
&.left {
top: 1.2em;
left: 1.2em;
transform: scale(0.4);
}
}
@keyframes load4 {

View file

@ -0,0 +1,8 @@
import React from 'react';
import Loadable from 'react-loadable';
const loading = () => <div className="spinner left" />;
export default function(loader) {
return Loadable({ loader, loading });
}

View file

@ -0,0 +1,62 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { library } from '@fortawesome/fontawesome-svg-core';
import {
faArrowCircleDown,
faArrowDown,
faArrowsAltV,
faArrowUp,
faPlus,
faTrash
} from '@fortawesome/free-solid-svg-icons';
import Flash from './Flash';
import OperationsQueue from './OperationsQueue';
import TypeDeChamps from './components/TypeDeChamps';
library.add(
faArrowCircleDown,
faArrowDown,
faArrowsAltV,
faArrowUp,
faPlus,
faTrash
);
class TypesDeChampEditor extends Component {
constructor(props) {
super(props);
const defaultTypeDeChampAttributes = {
type_champ: 'text',
types_de_champ: [],
private: props.isAnnotation,
libelle: `${
props.isAnnotation ? 'Nouvelle annotation' : 'Nouveau champ'
} ${props.typeDeChampsTypes[0][0]}`
};
this.state = {
flash: new Flash(props.isAnnotation),
queue: new OperationsQueue(props.baseUrl),
defaultTypeDeChampAttributes,
typeDeChampsTypes: props.typeDeChampsTypes,
directUploadUrl: props.directUploadUrl,
isAnnotation: props.isAnnotation
};
}
render() {
return (
<TypeDeChamps state={this.state} typeDeChamps={this.props.typeDeChamps} />
);
}
}
TypesDeChampEditor.propTypes = {
baseUrl: PropTypes.string,
directUploadUrl: PropTypes.string,
isAnnotation: PropTypes.bool,
typeDeChamps: PropTypes.array,
typeDeChampsTypes: PropTypes.array
};
export default TypesDeChampEditor;

View file

@ -1,66 +1,3 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { library } from '@fortawesome/fontawesome-svg-core';
import {
faArrowDown,
faArrowsAltV,
faArrowUp,
faArrowCircleDown,
faPlus,
faTrash
} from '@fortawesome/free-solid-svg-icons';
import Loadable from '../Loadable';
import Flash from './Flash';
import OperationsQueue from './OperationsQueue';
import TypeDeChamps from './components/TypeDeChamps';
library.add(
faArrowDown,
faArrowsAltV,
faArrowUp,
faArrowCircleDown,
faPlus,
faTrash
);
class TypesDeChampEditor extends Component {
constructor(props) {
super(props);
const defaultTypeDeChampAttributes = {
type_champ: 'text',
types_de_champ: [],
private: props.isAnnotation,
libelle: `${
props.isAnnotation ? 'Nouvelle annotation' : 'Nouveau champ'
} ${props.typeDeChampsTypes[0][0]}`
};
this.state = {
flash: new Flash(props.isAnnotation),
queue: new OperationsQueue(props.baseUrl),
defaultTypeDeChampAttributes,
typeDeChampsTypes: props.typeDeChampsTypes,
directUploadUrl: props.directUploadUrl,
isAnnotation: props.isAnnotation
};
}
render() {
return (
<TypeDeChamps state={this.state} typeDeChamps={this.props.typeDeChamps} />
);
}
}
TypesDeChampEditor.propTypes = {
baseUrl: PropTypes.string,
directUploadUrl: PropTypes.string,
isAnnotation: PropTypes.bool,
typeDeChamps: PropTypes.array,
typeDeChampsTypes: PropTypes.array
};
export function createReactUJSElement(props) {
return React.createElement(TypesDeChampEditor, props);
}
export default TypesDeChampEditor;
export default Loadable(() => import('./TypesDeChampEditor'));

View file

@ -6,9 +6,7 @@ import '@rails/actiontext';
import 'whatwg-fetch'; // window.fetch polyfill
import Chartkick from 'chartkick';
import Highcharts from 'highcharts';
import ReactUJS from '../shared/react-ujs';
import reactComponents from '../shared/react-components';
import ReactRailsUJS from 'react_ujs';
import '../shared/page-update-event';
import '../shared/activestorage/ujs';
@ -61,8 +59,18 @@ Rails.start();
Turbolinks.start();
ActiveStorage.start();
const loader = new ReactUJS(reactComponents);
loader.start();
// If Turbolinks is imported via Webpacker (and thus not available globally),
// ReactRailsUJS will be unable to locate it.
// https://github.com/reactjs/react-rails#event-handling
// eslint-disable-next-line no-undef
ReactRailsUJS.useContext(require.context('components', true));
// Add Turbolinks to the global namespace:
window.Turbolinks = Turbolinks;
// Remove previous event handlers and add new ones:
ReactRailsUJS.detectEvents();
// (Optional) Clean up global namespace:
delete window.Turbolinks;
// Expose globals
window.DS = window.DS || DS;

View file

@ -1,8 +0,0 @@
export default function reactComponents(className) {
switch (className) {
case 'TypesDeChampEditor':
return import('components/TypesDeChampEditor').then(
mod => mod.createReactUJSElement
);
}
}

View file

@ -1,61 +0,0 @@
import ReactDOM from 'react-dom';
// This attribute holds the name of component which should be mounted
// example: `data-react-class="MyApp.Items.EditForm"`
const CLASS_NAME_ATTR = 'data-react-class';
// This attribute holds JSON stringified props for initializing the component
// example: `data-react-props="{\"item\": { \"id\": 1, \"name\": \"My Item\"} }"`
const PROPS_ATTR = 'data-react-props';
// This attribute holds which method to use between: ReactDOM.hydrate, ReactDOM.render
const RENDER_ATTR = 'data-hydrate';
function findDOMNodes() {
return document.querySelectorAll(`[${CLASS_NAME_ATTR}]`);
}
export default class ReactUJS {
constructor(loadComponent) {
this.loadComponent = loadComponent;
}
async mountComponents() {
const nodes = findDOMNodes();
for (let node of nodes) {
const className = node.getAttribute(CLASS_NAME_ATTR);
const createReactUJSElement = await this.loadComponent(className).catch(
() => null
);
if (!createReactUJSElement) {
const message = "Cannot find component: '" + className + "'";
// eslint-disable-next-line no-console
console.error(
'%c[react-rails] %c' + message + ' for element',
'font-weight: bold',
'',
node
);
throw new Error(
message + '. Make sure your component is available to render.'
);
} else {
const propsJson = node.getAttribute(PROPS_ATTR);
const props = propsJson && JSON.parse(propsJson);
const hydrate = node.getAttribute(RENDER_ATTR);
if (hydrate && typeof ReactDOM.hydrate === 'function') {
ReactDOM.hydrate(createReactUJSElement(props), node);
} else {
ReactDOM.render(createReactUJSElement(props), node);
}
}
}
}
start() {
addEventListener('ds:page:update', () => this.mountComponents());
}
}

View file

@ -5,7 +5,7 @@
required: champ.mandatory?,
pattern: "[0-9]{14}",
title: "Le numéro de SIRET doit comporter exactement 14 chiffres"
.spinner.right-spinner.hidden
.spinner.right.hidden
%div{ class: "siret-info-#{form.index}" }
- if champ.etablissement.present?
= render partial: 'shared/dossiers/editable_champs/etablissement_titre', locals: { etablissement: champ.etablissement }

View file

@ -27,6 +27,7 @@
"react": "^16.11.0",
"react-dom": "^16.11.0",
"react-intersection-observer": "^8.25.1",
"react-loadable": "^5.5.0",
"react-scroll-to-component": "^1.0.2",
"react-sortable-hoc": "^1.10.1",
"react_ujs": "^2.6.0",

View file

@ -6953,7 +6953,7 @@ promise-inflight@^1.0.1:
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.6.2, prop-types@^15.7.2:
prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@ -7142,6 +7142,13 @@ react-is@^16.8.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==
react-loadable@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/react-loadable/-/react-loadable-5.5.0.tgz#582251679d3da86c32aae2c8e689c59f1196d8c4"
integrity sha512-C8Aui0ZpMd4KokxRdVAm2bQtI03k2RMRNzOB+IipV3yxFTSVICv7WoUr5L9ALB5BmKO1iHgZtWM8EvYG83otdg==
dependencies:
prop-types "^15.5.0"
react-scroll-to-component@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/react-scroll-to-component/-/react-scroll-to-component-1.0.2.tgz#f260dc936c62a53e772786d7832fe0884e195354"