mirror of
				https://gitlab.nic.cz/turris/reforis/foris-js.git
				synced 2025-11-03 23:00:31 +01:00 
			
		
		
		
	Merge branch 'dev' into 'master'
Publish 2.0.0. See merge request turris/reforis/foris-js!57
This commit is contained in:
		@@ -1,4 +1,5 @@
 | 
				
			|||||||
# foris-js
 | 
					# foris-js
 | 
				
			||||||
 | 
					Set of utils and common React elements for reForis.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Publishing package
 | 
					## Publishing package
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "foris",
 | 
					  "name": "foris",
 | 
				
			||||||
  "version": "1.4.0",
 | 
					  "version": "2.0.0",
 | 
				
			||||||
  "lockfileVersion": 1,
 | 
					  "lockfileVersion": 1,
 | 
				
			||||||
  "requires": true,
 | 
					  "requires": true,
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "foris",
 | 
					  "name": "foris",
 | 
				
			||||||
  "version": "1.4.0",
 | 
					  "version": "2.0.0",
 | 
				
			||||||
  "description": "Set of components and utils for Foris and its plugins.",
 | 
					  "description": "Set of components and utils for Foris and its plugins.",
 | 
				
			||||||
  "author": "CZ.NIC, z.s.p.o.",
 | 
					  "author": "CZ.NIC, z.s.p.o.",
 | 
				
			||||||
  "repository": {
 | 
					  "repository": {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Collect files
 | 
					# Collect files
 | 
				
			||||||
npm run build
 | 
					npm run build
 | 
				
			||||||
cp package.json dist
 | 
					cp package.json README.md dist
 | 
				
			||||||
cp -rf translations dist
 | 
					cp -rf translations dist
 | 
				
			||||||
# Remove unwanted files
 | 
					# Remove unwanted files
 | 
				
			||||||
rm -rf dist/**/__tests__
 | 
					rm -rf dist/**/__tests__
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,13 +17,13 @@ import {
 | 
				
			|||||||
const DATA_METHODS = ["POST", "PATCH", "PUT"];
 | 
					const DATA_METHODS = ["POST", "PATCH", "PUT"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function createAPIHook(method) {
 | 
					function createAPIHook(method) {
 | 
				
			||||||
    return (url, contentType) => {
 | 
					    return (urlRoot, contentType) => {
 | 
				
			||||||
        const [state, dispatch] = useReducer(APIReducer, {
 | 
					        const [state, dispatch] = useReducer(APIReducer, {
 | 
				
			||||||
            state: API_STATE.INIT,
 | 
					            state: API_STATE.INIT,
 | 
				
			||||||
            data: null,
 | 
					            data: null,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const sendRequest = useCallback(async (data) => {
 | 
					        const sendRequest = useCallback(async ({ data, suffix } = {}) => {
 | 
				
			||||||
            const headers = { ...HEADERS };
 | 
					            const headers = { ...HEADERS };
 | 
				
			||||||
            if (contentType) {
 | 
					            if (contentType) {
 | 
				
			||||||
                headers["Content-Type"] = contentType;
 | 
					                headers["Content-Type"] = contentType;
 | 
				
			||||||
@@ -31,17 +31,23 @@ function createAPIHook(method) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            dispatch({ type: API_ACTIONS.INIT });
 | 
					            dispatch({ type: API_ACTIONS.INIT });
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
 | 
					                // Prepare request
 | 
				
			||||||
                const request = API_METHODS[method];
 | 
					                const request = API_METHODS[method];
 | 
				
			||||||
                const config = {
 | 
					                const config = {
 | 
				
			||||||
                    timeout: TIMEOUT,
 | 
					                    timeout: TIMEOUT,
 | 
				
			||||||
                    headers,
 | 
					                    headers,
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
 | 
					                const url = suffix ? `${urlRoot}/${suffix}` : urlRoot;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Make request
 | 
				
			||||||
                let result;
 | 
					                let result;
 | 
				
			||||||
                if (DATA_METHODS.includes(method)) {
 | 
					                if (DATA_METHODS.includes(method)) {
 | 
				
			||||||
                    result = await request(url, data, config);
 | 
					                    result = await request(url, data, config);
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    result = await request(url, config);
 | 
					                    result = await request(url, config);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Process request result
 | 
				
			||||||
                dispatch({
 | 
					                dispatch({
 | 
				
			||||||
                    type: API_ACTIONS.SUCCESS,
 | 
					                    type: API_ACTIONS.SUCCESS,
 | 
				
			||||||
                    payload: result.data,
 | 
					                    payload: result.data,
 | 
				
			||||||
@@ -53,7 +59,7 @@ function createAPIHook(method) {
 | 
				
			|||||||
                    payload: getErrorPayload(error),
 | 
					                    payload: getErrorPayload(error),
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }, [url, contentType]);
 | 
					        }, [urlRoot, contentType]);
 | 
				
			||||||
        return [state, sendRequest];
 | 
					        return [state, sendRequest];
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -46,6 +46,8 @@ export function Spinner({
 | 
				
			|||||||
SpinnerElement.propTypes = {
 | 
					SpinnerElement.propTypes = {
 | 
				
			||||||
    /** Spinner's size */
 | 
					    /** Spinner's size */
 | 
				
			||||||
    small: PropTypes.bool,
 | 
					    small: PropTypes.bool,
 | 
				
			||||||
 | 
					    /** Additional className */
 | 
				
			||||||
 | 
					    className: PropTypes.string,
 | 
				
			||||||
    /** Children components */
 | 
					    /** Children components */
 | 
				
			||||||
    children: PropTypes.oneOfType([
 | 
					    children: PropTypes.oneOfType([
 | 
				
			||||||
        PropTypes.arrayOf(PropTypes.node),
 | 
					        PropTypes.arrayOf(PropTypes.node),
 | 
				
			||||||
@@ -53,13 +55,16 @@ SpinnerElement.propTypes = {
 | 
				
			|||||||
    ]),
 | 
					    ]),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function SpinnerElement({ small, children }) {
 | 
					export function SpinnerElement({ small, className, children }) {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <>
 | 
					        <>
 | 
				
			||||||
            <div className={`spinner-border ${small ? "spinner-border-sm" : ""}`} role="status">
 | 
					            <div
 | 
				
			||||||
 | 
					                className={`spinner-border ${small ? "spinner-border-sm" : ""} ${className || ""}`.trim()}
 | 
				
			||||||
 | 
					                role="status"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
                <span className="sr-only" />
 | 
					                <span className="sr-only" />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div className="spinner-text">{children}</div>
 | 
					            {children && <div className="spinner-text">{children}</div>}
 | 
				
			||||||
        </>
 | 
					        </>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,14 +20,16 @@ import { useForisModule, useForm } from "../hooks";
 | 
				
			|||||||
import { STATES as SUBMIT_BUTTON_STATES, SubmitButton } from "./SubmitButton";
 | 
					import { STATES as SUBMIT_BUTTON_STATES, SubmitButton } from "./SubmitButton";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ForisForm.propTypes = {
 | 
					ForisForm.propTypes = {
 | 
				
			||||||
    /** WebSocket object see `scr/common/WebSockets.js`. */
 | 
					    /** Optional WebSocket object. See `scr/common/WebSockets.js`.
 | 
				
			||||||
 | 
					     * `forisConfig.wsModule` should be specified when it's passed.
 | 
				
			||||||
 | 
					     *  */
 | 
				
			||||||
    ws: PropTypes.object,
 | 
					    ws: PropTypes.object,
 | 
				
			||||||
    /** Foris configuration object. See usage in main components. */
 | 
					    /** Foris configuration object. See usage in main components. */
 | 
				
			||||||
    forisConfig: PropTypes.shape({
 | 
					    forisConfig: PropTypes.shape({
 | 
				
			||||||
        /** reForis Flask aplication API endpoint from `src/common/API.js`. */
 | 
					        /** reForis Flask aplication API endpoint from `src/common/API.js`. */
 | 
				
			||||||
        endpoint: PropTypes.string.isRequired,
 | 
					        endpoint: PropTypes.string.isRequired,
 | 
				
			||||||
        /** `foris-controller` module name to be used via WebSockets.
 | 
					        /** `foris-controller` module name to be used via WebSockets.
 | 
				
			||||||
         *  If it's not passed then WebSockets aren't used
 | 
					         *  It can be use only with `ws` prop.
 | 
				
			||||||
         * */
 | 
					         * */
 | 
				
			||||||
        wsModule: PropTypes.string,
 | 
					        wsModule: PropTypes.string,
 | 
				
			||||||
        /** `foris-controller` action name to be used via WebSockets.
 | 
					        /** `foris-controller` action name to be used via WebSockets.
 | 
				
			||||||
@@ -49,6 +51,17 @@ ForisForm.propTypes = {
 | 
				
			|||||||
    children: PropTypes.node.isRequired,
 | 
					    children: PropTypes.node.isRequired,
 | 
				
			||||||
    /** Optional override of form submit callback */
 | 
					    /** Optional override of form submit callback */
 | 
				
			||||||
    onSubmitOverridden: PropTypes.func,
 | 
					    onSubmitOverridden: PropTypes.func,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // eslint-disable-next-line react/no-unused-prop-types
 | 
				
			||||||
 | 
					    customWSProp(props) {
 | 
				
			||||||
 | 
					        const wsModuleIsSpecified = !!(props.forisConfig && props.forisConfig.wsModule);
 | 
				
			||||||
 | 
					        if (props.ws && !wsModuleIsSpecified) {
 | 
				
			||||||
 | 
					            return new Error("forisConfig.wsModule should be specified when ws object is passed.");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (!props.ws && wsModuleIsSpecified) {
 | 
				
			||||||
 | 
					            return new Error("forisConfig.wsModule is specified without passing ws object.");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ForisForm.defaultProps = {
 | 
					ForisForm.defaultProps = {
 | 
				
			||||||
@@ -103,7 +116,7 @@ export function ForisForm({
 | 
				
			|||||||
        resetFormData();
 | 
					        resetFormData();
 | 
				
			||||||
        const copiedFormData = JSON.parse(JSON.stringify(formState.data));
 | 
					        const copiedFormData = JSON.parse(JSON.stringify(formState.data));
 | 
				
			||||||
        const preparedData = prepDataToSubmit(copiedFormData);
 | 
					        const preparedData = prepDataToSubmit(copiedFormData);
 | 
				
			||||||
        post(preparedData);
 | 
					        post({ data: preparedData });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function getSubmitButtonState() {
 | 
					    function getSubmitButtonState() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,16 +26,13 @@ exports[`conditional HOCs withSpinner should render spinner 1`] = `
 | 
				
			|||||||
    class="spinner-wrapper my-3 text-center"
 | 
					    class="spinner-wrapper my-3 text-center"
 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
    <div
 | 
					    <div
 | 
				
			||||||
      class="spinner-border "
 | 
					      class="spinner-border"
 | 
				
			||||||
      role="status"
 | 
					      role="status"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <span
 | 
					      <span
 | 
				
			||||||
        class="sr-only"
 | 
					        class="sr-only"
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <div
 | 
					 | 
				
			||||||
      class="spinner-text"
 | 
					 | 
				
			||||||
    />
 | 
					 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
`;
 | 
					`;
 | 
				
			||||||
@@ -46,16 +43,13 @@ exports[`conditional HOCs withSpinnerOnSending should render spinner 1`] = `
 | 
				
			|||||||
    class="spinner-wrapper my-3 text-center"
 | 
					    class="spinner-wrapper my-3 text-center"
 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
    <div
 | 
					    <div
 | 
				
			||||||
      class="spinner-border "
 | 
					      class="spinner-border"
 | 
				
			||||||
      role="status"
 | 
					      role="status"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <span
 | 
					      <span
 | 
				
			||||||
        class="sr-only"
 | 
					        class="sr-only"
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <div
 | 
					 | 
				
			||||||
      class="spinner-text"
 | 
					 | 
				
			||||||
    />
 | 
					 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
`;
 | 
					`;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -61,9 +61,36 @@ export class WebSockets {
 | 
				
			|||||||
        return this;
 | 
					        return this;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    subscribe(params) {
 | 
					    subscribe(module) {
 | 
				
			||||||
        this.waitForConnection(() => {
 | 
					        this.waitForConnection(() => {
 | 
				
			||||||
            this.send("subscribe", params);
 | 
					            this.send("subscribe", module);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    unbind(module, action, callback) {
 | 
				
			||||||
 | 
					        const callbacks = this.callbacks[module][action];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const index = callbacks.indexOf(callback);
 | 
				
			||||||
 | 
					        if (index !== -1) {
 | 
				
			||||||
 | 
					            callbacks.splice(index, 1);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (callbacks.length === 0) {
 | 
				
			||||||
 | 
					            delete this.callbacks[module][action];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (Object.keys(this.callbacks[module]).length === 0) {
 | 
				
			||||||
 | 
					            this.unsubscribe(module);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    unsubscribe(module) {
 | 
				
			||||||
 | 
					        this.waitForConnection(() => {
 | 
				
			||||||
 | 
					            this.send("unsubscribe", module);
 | 
				
			||||||
 | 
					            delete this.callbacks[module];
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        return this;
 | 
					        return this;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -82,15 +109,15 @@ export class WebSockets {
 | 
				
			|||||||
        let chain;
 | 
					        let chain;
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            chain = this.callbacks[json.module][json.action];
 | 
					            chain = this.callbacks[json.module][json.action];
 | 
				
			||||||
        } catch (e) {
 | 
					        } catch (error) {
 | 
				
			||||||
            if (e instanceof TypeError) {
 | 
					            if (error instanceof TypeError) {
 | 
				
			||||||
                console.log(`Callback for this message wasn't found:${e.data}`);
 | 
					                console.log(`Callback for this message wasn't found:${error.data}`);
 | 
				
			||||||
            } else throw e;
 | 
					            } else throw error;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (typeof chain === "undefined") return;
 | 
					        if (typeof chain === "undefined") return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (let i = 0; i < chain.length; i++) chain[i](json);
 | 
					        chain.forEach((callback) => callback(json));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    close() {
 | 
					    close() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,12 +11,21 @@ export function useWSForisModule(ws, module, action = "update_settings") {
 | 
				
			|||||||
    const [data, setData] = useState(null);
 | 
					    const [data, setData] = useState(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
        if (ws && module) {
 | 
					        // Sometimes we want to disable this hook if WS is not passed. We can't make conditional
 | 
				
			||||||
            ws.subscribe(module)
 | 
					        // hooks, but we can disable it here. It's used especially in ForisForm when a module
 | 
				
			||||||
                .bind(module, action, (msg) => {
 | 
					        // doesn't present any WS endpoint.
 | 
				
			||||||
 | 
					        if (!ws) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function callback(msg) {
 | 
				
			||||||
            setData(msg.data);
 | 
					            setData(msg.data);
 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ws.subscribe(module)
 | 
				
			||||||
 | 
					            .bind(module, action, callback);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return () => {
 | 
				
			||||||
 | 
					            ws.unbind(module, action, callback);
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
    }, [action, module, ws]);
 | 
					    }, [action, module, ws]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return [data];
 | 
					    return [data];
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user