1
0
mirror of https://gitlab.nic.cz/turris/reforis/foris-js.git synced 2025-06-15 13:36:35 +02:00

Compare commits

...

17 Commits

Author SHA1 Message Date
c114579b7f Merge branch 'dev' into 'master'
Release 1.2.0

See merge request turris/reforis/foris-js!28
2019-10-24 13:52:35 +00:00
22b2bc9c09 Merge branch 'release-1.2.0' into 'dev'
Release 1.2.0

See merge request turris/reforis/foris-js!27

[skip ci]
2019-10-24 11:54:36 +00:00
cab2cfa068 Release 1.2.0 2019-10-24 13:47:25 +02:00
a137a0d4cf Merge branch 'dev' into 'master'
Release 1.1.1

See merge request turris/reforis/foris-js!26
2019-10-24 11:24:57 +00:00
54cf7e3c06 Merge branch 'master' into dev 2019-10-24 10:47:13 +02:00
18eb28f368 Merge branch 'radio-checkbox-whitespace' into 'dev'
Radio checkbox whitespace

See merge request turris/reforis/foris-js!25

[skip ci]
2019-10-24 08:12:39 +00:00
88f3836485 Radio checkbox whitespace 2019-10-24 08:12:39 +00:00
8f88b09e66 Merge branch 'dev' into 'master'
Release 1.1.0

See merge request turris/reforis/foris-js!24
2019-10-22 08:24:10 +00:00
f2aa28f172 Release 1.1.0 2019-10-22 08:24:10 +00:00
a88fbf63e9 Merge branch 'shell-quotes' into 'dev'
Added quotes to shell variables

See merge request turris/reforis/foris-js!23
2019-10-15 08:50:53 +00:00
ff9e8fdeb1 Added quotes to shell variables 2019-10-15 10:10:33 +02:00
8cd4ac8b44 Merge branch 'ws-connection-closed' into 'dev'
Download button

See merge request turris/reforis/foris-js!22
2019-10-11 14:11:14 +00:00
760e6d9243 Download button 2019-10-11 14:11:14 +00:00
2429f4662c Merge branch '5-versioning' into 'dev'
Changed beta versioning procedure

Closes #5

See merge request turris/reforis/foris-js!21
2019-10-11 08:25:06 +00:00
b320e6a860 Changed beta versioning procedure 2019-10-11 10:15:53 +02:00
1e3e9433ec Merge branch 'client-configuration' into 'dev'
Client configuration

See merge request turris/reforis/foris-js!20
2019-10-10 15:25:00 +00:00
e3a795e40d Client configuration 2019-10-10 15:25:00 +00:00
23 changed files with 250 additions and 162 deletions

View File

@ -35,6 +35,8 @@ lint:
test: test:
npm test npm test
test-js-update-snapshots:
npm test -- -u
create-messages: create-messages:
pybabel extract -F babel.cfg -o ./translations/forisjs.pot . pybabel extract -F babel.cfg -o ./translations/forisjs.pot .

View File

@ -6,7 +6,7 @@
Each commit to `dev` branch will result in publishing a new version of library Each commit to `dev` branch will result in publishing a new version of library
tagged `beta`. Versions names are based on commit SHA, e.g. tagged `beta`. Versions names are based on commit SHA, e.g.
`foris@0.1.0-d9073aa4.0`. `foris@0.1.0-beta.d9073aa4`.
### Preparing a release ### Preparing a release

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "foris", "name": "foris",
"version": "1.0.0", "version": "1.2.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "foris", "name": "foris",
"version": "1.0.0", "version": "1.2.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": {
@ -73,7 +73,6 @@
"test": "jest", "test": "jest",
"test:watch": "jest --watch", "test:watch": "jest --watch",
"test:coverage": "jest --coverage --colors", "test:coverage": "jest --coverage --colors",
"test:update-snapshots": "jest -u",
"docs": "npx styleguidist build ", "docs": "npx styleguidist build ",
"docs:watch": "styleguidist server" "docs:watch": "styleguidist server"
}, },

View File

@ -6,11 +6,12 @@ then
exit 1 exit 1
else else
# Need to replace "_" with "_" as GitLab CI won't accept secret vars with "-" # Need to replace "_" with "_" as GitLab CI won't accept secret vars with "-"
echo "//registry.npmjs.org/:_authToken=$(echo $NPM_TOKEN | tr _ -)" > .npmrc echo "//registry.npmjs.org/:_authToken=$(echo "$NPM_TOKEN" | tr _ -)" > .npmrc
echo "unsafe-perm = true" >> ~/.npmrc echo "unsafe-perm = true" >> ~/.npmrc
if test "$1" = "beta" if test "$1" = "beta"
then then
npm version prerelease --preid=$CI_COMMIT_SHORT_SHA --git-tag-version false BETA_VERSION=$(npx -c 'echo "$npm_package_version"')-beta.$CI_COMMIT_SHORT_SHA
npm version "$BETA_VERSION" --git-tag-version false
npm publish --tag beta npm publish --tag beta
elif test "$1" = "latest" elif test "$1" = "latest"
then then

View File

@ -12,7 +12,7 @@ import {
API_ACTIONS, TIMEOUT, HEADERS, APIReducer, getErrorMessage, API_ACTIONS, TIMEOUT, HEADERS, APIReducer, getErrorMessage,
} from "./utils"; } from "./utils";
export function useAPIPost(url) { export function useAPIPost(url, contentType) {
const [state, dispatch] = useReducer(APIReducer, { const [state, dispatch] = useReducer(APIReducer, {
isSending: false, isSending: false,
isError: false, isError: false,
@ -20,12 +20,17 @@ export function useAPIPost(url) {
data: null, data: null,
}); });
const headers = { ...HEADERS };
if (contentType) {
headers["Content-Type"] = contentType;
}
const post = async (data) => { const post = async (data) => {
dispatch({ type: API_ACTIONS.INIT }); dispatch({ type: API_ACTIONS.INIT });
try { try {
const result = await axios.post(url, data, { const result = await axios.post(url, data, {
timeout: TIMEOUT, timeout: TIMEOUT,
headers: HEADERS, headers,
}); });
dispatch({ type: API_ACTIONS.SUCCESS, payload: result.data }); dispatch({ type: API_ACTIONS.SUCCESS, payload: result.data });
} catch (error) { } catch (error) {

View File

@ -7,7 +7,7 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useUID } from "react-uid/dist/es5/index"; import { useUID } from "react-uid";
import { formFieldsSize } from "./constants"; import { formFieldsSize } from "./constants";
@ -32,8 +32,8 @@ export function CheckBox({
}) { }) {
const uid = useUID(); const uid = useUID();
return ( return (
<div className={useDefaultSize ? formFieldsSize : ""} style={{ marginBottom: "1rem" }}> <div className={`form-group ${useDefaultSize ? formFieldsSize : ""}`.trim()}>
<div className="custom-control custom-checkbox" style={{ marginBottom: "0" }}> <div className="custom-control custom-checkbox ">
<input <input
className="custom-control-input" className="custom-control-input"
type="checkbox" type="checkbox"
@ -42,9 +42,9 @@ export function CheckBox({
{...props} {...props}
/> />
<label className="custom-control-label" htmlFor={uid} style={helpText ? { marginBottom: "0" } : null}>{label}</label> <label className="custom-control-label" htmlFor={uid}>{label}</label>
{helpText && <small className="form-text text-muted">{helpText}</small>}
</div> </div>
{helpText ? <small className="form-text text-muted">{helpText}</small> : null}
</div> </div>
); );
} }

View File

@ -0,0 +1,21 @@
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React from "react";
import PropTypes from "prop-types";
DownloadButton.propTypes = {
href: PropTypes.string.isRequired,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]),
};
export function DownloadButton({ href, children }) {
return <a href={href} className="btn btn-primary" download>{children}</a>;
}

View File

@ -0,0 +1,7 @@
Hyperlink with apperance of a button.
It has `download` attribute, which prevents closing WebSocket connection on Firefox. See [related issue](https://bugzilla.mozilla.org/show_bug.cgi?id=858538) for more details.
```js
<DownloadButton href="example.zip">Download</DownloadButton>
```

View File

@ -0,0 +1,34 @@
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React from "react";
import PropTypes from "prop-types";
import { Input } from "./Input";
FileInput.propTypes = {
/** Field label. */
label: PropTypes.string.isRequired,
/** Error message. */
error: PropTypes.string,
/** Help text message. */
helpText: PropTypes.string,
/** Email value. */
value: PropTypes.string,
};
export function FileInput({ ...props }) {
return (
<Input
type="file"
className="custom-file-input"
labelClassName="custom-file-label"
groupClassName="custom-file"
{...props}
/>
);
}

View File

@ -0,0 +1,15 @@
Bootstrap component for file input. Includes label and has predefined sizes and structure for using in foris forms.
All additional `props` are passed to the `<input type="file">` HTML component.
```js
import {useState} from 'react';
const [files, setFiles] = useState([]);
<FileInput
files={files}
label="Some file"
helpText="Will be uploaded"
onChange={event =>setFiles(event.target.files)}
/>
```

View File

@ -6,7 +6,7 @@
*/ */
import React from "react"; import React from "react";
import { useUID } from "react-uid/dist/es5/index"; import { useUID } from "react-uid";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { formFieldsSize } from "./constants"; import { formFieldsSize } from "./constants";
@ -21,31 +21,31 @@ Input.propTypes = {
PropTypes.arrayOf(PropTypes.node), PropTypes.arrayOf(PropTypes.node),
PropTypes.node, PropTypes.node,
]), ]),
labelClassName: PropTypes.string,
groupClassName: PropTypes.string,
}; };
/** Base bootstrap input component. */ /** Base bootstrap input component. */
export function Input({ export function Input({
type, label, helpText, error, className, children, ...props type, label, helpText, error, className, children, labelClassName, groupClassName, ...props
}) { }) {
const uid = useUID(); const uid = useUID();
const inputClassName = `form-control ${className || ""} ${(error ? "is-invalid" : "")}`.trim(); const inputClassName = `form-control ${className || ""} ${(error ? "is-invalid" : "")}`.trim();
return ( return (
<div className={formFieldsSize}> <div className={`form-group ${formFieldsSize}`}>
<div className="form-group"> <label className={labelClassName} htmlFor={uid}>{label}</label>
<label htmlFor={uid}>{label}</label> <div className={`input-group ${groupClassName || ""}`.trim()}>
<div className="input-group"> <input
<input className={inputClassName}
className={inputClassName} type={type}
type={type} id={uid}
id={uid}
{...props} {...props}
/> />
{children} {children}
</div>
{error ? <div className="invalid-feedback">{error}</div> : null}
{helpText ? <small className="form-text text-muted">{helpText}</small> : null}
</div> </div>
{error ? <div className="invalid-feedback">{error}</div> : null}
{helpText ? <small className="form-text text-muted">{helpText}</small> : null}
</div> </div>
); );
} }

View File

@ -7,7 +7,7 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useUID } from "react-uid/dist/es5/index"; import { useUID } from "react-uid";
import { formFieldsSize } from "./constants"; import { formFieldsSize } from "./constants";
@ -52,16 +52,10 @@ export function RadioSet({
}); });
return ( return (
<div className={`form-group ${formFieldsSize}`} style={{ marginBottom: "1rem" }}> <div className={`form-group ${formFieldsSize}`}>
{label {label && <label htmlFor={uid} className="d-block">{label}</label>}
? (
<label className="col-12" htmlFor={uid} style={{ paddingLeft: "0" }}>
{label}
</label>
)
: null}
{radios} {radios}
{helpText ? <small className="form-text text-muted">{helpText}</small> : null} {helpText && <small className="form-text text-muted">{helpText}</small>}
</div> </div>
); );
} }
@ -77,7 +71,7 @@ function Radio({
}) { }) {
return ( return (
<> <>
<div className="custom-control custom-radio custom-control-inline"> <div className={`custom-control custom-radio ${!helpText ? "custom-control-inline" : ""}`.trim()}>
<input <input
id={id} id={id}
className="custom-control-input" className="custom-control-input"
@ -86,8 +80,8 @@ function Radio({
{...props} {...props}
/> />
<label className="custom-control-label" htmlFor={id}>{label}</label> <label className="custom-control-label" htmlFor={id}>{label}</label>
{helpText && <small className="form-text text-muted mt-0 mb-3">{helpText}</small>}
</div> </div>
{helpText ? <small className="form-text text-muted">{helpText}</small> : null}
</> </>
); );
} }

View File

@ -2,6 +2,8 @@ Set of radio Bootstrap component input with label and predefined sizes and struc
All additional `props` are passed to the `<input type="number">` HTML component. All additional `props` are passed to the `<input type="number">` HTML component.
Unless `helpText` is set for one of the options they are displayed inline.
```js ```js
import {useState} from 'react'; import {useState} from 'react';
const CHOICES=[ const CHOICES=[

View File

@ -7,7 +7,7 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useUID } from "react-uid/dist/es5/index"; import { useUID } from "react-uid";
Select.propTypes = { Select.propTypes = {

View File

@ -0,0 +1,19 @@
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React from "react";
import { render } from "customTestRender";
import { DownloadButton } from "../DownloadButton";
describe("<DownloadButton />", () => {
it("should have download attribute", () => {
const { container } = render(<DownloadButton href="http://example.com">Test Button</DownloadButton>);
expect(container.firstChild.getAttribute("download")).not.toBeNull();
});
});

View File

@ -2,12 +2,10 @@
exports[`<Checkbox/> Render checkbox 1`] = ` exports[`<Checkbox/> Render checkbox 1`] = `
<div <div
class="col-sm-12 offset-lg-1 col-lg-10" class="form-group col-sm-12 offset-lg-1 col-lg-10"
style="margin-bottom: 1rem;"
> >
<div <div
class="custom-control custom-checkbox" class="custom-control custom-checkbox "
style="margin-bottom: 0px;"
> >
<input <input
checked="" checked=""
@ -18,27 +16,24 @@ exports[`<Checkbox/> Render checkbox 1`] = `
<label <label
class="custom-control-label" class="custom-control-label"
for="1" for="1"
style="margin-bottom: 0px;"
> >
Test label Test label
</label> </label>
<small
class="form-text text-muted"
>
Some help text
</small>
</div> </div>
<small
class="form-text text-muted"
>
Some help text
</small>
</div> </div>
`; `;
exports[`<Checkbox/> Render uncheked checkbox 1`] = ` exports[`<Checkbox/> Render uncheked checkbox 1`] = `
<div <div
class="col-sm-12 offset-lg-1 col-lg-10" class="form-group col-sm-12 offset-lg-1 col-lg-10"
style="margin-bottom: 1rem;"
> >
<div <div
class="custom-control custom-checkbox" class="custom-control custom-checkbox "
style="margin-bottom: 0px;"
> >
<input <input
class="custom-control-input" class="custom-control-input"
@ -48,15 +43,14 @@ exports[`<Checkbox/> Render uncheked checkbox 1`] = `
<label <label
class="custom-control-label" class="custom-control-label"
for="1" for="1"
style="margin-bottom: 0px;"
> >
Test label Test label
</label> </label>
<small
class="form-text text-muted"
>
Some help text
</small>
</div> </div>
<small
class="form-text text-muted"
>
Some help text
</small>
</div> </div>
`; `;

View File

@ -2,53 +2,49 @@
exports[`<NumberInput/> Render number input 1`] = ` exports[`<NumberInput/> Render number input 1`] = `
<div <div
class="col-sm-12 offset-lg-1 col-lg-10" class="form-group col-sm-12 offset-lg-1 col-lg-10"
> >
<div <label
class="form-group" for="1"
> >
<label Test label
for="1" </label>
> <div
Test label class="input-group"
</label> >
<input
class="form-control"
id="1"
type="number"
value="1"
/>
<div <div
class="input-group" class="input-group-append"
> >
<input <button
class="form-control" aria-label="Increase"
id="1" class="btn btn-outline-secondary"
type="number" type="button"
value="1"
/>
<div
class="input-group-append"
> >
<button <i
aria-label="Increase" class="fas fa-plus"
class="btn btn-outline-secondary" />
type="button" </button>
> <button
<i aria-label="Decrease"
class="fas fa-plus" class="btn btn-outline-secondary"
/> type="button"
</button> >
<button <i
aria-label="Decrease" class="fas fa-minus"
class="btn btn-outline-secondary" />
type="button" </button>
>
<i
class="fas fa-minus"
/>
</button>
</div>
</div> </div>
<small
class="form-text text-muted"
>
Some help text
</small>
</div> </div>
<small
class="form-text text-muted"
>
Some help text
</small>
</div> </div>
`; `;

View File

@ -2,32 +2,28 @@
exports[`<PasswordInput/> Render password input 1`] = ` exports[`<PasswordInput/> Render password input 1`] = `
<div <div
class="col-sm-12 offset-lg-1 col-lg-10" class="form-group col-sm-12 offset-lg-1 col-lg-10"
> >
<div <label
class="form-group" for="1"
> >
<label Test label
for="1" </label>
> <div
Test label class="input-group"
</label> >
<div <input
class="input-group" autocomplete="new-password"
> class="form-control"
<input id="1"
autocomplete="new-password" type="password"
class="form-control" value="Some password"
id="1" />
type="password"
value="Some password"
/>
</div>
<small
class="form-text text-muted"
>
Some help text
</small>
</div> </div>
<small
class="form-text text-muted"
>
Some help text
</small>
</div> </div>
`; `;

View File

@ -3,12 +3,10 @@
exports[`<RadioSet/> Render radio set 1`] = ` exports[`<RadioSet/> Render radio set 1`] = `
<div <div
class="form-group col-sm-12 offset-lg-1 col-lg-10" class="form-group col-sm-12 offset-lg-1 col-lg-10"
style="margin-bottom: 1rem;"
> >
<label <label
class="col-12" class="d-block"
for="1" for="1"
style="padding-left: 0px;"
> >
Radios set label Radios set label
</label> </label>

View File

@ -2,31 +2,27 @@
exports[`<TextInput/> Render text input 1`] = ` exports[`<TextInput/> Render text input 1`] = `
<div <div
class="col-sm-12 offset-lg-1 col-lg-10" class="form-group col-sm-12 offset-lg-1 col-lg-10"
> >
<div <label
class="form-group" for="1"
> >
<label Test label
for="1" </label>
> <div
Test label class="input-group"
</label> >
<div <input
class="input-group" class="form-control"
> id="1"
<input type="text"
class="form-control" value="Some text"
id="1" />
type="text"
value="Some text"
/>
</div>
<small
class="form-text text-muted"
>
Some help text
</small>
</div> </div>
<small
class="form-text text-muted"
>
Some help text
</small>
</div> </div>
`; `;

View File

@ -17,7 +17,7 @@ const FORM_ACTIONS = {
resetData: 2, resetData: 2,
}; };
export function useForm(validator, prepData) { export function useForm(validator, dataPreprocessor) {
const [state, dispatch] = useReducer(formReducer, { const [state, dispatch] = useReducer(formReducer, {
data: null, data: null,
initialData: null, initialData: null,
@ -28,10 +28,10 @@ export function useForm(validator, prepData) {
dispatch({ dispatch({
type: FORM_ACTIONS.resetData, type: FORM_ACTIONS.resetData,
data, data,
prepData, dataPreprocessor,
validator, validator,
}); });
}, [prepData, validator]); }, [dataPreprocessor, validator]);
const onFormChangeHandler = useCallback((updateRule) => (event) => { const onFormChangeHandler = useCallback((updateRule) => (event) => {
dispatch({ dispatch({
@ -41,6 +41,7 @@ export function useForm(validator, prepData) {
validator, validator,
}); });
}, [validator]); }, [validator]);
return [ return [
state, state,
onFormChangeHandler, onFormChangeHandler,
@ -61,12 +62,15 @@ function formReducer(state, action) {
}; };
} }
case FORM_ACTIONS.resetData: { case FORM_ACTIONS.resetData: {
if (!action.data) return { ...state, initialData: state.data }; if (!action.data) {
const prepData = action.prepData ? action.prepData(action.data) : action.data; return { ...state, initialData: state.data };
}
const data = action.dataPreprocessor ? action.dataPreprocessor(action.data) : action.data;
return { return {
data: prepData, data,
initialData: prepData, initialData: data,
errors: action.data ? action.validator(prepData) : undefined, errors: action.data ? action.validator(data) : undefined,
}; };
} }
default: { default: {
@ -82,6 +86,9 @@ function getChangedValue(target) {
} else if (target.type === "number") { } else if (target.type === "number") {
const parsedValue = parseInt(value); const parsedValue = parseInt(value);
value = Number.isNaN(parsedValue) ? value : parsedValue; value = Number.isNaN(parsedValue) ? value : parsedValue;
} else if (target.type === "file") {
// Return first file (we don't need multiple yet)
[value] = target.files;
} }
return value; return value;
} }

View File

@ -15,15 +15,17 @@ export { useAPIPatch } from "api/patch";
export { Alert } from "bootstrap/Alert"; export { Alert } from "bootstrap/Alert";
export { Button } from "bootstrap/Button"; export { Button } from "bootstrap/Button";
export { CheckBox } from "bootstrap/CheckBox"; export { CheckBox } from "bootstrap/CheckBox";
export { formFieldsSize } from "bootstrap/constants"; export { DownloadButton } from "bootstrap/DownloadButton";
export { DataTimeInput } from "bootstrap/DataTimeInput"; export { DataTimeInput } from "bootstrap/DataTimeInput";
export { EmailInput } from "bootstrap/EmailInput"; export { EmailInput } from "bootstrap/EmailInput";
export { FileInput } from "bootstrap/FileInput";
export { Input } from "bootstrap/Input"; export { Input } from "bootstrap/Input";
export { NumberInput } from "bootstrap/NumberInput"; export { NumberInput } from "bootstrap/NumberInput";
export { PasswordInput } from "bootstrap/PasswordInput"; export { PasswordInput } from "bootstrap/PasswordInput";
export { RadioSet } from "bootstrap/RadioSet"; export { RadioSet } from "bootstrap/RadioSet";
export { Select } from "bootstrap/Select"; export { Select } from "bootstrap/Select";
export { TextInput } from "bootstrap/TextInput"; export { TextInput } from "bootstrap/TextInput";
export { formFieldsSize } from "bootstrap/constants";
export { export {
Spinner, Spinner,