1
0
mirror of https://gitlab.nic.cz/turris/reforis/foris-js.git synced 2024-10-04 12:14:19 +02:00

Merge branch 'dev' into 'master'

Publish 2.0.0.

See merge request turris/reforis/foris-js!57
This commit is contained in:
Bogdan Bodnar 2019-12-09 10:51:49 +01:00
commit a8d8c872f9
10 changed files with 87 additions and 32 deletions

View File

@ -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
View File

@ -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": {

View File

@ -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": {

View File

@ -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__

View File

@ -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];
}; };
} }

View File

@ -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>}
</> </>
); );
} }

View File

@ -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() {

View File

@ -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>
`; `;

View File

@ -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() {

View File

@ -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.
setData(msg.data); if (!ws) return;
});
function callback(msg) {
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];