mirror of
https://gitlab.nic.cz/turris/reforis/foris-js.git
synced 2025-06-15 13:36:35 +02:00
Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
1009169315 | |||
661f92bbcf | |||
12b862c568 | |||
54f9f984f1 | |||
5dbc58d44b | |||
e7f9fbca96 | |||
8d40dbb841 | |||
cea8aa0c12 | |||
16a7a6c52d | |||
597b6fcf4c | |||
5eb6b90ed4 | |||
48c323c1a1 | |||
ae8baddbdd | |||
67e4abe4d1 | |||
57f1ccced8 | |||
1e95bff7ff | |||
0f253ecc19 | |||
a5e096dc00 | |||
182cbe698f | |||
982eb371ad | |||
2786f856f7 | |||
48b080dc26 |
@ -33,3 +33,12 @@ externals: {
|
||||
}
|
||||
```
|
||||
|
||||
### Docs
|
||||
Build or watch docs to get more info about library:
|
||||
```bash
|
||||
make docs
|
||||
```
|
||||
or
|
||||
```bash
|
||||
make docs-watch
|
||||
```
|
||||
|
22
docs/development.md
Normal file
22
docs/development.md
Normal file
@ -0,0 +1,22 @@
|
||||
Sooner or later you will face with situation when you want/need to make some changes in the library.
|
||||
Then the most important tool for you it's [`npm link`](https://docs.npmjs.com/cli/link).
|
||||
|
||||
Please, notice that it will not work if you link library just from root of the repo. It happens due to location of
|
||||
sources `./src`. You need to pack library first `make pack` and then link it from `./dist` directory.
|
||||
|
||||
Yeah it's not such comfortable solution for development. But it can fixed by writing small script similar as `make pack`
|
||||
but with linking every file and directory from `./src` to the some directory and linking then from it. Notice that you
|
||||
need to link `package.json` and `package-lock.json` as well.
|
||||
|
||||
So step by step:
|
||||
|
||||
```bash
|
||||
make pack;
|
||||
cd dist;
|
||||
npm link;
|
||||
|
||||
cd $project_dir/js # Navigate to JS directory of the project where you want to link the library
|
||||
npm link foris
|
||||
```
|
||||
|
||||
And that's it ;)
|
@ -1 +1,4 @@
|
||||
Foris JS library is set of componets and utils for Foris JS application and plugins.
|
||||
Foris JS library is set of components and utils for Foris JS application and plugins.
|
||||
|
||||
Please notice that all of these components or utils are used in reForis and plugins. If you like to study by example I would
|
||||
recommend to full-text search these repos.
|
||||
|
1954
package-lock.json
generated
1954
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "foris",
|
||||
"version": "4.5.1",
|
||||
"version": "5.0.3",
|
||||
"description": "Set of components and utils for Foris and its plugins.",
|
||||
"author": "CZ.NIC, z.s.p.o.",
|
||||
"repository": {
|
||||
@ -38,8 +38,11 @@
|
||||
"@testing-library/react": "^8.0.9",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"bootstrap": "^4.5.0",
|
||||
"css-loader": "^3.5.3",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-reforis": "^1.0.0",
|
||||
"file-loader": "^6.0.0",
|
||||
"jest": "^25.2.0",
|
||||
"jest-mock-axios": "^3.2.0",
|
||||
"moment-timezone": "^0.5.28",
|
||||
@ -48,7 +51,9 @@
|
||||
"react-dom": "16.9.0",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-styleguidist": "^10.6.2",
|
||||
"snapshot-diff": "^0.7.0"
|
||||
"snapshot-diff": "^0.7.0",
|
||||
"style-loader": "^1.2.1",
|
||||
"webpack": "^4.43.0"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint src",
|
||||
|
4
src/alertContext/AlertContext.md
Normal file
4
src/alertContext/AlertContext.md
Normal file
@ -0,0 +1,4 @@
|
||||
It provides alert context to children. `AlertContext` allows using `useAlert` in components.
|
||||
|
||||
Notice that `<div id="alert-container"/>` should be presented in HTML doc to get it work (In reForis it's already done
|
||||
with base Jinja2 templates).
|
@ -1,4 +1,5 @@
|
||||
Bootstrap alert component.
|
||||
|
||||
```jsx
|
||||
import {useState} from 'react';
|
||||
|
||||
@ -7,14 +8,14 @@ function AlertExample(){
|
||||
if (alert)
|
||||
return <Alert
|
||||
type='warning'
|
||||
message='Some warning out there!'
|
||||
onDismiss={()=>setAlert(false)}
|
||||
/>;
|
||||
>
|
||||
Some warning out there!
|
||||
</Alert>;
|
||||
return <button
|
||||
className='btn btn-secondary'
|
||||
onClick={()=>setAlert(true)}
|
||||
>Show alert again</button>;
|
||||
};
|
||||
<AlertExample/>
|
||||
|
||||
```
|
||||
|
@ -19,6 +19,8 @@ FileInput.propTypes = {
|
||||
helpText: PropTypes.string,
|
||||
/** Email value. */
|
||||
value: PropTypes.string,
|
||||
/** Allow selecting multiple files. */
|
||||
multiple: PropTypes.bool,
|
||||
};
|
||||
|
||||
export function FileInput({ ...props }) {
|
||||
|
@ -3,13 +3,39 @@ Bootstrap component for file input. Includes label and has predefined sizes and
|
||||
All additional `props` are passed to the `<input type="file">` HTML component.
|
||||
|
||||
```js
|
||||
import {useState} from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
const [files, setFiles] = useState([]);
|
||||
|
||||
<FileInput
|
||||
files={files}
|
||||
label="Some file"
|
||||
helpText="Will be uploaded"
|
||||
onChange={event =>setFiles(event.target.files)}
|
||||
/>
|
||||
// Note that files is not an array but FileList.
|
||||
const label = files.length === 1 ? files[0].name : "Choose file";
|
||||
|
||||
<form className="col">
|
||||
<FileInput
|
||||
files={files}
|
||||
label={label}
|
||||
helpText="Will be uploaded"
|
||||
onChange={event=>setFiles(event.target.files)}
|
||||
/>
|
||||
</form>
|
||||
```
|
||||
|
||||
### FileInput with multiple files
|
||||
```js
|
||||
import { useState } from 'react';
|
||||
|
||||
const [files, setFiles] = useState([]);
|
||||
|
||||
// Note that files is not an array but FileList.
|
||||
const label = files.length > 0 ? Array.from(files).map(file=>file.name).join(", ") : "Choose files";
|
||||
|
||||
<form className="col">
|
||||
<FileInput
|
||||
files={files}
|
||||
label={label}
|
||||
helpText="Will be uploaded"
|
||||
onChange={event=>setFiles(event.target.files)}
|
||||
multiple
|
||||
/>
|
||||
</form>
|
||||
```
|
||||
|
@ -6,8 +6,6 @@ it's required to have an element `<div id={"modal-container"}/>` somewhere on th
|
||||
<div id="modal-container"/>
|
||||
```
|
||||
|
||||
I have no idea why example doesn't work here but you can investigate HTML code and Foris project.
|
||||
|
||||
```js
|
||||
import {ModalHeader, ModalBody, ModalFooter} from './Modal';
|
||||
|
||||
|
@ -28,10 +28,11 @@ export function Select({
|
||||
}) {
|
||||
const uid = useUID();
|
||||
|
||||
const options = Object.keys(choices).map(
|
||||
const options = Object.keys(choices).sort(
|
||||
(a, b) => a - b || a.toString().localeCompare(b.toString()),
|
||||
).map(
|
||||
(key) => <option key={key} value={key}>{choices[key]}</option>,
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="form-group">
|
||||
<label htmlFor={uid}>{label}</label>
|
||||
|
@ -31,18 +31,19 @@ WiFiForm.propTypes = {
|
||||
|
||||
WiFiForm.defaultProps = {
|
||||
formData: { devices: [] },
|
||||
setFormValue: () => {},
|
||||
setFormValue: () => { },
|
||||
hasGuestNetwork: true,
|
||||
};
|
||||
|
||||
export default function WiFiForm({
|
||||
formData, formErrors, setFormValue, hasGuestNetwork, disabled,
|
||||
}) {
|
||||
return formData.devices.map((device) => (
|
||||
return formData.devices.map((device, index) => (
|
||||
<DeviceForm
|
||||
key={device.id}
|
||||
formData={device}
|
||||
formErrors={(formErrors || [])[device.id]}
|
||||
deviceIndex={index}
|
||||
formErrors={(formErrors || [])[index]}
|
||||
setFormValue={setFormValue}
|
||||
hasGuestNetwork={hasGuestNetwork}
|
||||
disabled={disabled}
|
||||
@ -65,6 +66,7 @@ DeviceForm.propTypes = {
|
||||
formErrors: PropTypes.object.isRequired,
|
||||
setFormValue: PropTypes.func.isRequired,
|
||||
hasGuestNetwork: PropTypes.bool,
|
||||
deviceIndex: PropTypes.number,
|
||||
};
|
||||
|
||||
DeviceForm.defaultProps = {
|
||||
@ -73,7 +75,7 @@ DeviceForm.defaultProps = {
|
||||
};
|
||||
|
||||
function DeviceForm({
|
||||
formData, formErrors, setFormValue, hasGuestNetwork, ...props
|
||||
formData, formErrors, setFormValue, hasGuestNetwork, deviceIndex, ...props
|
||||
}) {
|
||||
const deviceID = formData.id;
|
||||
return (
|
||||
@ -84,7 +86,7 @@ function DeviceForm({
|
||||
checked={formData.enabled}
|
||||
|
||||
onChange={setFormValue(
|
||||
(value) => ({ devices: { [deviceID]: { enabled: { $set: value } } } }),
|
||||
(value) => ({ devices: { [deviceIndex]: { enabled: { $set: value } } } }),
|
||||
)}
|
||||
|
||||
{...props}
|
||||
@ -98,7 +100,13 @@ function DeviceForm({
|
||||
error={formErrors.SSID || null}
|
||||
required
|
||||
onChange={setFormValue(
|
||||
(value) => ({ devices: { [deviceID]: { SSID: { $set: value } } } }),
|
||||
(value) => ({
|
||||
devices: {
|
||||
[deviceIndex]: {
|
||||
SSID: { $set: value },
|
||||
},
|
||||
},
|
||||
}),
|
||||
)}
|
||||
|
||||
{...props}
|
||||
@ -121,7 +129,7 @@ function DeviceForm({
|
||||
|
||||
onChange={setFormValue(
|
||||
(value) => (
|
||||
{ devices: { [deviceID]: { password: { $set: value } } } }
|
||||
{ devices: { [deviceIndex]: { password: { $set: value } } } }
|
||||
),
|
||||
)}
|
||||
|
||||
@ -135,7 +143,7 @@ function DeviceForm({
|
||||
|
||||
onChange={setFormValue(
|
||||
(value) => (
|
||||
{ devices: { [deviceID]: { hidden: { $set: value } } } }
|
||||
{ devices: { [deviceIndex]: { hidden: { $set: value } } } }
|
||||
),
|
||||
)}
|
||||
|
||||
@ -152,7 +160,7 @@ function DeviceForm({
|
||||
onChange={setFormValue(
|
||||
(value) => ({
|
||||
devices: {
|
||||
[deviceID]: {
|
||||
[deviceIndex]: {
|
||||
hwmode: { $set: value },
|
||||
channel: { $set: "0" },
|
||||
},
|
||||
@ -171,7 +179,7 @@ function DeviceForm({
|
||||
|
||||
onChange={setFormValue(
|
||||
(value) => (
|
||||
{ devices: { [deviceID]: { htmode: { $set: value } } } }
|
||||
{ devices: { [deviceIndex]: { htmode: { $set: value } } } }
|
||||
),
|
||||
)}
|
||||
|
||||
@ -185,7 +193,7 @@ function DeviceForm({
|
||||
|
||||
onChange={setFormValue(
|
||||
(value) => (
|
||||
{ devices: { [deviceID]: { channel: { $set: value } } } }
|
||||
{ devices: { [deviceIndex]: { channel: { $set: value } } } }
|
||||
),
|
||||
)}
|
||||
|
||||
@ -194,7 +202,7 @@ function DeviceForm({
|
||||
|
||||
{hasGuestNetwork && (
|
||||
<WifiGuestForm
|
||||
formData={{ id: deviceID, ...formData.guest_wifi }}
|
||||
formData={{ id: deviceIndex, ...formData.guest_wifi }}
|
||||
formErrors={formErrors.guest_wifi || {}}
|
||||
|
||||
setFormValue={setFormValue}
|
||||
|
@ -64,7 +64,7 @@ function prepDataToSubmit(formData) {
|
||||
return formData;
|
||||
}
|
||||
|
||||
function validator(formData) {
|
||||
export function validator(formData) {
|
||||
const formErrors = formData.devices.map(
|
||||
(device) => {
|
||||
if (!device.enabled) return {};
|
||||
@ -89,5 +89,5 @@ function validator(formData) {
|
||||
return errors;
|
||||
},
|
||||
);
|
||||
return JSON.stringify(formErrors) === "[{},{}]" ? null : formErrors;
|
||||
return JSON.stringify(formErrors).match(/\[[{},?]+\]/) ? null : formErrors;
|
||||
}
|
||||
|
@ -13,8 +13,8 @@ import { fireEvent, render, wait } from "customTestRender";
|
||||
import { WebSockets } from "webSockets/WebSockets";
|
||||
import { mockJSONError } from "testUtils/network";
|
||||
|
||||
import { wifiSettingsFixture } from "./__fixtures__/wifiSettings";
|
||||
import { WiFiSettings } from "../WiFiSettings";
|
||||
import { wifiSettingsFixture, oneDevice, twoDevices, threeDevices } from "./__fixtures__/wifiSettings";
|
||||
import { WiFiSettings, validator } from "../WiFiSettings";
|
||||
|
||||
describe("<WiFiSettings/>", () => {
|
||||
let firstRender;
|
||||
@ -159,4 +159,18 @@ describe("<WiFiSettings/>", () => {
|
||||
};
|
||||
expect(mockAxios.post).toHaveBeenCalledWith(endpoint, data, expect.anything());
|
||||
});
|
||||
|
||||
it("Validator function using regex for one device", () => {
|
||||
expect(validator(oneDevice)).toEqual(null);
|
||||
});
|
||||
|
||||
it("Validator function using regex for two devices", () => {
|
||||
const twoDevicesFormErrors = [{SSID: "SSID can't be empty"}, {}];
|
||||
expect(validator(twoDevices)).toEqual(twoDevicesFormErrors);
|
||||
});
|
||||
|
||||
it("Validator function using regex for three devices", () => {
|
||||
const threeDevicesFormErrors = [{}, {}, {password: "Password must contain at least 8 symbols"}];
|
||||
expect(validator(threeDevices)).toEqual(threeDevicesFormErrors);
|
||||
});
|
||||
});
|
||||
|
@ -316,3 +316,85 @@ export function wifiSettingsFixture() {
|
||||
],
|
||||
};
|
||||
}
|
||||
const oneDevice = {
|
||||
devices: [
|
||||
{
|
||||
SSID: "Turris1",
|
||||
channel: 60,
|
||||
enabled: true,
|
||||
guest_wifi: { enabled: false },
|
||||
hidden: false,
|
||||
htmode: "HT40",
|
||||
hwmode: "11a",
|
||||
id: 0,
|
||||
password: "TestPass"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const twoDevices = {
|
||||
devices: [
|
||||
{
|
||||
SSID: "",
|
||||
channel: 60,
|
||||
enabled: true,
|
||||
guest_wifi: { enabled: false },
|
||||
hidden: false,
|
||||
htmode: "HT40",
|
||||
hwmode: "11a",
|
||||
id: 0,
|
||||
password: "TestPass"
|
||||
},
|
||||
{
|
||||
SSID: "Turris2",
|
||||
channel: 60,
|
||||
enabled: true,
|
||||
guest_wifi: { enabled: false },
|
||||
hidden: false,
|
||||
htmode: "HT40",
|
||||
hwmode: "11a",
|
||||
id: 0,
|
||||
password: "TestPass"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const threeDevices = {
|
||||
devices: [
|
||||
{
|
||||
SSID: "Turris1",
|
||||
channel: 60,
|
||||
enabled: true,
|
||||
guest_wifi: { enabled: false },
|
||||
hidden: false,
|
||||
htmode: "HT40",
|
||||
hwmode: "11a",
|
||||
id: 0,
|
||||
password: "TestPass"
|
||||
},
|
||||
{
|
||||
SSID: "Turris2",
|
||||
channel: 60,
|
||||
enabled: false,
|
||||
guest_wifi: { enabled: false },
|
||||
hidden: false,
|
||||
htmode: "HT40",
|
||||
hwmode: "11a",
|
||||
id: 0,
|
||||
password: "TestPass"
|
||||
},
|
||||
{
|
||||
SSID: "Turris3",
|
||||
channel: 60,
|
||||
enabled: true,
|
||||
guest_wifi: { enabled: false },
|
||||
hidden: false,
|
||||
htmode: "HT40",
|
||||
hwmode: "11a",
|
||||
id: 0,
|
||||
password: ""
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export {oneDevice, twoDevices, threeDevices};
|
@ -617,11 +617,6 @@ exports[`<WiFiSettings/> Snapshot one module enabled. 1`] = `
|
||||
+ id=\\"8\\"
|
||||
+ >
|
||||
+ <option
|
||||
+ value=\\"NOHT\\"
|
||||
+ >
|
||||
+ Disabled
|
||||
+ </option>
|
||||
+ <option
|
||||
+ value=\\"HT20\\"
|
||||
+ >
|
||||
+ 802.11n - 20 MHz wide channel
|
||||
@ -632,6 +627,11 @@ exports[`<WiFiSettings/> Snapshot one module enabled. 1`] = `
|
||||
+ 802.11n - 40 MHz wide channel
|
||||
+ </option>
|
||||
+ <option
|
||||
+ value=\\"NOHT\\"
|
||||
+ >
|
||||
+ Disabled
|
||||
+ </option>
|
||||
+ <option
|
||||
+ value=\\"VHT20\\"
|
||||
+ >
|
||||
+ 802.11ac - 20 MHz wide channel
|
||||
|
@ -12,6 +12,7 @@ export const HTMODES = {
|
||||
VHT20: _("802.11ac - 20 MHz wide channel"),
|
||||
VHT40: _("802.11ac - 40 MHz wide channel"),
|
||||
VHT80: _("802.11ac - 80 MHz wide channel"),
|
||||
VHT160: _("802.11ac - 160 MHz wide channel"),
|
||||
};
|
||||
export const HWMODES = {
|
||||
"11g": "2.4",
|
||||
|
12
src/testUtils/mockGlobals.js
Normal file
12
src/testUtils/mockGlobals.js
Normal file
@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
|
||||
// Mock babel (gettext)
|
||||
global._ = (str) => str;
|
||||
global.ngettext = (str) => str;
|
||||
global.babel = { format: (str) => str };
|
||||
global.ForisTranslations = { locale: "en" };
|
@ -7,18 +7,13 @@
|
||||
|
||||
import mockAxios from "jest-mock-axios";
|
||||
import moment from "moment-timezone";
|
||||
import "./mockGlobals";
|
||||
|
||||
// Setup axios cleanup
|
||||
global.afterEach(() => {
|
||||
mockAxios.reset();
|
||||
});
|
||||
|
||||
// Mock babel (gettext)
|
||||
global._ = (str) => str;
|
||||
global.ngettext = (str) => str;
|
||||
global.babel = { format: (str) => str };
|
||||
global.ForisTranslations = { locale: "en" };
|
||||
|
||||
// Mock web sockets
|
||||
window.WebSocket = jest.fn();
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
|
||||
* Copyright (C) 2020 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.
|
||||
@ -12,7 +12,7 @@ import { ForisURLs } from "../utils/forisUrls";
|
||||
const PROTOCOL = window.location.protocol === "http:" ? "ws" : "wss";
|
||||
|
||||
const URL = process.env.LIGHTTPD
|
||||
? `${PROTOCOL}://${window.location.hostname}/foris-ws`
|
||||
? `${PROTOCOL}://${window.location.host}/foris-ws`
|
||||
: `${PROTOCOL}://${window.location.hostname}:${9081}`;
|
||||
|
||||
const WAITING_FOR_CONNECTION_TIMEOUT = 500;
|
||||
|
@ -14,6 +14,10 @@ module.exports = {
|
||||
name: "Foris JS",
|
||||
content: "docs/intro.md",
|
||||
},
|
||||
{
|
||||
name: "Development (Linking)",
|
||||
content: "docs/development.md",
|
||||
},
|
||||
{
|
||||
name: "Foris forms",
|
||||
components: [
|
||||
@ -24,6 +28,14 @@ module.exports = {
|
||||
exampleMode: "expand",
|
||||
usageMode: "expand",
|
||||
},
|
||||
{
|
||||
name: "Alert Context",
|
||||
components: [
|
||||
"src/alertContext/AlertContext.js",
|
||||
],
|
||||
exampleMode: "expand",
|
||||
usageMode: "expand",
|
||||
},
|
||||
{
|
||||
name: "Bootstrap components",
|
||||
description: "Set of bootstrap components.",
|
||||
@ -37,6 +49,7 @@ module.exports = {
|
||||
],
|
||||
require: [
|
||||
"babel-polyfill",
|
||||
path.join(__dirname, "src/testUtils/mockGlobals"),
|
||||
path.join(__dirname, "node_modules/bootstrap/dist/css/bootstrap.min.css"),
|
||||
path.join(__dirname, "node_modules/@fortawesome/fontawesome-free/css/all.min.css"),
|
||||
],
|
||||
|
Reference in New Issue
Block a user