mirror of
https://gitlab.nic.cz/turris/reforis/foris-js.git
synced 2024-12-26 00:21:36 +01:00
Merge branch 'dev' into 'master'
Publish 2.0.0. See merge request turris/reforis/foris-js!57
This commit is contained in:
commit
a8d8c872f9
|
@ -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() {
|
||||||
|
|
|
@ -33,9 +33,6 @@ exports[`conditional HOCs withSpinner should render spinner 1`] = `
|
||||||
class="sr-only"
|
class="sr-only"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="spinner-text"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
@ -53,9 +50,6 @@ exports[`conditional HOCs withSpinnerOnSending should render spinner 1`] = `
|
||||||
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];
|
||||||
|
|
Loading…
Reference in New Issue
Block a user