Simplify React components loader
This commit is contained in:
parent
39afa6659c
commit
2cf415dc41
10 changed files with 107 additions and 147 deletions
|
@ -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 {
|
||||
|
|
8
app/javascript/components/Loadable.js
Normal file
8
app/javascript/components/Loadable.js
Normal 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 });
|
||||
}
|
|
@ -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;
|
|
@ -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'));
|
||||
|
|
|
@ -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;
|
||||
|
|
8
app/javascript/shared/react-components.js
vendored
8
app/javascript/shared/react-components.js
vendored
|
@ -1,8 +0,0 @@
|
|||
export default function reactComponents(className) {
|
||||
switch (className) {
|
||||
case 'TypesDeChampEditor':
|
||||
return import('components/TypesDeChampEditor').then(
|
||||
mod => mod.createReactUJSElement
|
||||
);
|
||||
}
|
||||
}
|
61
app/javascript/shared/react-ujs.js
vendored
61
app/javascript/shared/react-ujs.js
vendored
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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 }
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue