mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-23 08:58:26 +00:00
Merge branch 'release/v0.7.4' into main
This commit is contained in:
commit
baa577bc44
316 changed files with 22922 additions and 29070 deletions
|
|
@ -1,2 +0,0 @@
|
|||
frontend/node_modules/**
|
||||
# **/*.js
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"semi": true,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 120,
|
||||
"singleQuote": true,
|
||||
"arrowParens": "always",
|
||||
"proseWrap": "preserve"
|
||||
}
|
||||
2
.version
2
.version
|
|
@ -1 +1 @@
|
|||
0.7.3
|
||||
0.7.4
|
||||
11
.vscode/extension.json
vendored
Normal file
11
.vscode/extension.json
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"CoenraadS.bracket-pair-colorizer",
|
||||
"mgmcdermott.vscode-language-babel",
|
||||
"formulahendry.auto-rename-tag",
|
||||
"xabikos.javascriptsnippets",
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
||||
14
.vscode/settings.json
vendored
Normal file
14
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"[javascript, typescript]": {
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
|
||||
},
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact"
|
||||
],
|
||||
"eslint.format.enable": true,
|
||||
"editor.formatOnSave": true,
|
||||
|
||||
}
|
||||
|
|
@ -32,8 +32,8 @@ Pull requests are the best way to propose changes to the codebase (we use [Git-F
|
|||
5. Make sure your code lints.
|
||||
6. Issue that pull request!
|
||||
|
||||
## Any contributions you make will be under the Apache-2.0 License
|
||||
In short, when you submit code changes, your submissions are understood to be under the same [Apache-2.0 License](https://www.apache.org/licenses/LICENSE-2.0) that covers the project.
|
||||
## Any contributions you make will be under the AGPL v3 License
|
||||
In short, when you submit code changes, your submissions are understood to be under the same [AGPL v3 License](https://www.gnu.org/licenses/agpl-3.0.en.html) that covers the project.
|
||||
|
||||
## Report bugs using Github's [issues](https://github.com/ToolJet/ToolJet/issues)
|
||||
We use GitHub issues to track public bugs. Report a bug by [opening a new issue](); it's that easy!
|
||||
|
|
@ -49,7 +49,7 @@ We use GitHub issues to track public bugs. Report a bug by [opening a new issue]
|
|||
- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)
|
||||
|
||||
## License
|
||||
By contributing, you agree that your contributions will be licensed under its Apache-2.0 License.
|
||||
By contributing, you agree that your contributions will be licensed under its AGPL v3 License.
|
||||
|
||||
## Questions?
|
||||
Contact us on slack [Slack](https://join.slack.com/t/tooljet/shared_invite/zt-r2neyfcw-KD1COL6t2kgVTlTtAV5rtg) or mail us at [hello@tooljet.io](hello@tooljet)
|
||||
Contact us on slack [Slack](https://join.slack.com/t/tooljet/shared_invite/zt-r2neyfcw-KD1COL6t2kgVTlTtAV5rtg) or mail us at [hello@tooljet.io](hello@tooljet)
|
||||
|
|
|
|||
143
LICENSE
143
LICENSE
|
|
@ -1,5 +1,5 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
|
|
@ -7,17 +7,15 @@ GNU GENERAL PUBLIC LICENSE
|
|||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
|
|
@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
|
|||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
|
@ -72,7 +60,7 @@ modification follow.
|
|||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
|
@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
|
|||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
|
|
@ -635,40 +633,29 @@ the "copyright" line and a pointer to where the full notice is found.
|
|||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
19
README.md
19
README.md
|
|
@ -6,14 +6,21 @@
|
|||
|
||||
ToolJet is an **open-source no-code framework** to build and deploy internal tools quickly without much effort from the engineering teams. You can connect to your data sources such as databases ( like PostgreSQL, MongoDB, Elasticsearch, etc ), API endpoints ( ToolJet supports importing OpenAPI spec & OAuth2 authorization) and external services ( like Stripe, Slack, Google Sheets, Airtable ) and use our pre-built UI widgets to build internal tools.
|
||||
|
||||

|
||||

|
||||
[](https://github.com/ToolJet/ToolJet/issues)
|
||||
[](https://github.com/ToolJet/ToolJet/stargazers)
|
||||

|
||||

|
||||

|
||||

|
||||
[](https://github.com/ToolJet/ToolJet)
|
||||
|
||||
|
||||
|
||||
<p align="center">
|
||||
<kbd>
|
||||
<img src="https://user-images.githubusercontent.com/7828962/130593222-d1268766-b776-4952-bd36-9caf9810d851.gif" />
|
||||
<img src="https://user-images.githubusercontent.com/7828962/134216201-b2c65c48-547a-4e79-946c-60b7be54b70c.png" />
|
||||
</kbd>
|
||||
</p>
|
||||
|
||||
|
|
@ -29,7 +36,6 @@ ToolJet is an **open-source no-code framework** to build and deploy internal too
|
|||
- Write JS code almost anywhere in the builder
|
||||
- Query editors for all supported data sources
|
||||
- Transform query results using JS code
|
||||
- Import endpoints from OpenAPI specs
|
||||
- All the credentials are securely encrypted using `aes-256-gcm`.
|
||||
- ToolJet acts only as a proxy and doesn't store any data.
|
||||
- Support for OAuth
|
||||
|
|
@ -44,8 +50,6 @@ You can deploy ToolJet on Heroku for free using the one-click-deployment button
|
|||
<a href="https://heroku.com/deploy?template=https://github.com/tooljet/tooljet/tree/main"><img src="https://www.herokucdn.com/deploy/button.svg" /></a>
|
||||
</P>
|
||||
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
[Building a Github contributor leaderboard using ToolJet](https://blog.tooljet.io/building-a-github-contributor-leaderboard-using-tooljet/)<br>
|
||||
|
|
@ -65,5 +69,10 @@ We use the git-flow branching model. The base branch is develop. If you are look
|
|||
Read our contributing guide (CONTRIBUTING.md) to learn about our development process, how to propose bugfixes and improvements, and how to build and test your changes to ToolJet. <br>
|
||||
[Contributing Guide](https://docs.tooljet.io/docs/contributing-guide/setup/docker)
|
||||
|
||||
## Contributors
|
||||
<a href="https://github.com/tooljet/tooljet/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=tooljet/tooljet" />
|
||||
</a>
|
||||
|
||||
## Licence
|
||||
ToolJet © 2021, ToolJet Inc - Released under the GNU General Public License v3.0.
|
||||
ToolJet © 2021, ToolJet Solutions Inc - Released under the GNU General Public License v3.0.
|
||||
|
|
|
|||
7
deploy/kubernetes/GKE/certificate.yaml
Normal file
7
deploy/kubernetes/GKE/certificate.yaml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
apiVersion: networking.gke.io/v1beta1
|
||||
kind: ManagedCertificate
|
||||
metadata:
|
||||
name: tj
|
||||
spec:
|
||||
domains:
|
||||
- tooljet.your-domain.com
|
||||
58
deploy/kubernetes/GKE/deployment.yaml
Normal file
58
deploy/kubernetes/GKE/deployment.yaml
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: tooljet-deployment
|
||||
spec:
|
||||
replicas: 2
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxUnavailable: 1
|
||||
maxSurge: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
component: tooljet
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: tooljet
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: docker-secret
|
||||
containers:
|
||||
- name: container
|
||||
image: tooljet/tooljet-ce:latest
|
||||
imagePullPolicy: Always
|
||||
args: ["npm", "run", "start:prod"]
|
||||
resources:
|
||||
limits:
|
||||
memory: "1000Mi"
|
||||
cpu: "500m"
|
||||
requests:
|
||||
memory: "1000Mi"
|
||||
cpu: "500m"
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
port: 3000
|
||||
path: /api/health
|
||||
successThreshold: 1
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
failureThreshold: 6
|
||||
env:
|
||||
- name: PG_HOST
|
||||
value: ""
|
||||
- name: PG_USER
|
||||
value: "postgres"
|
||||
- name: PG_PASS
|
||||
value: ""
|
||||
- name: PG_DB
|
||||
value: ""
|
||||
- name: LOCKBOX_MASTER_KEY
|
||||
value: ""
|
||||
- name: SECRET_KEY_BASE
|
||||
value: ""
|
||||
- name: TOOLJET_HOST
|
||||
value: "https://tooljet.your-domain.com"
|
||||
19
deploy/kubernetes/GKE/ingress.yaml
Normal file
19
deploy/kubernetes/GKE/ingress.yaml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: backend-ingress
|
||||
annotations:
|
||||
kubernetes.io/ingress.global-static-ip-name: "tj-static-ip"
|
||||
networking.gke.io/managed-certificates: "tj"
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: 'true'
|
||||
|
||||
spec:
|
||||
rules:
|
||||
- host: tooljet.your-domain.com
|
||||
http:
|
||||
paths:
|
||||
- path: /*
|
||||
backend:
|
||||
serviceName: tj-service
|
||||
servicePort: 8080
|
||||
|
||||
13
deploy/kubernetes/GKE/service.yaml
Normal file
13
deploy/kubernetes/GKE/service.yaml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: tj-service
|
||||
namespace: default
|
||||
spec:
|
||||
ports:
|
||||
- port: 8080
|
||||
protocol: TCP
|
||||
targetPort: 3000
|
||||
selector:
|
||||
component: tooljet
|
||||
type: NodePort
|
||||
|
|
@ -12,6 +12,7 @@ ENV PATH /app/node_modules/.bin:$PATH
|
|||
# Fix for heap limit allocation issue
|
||||
ENV NODE_OPTIONS="--max-old-space-size=2048"
|
||||
|
||||
|
||||
# install app dependencies
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm install
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ RUN apt update && apt install -y \
|
|||
RUN mkdir -p /app
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
COPY ./package.json ./package-lock.json .
|
||||
COPY ./package.json ./package-lock.json ./
|
||||
|
||||
# Building ToolJet client
|
||||
COPY ./frontend/package.json ./frontend/package-lock.json ./frontend/
|
||||
|
|
|
|||
|
|
@ -27,5 +27,5 @@ Click on `+` button of the query manager at the bottom panel of the editor and s
|
|||
Select the operation that you want to perform on Firestore and click 'Save' to save the query.
|
||||
|
||||
:::tip
|
||||
Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/tutorial/transformations)
|
||||
Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/docs/tutorial/transformations)
|
||||
:::
|
||||
|
|
@ -23,5 +23,5 @@ Click on `+` button of the query manager at the bottom panel of the editor and s
|
|||
Select the operation that you want to perform on Firestore and click 'Save' to save the query.
|
||||
|
||||
:::tip
|
||||
Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/tutorial/transformations)
|
||||
Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/docs/tutorial/transformations)
|
||||
:::
|
||||
|
|
@ -36,5 +36,5 @@ Click on `+` button of the query manager at the bottom panel of the editor and s
|
|||
Click on the 'run' button to run the query. NOTE: Query should be saved before running.
|
||||
|
||||
:::tip
|
||||
Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/tutorial/transformations)
|
||||
Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/docs/tutorial/transformations)
|
||||
:::
|
||||
|
|
@ -37,5 +37,5 @@ Click on the 'run' button to run the query. NOTE: Query should be saved before r
|
|||
|
||||
|
||||
:::tip
|
||||
Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/tutorial/transformations)
|
||||
Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/docs/tutorial/transformations)
|
||||
:::
|
||||
|
|
@ -33,5 +33,5 @@ Click on `+` button of the query manager at the bottom panel of the editor.
|
|||
Click on the 'run' button to run the query. NOTE: Query should be saved before running.
|
||||
|
||||
:::tip
|
||||
Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/tutorial/transformations)
|
||||
Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/docs/tutorial/transformations)
|
||||
:::
|
||||
|
|
@ -34,5 +34,5 @@ Click on '+' button of the query manager at the bottom panel of the editor and s
|
|||
Click on the 'run' button to run the query. NOTE: Query should be saved before running.
|
||||
|
||||
:::tip
|
||||
Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/tutorial/transformations)
|
||||
Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/docs/tutorial/transformations)
|
||||
:::
|
||||
|
|
@ -41,5 +41,5 @@ NOTE: Query should be saved before running.
|
|||
:::
|
||||
|
||||
:::tip
|
||||
Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/tutorial/transformations)
|
||||
Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/docs/tutorial/transformations)
|
||||
:::
|
||||
62
docs/docs/deployment/kubernetes-gke.md
Normal file
62
docs/docs/deployment/kubernetes-gke.md
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
---
|
||||
sidebar_position: 4
|
||||
sidebar_label: Kubernetes (GKE)
|
||||
---
|
||||
|
||||
# Deploying ToolJet on Kubernetes (GKE)
|
||||
|
||||
:::info
|
||||
You should setup a PostgreSQL database manually to be used by ToolJet. We recommend using Cloud SQL since this guide is for deploying using GKE.
|
||||
:::
|
||||
|
||||
Follow the steps below to deploy ToolJet on a GKE Kubernetes cluster.
|
||||
|
||||
1. Create an SSL certificate.
|
||||
```bash
|
||||
curl -LO https://raw.githubusercontent.com/ToolJet/ToolJet/main/deploy/kubernetes/GKE/certificate.yaml
|
||||
```
|
||||
|
||||
Change the domain name to the domain/subdomain that you wish to use for ToolJet installation.
|
||||
|
||||
2. Reserve a static IP address using `gcloud` cli
|
||||
```bash
|
||||
gcloud compute addresses create tj-static-ip --global
|
||||
```
|
||||
|
||||
3. Create k8s deployment
|
||||
```bash
|
||||
curl -LO https://raw.githubusercontent.com/ToolJet/ToolJet/main/deploy/kubernetes/GKE/deployment.yaml
|
||||
```
|
||||
|
||||
Make sure to edit the environment variables in the `deployment.yaml`. You can check out the available options [here](https://docs.tooljet.io/docs/deployment/env-vars).
|
||||
|
||||
4. Create k8s service
|
||||
```bash
|
||||
curl -LO https://raw.githubusercontent.com/ToolJet/ToolJet/main/deploy/kubernetes/GKE/service.yaml
|
||||
```
|
||||
|
||||
5. Create k8s ingress
|
||||
```bash
|
||||
curl -LO https://raw.githubusercontent.com/ToolJet/ToolJet/main/deploy/kubernetes/GKE/ingress.yaml
|
||||
```
|
||||
|
||||
Change the domain name to the domain/subdomain that you wish to use for ToolJet installation.
|
||||
|
||||
6. Apply YAML configs
|
||||
|
||||
```bash
|
||||
kubectl apply -f certificate.yaml, deployment.yaml, service.yaml, ingress.yaml
|
||||
```
|
||||
|
||||
:::info
|
||||
It might take a few minutes to provision the managed certificates. [Managed certificates documentation](https://cloud.google.com/kubernetes-engine/docs/how-to/managed-certs).
|
||||
:::
|
||||
|
||||
You will be able to access your ToolJet installation once the pods, service and the ingress is running.
|
||||
|
||||
If you want to seed the database with a sample user, please SSH into a pod and run:
|
||||
`npm run db:seed --prefix server`.
|
||||
This seeds the database with a default user with the following credentials:
|
||||
|
||||
email: `dev@tooljet.io`
|
||||
password: `password`
|
||||
|
|
@ -40,5 +40,5 @@ Application load balancing on Amazon EKS: https://docs.aws.amazon.com/eks/latest
|
|||
GKE Ingress for HTTP(S) Load Balancing: https://cloud.google.com/kubernetes-engine/docs/concepts/ingress
|
||||
|
||||
:::tip
|
||||
If you want to serve ToolJet client from services such as Firebase or Netlify, please read the client deployment documentation [here](/docs/setup/client).
|
||||
If you want to serve ToolJet client from services such as Firebase or Netlify, please read the client deployment documentation [here](/docs/deployment/client).
|
||||
:::
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ These resources will help you to quickly build and deploy apps using ToolJet:
|
|||
|
||||
The references for datasources and widgets:
|
||||
|
||||
- [Datasource Reference](/docs/data-sources/redis)
|
||||
- [Widget Reference](/docs/widgets/table)
|
||||
- **[Datasource Reference](/docs/data-sources/redis)**
|
||||
- **[Widget Reference](/docs/widgets/table)**
|
||||
|
||||
## Help and Support
|
||||
We have extensively documented the features of ToolJet, but in case you are stuck, please feel to mail us: hello@tooljet.io.
|
||||
|
|
|
|||
5
docs/docs/sso/_category_.json
Normal file
5
docs/docs/sso/_category_.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"label": "Single Sign-on",
|
||||
"position": 6,
|
||||
"collapsed": true
|
||||
}
|
||||
51
docs/docs/sso/google.md
Normal file
51
docs/docs/sso/google.md
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
---
|
||||
sidebar_position: 6
|
||||
sidebar_label: Google
|
||||
---
|
||||
|
||||
# Google Single Sign-on
|
||||
|
||||
:::info
|
||||
This feature is available only on the enterprise edition for ToolJet
|
||||
:::
|
||||
|
||||
Goto [Google cloud console](https://console.cloud.google.com/) and create a project.
|
||||
|
||||
<img class="screenshot-full" src="/img/sso/google/create-project.png" alt="ToolJet - Google create project" height="420"/>
|
||||
<br /><br /><br />
|
||||
|
||||
Goto [Google cloud console credentials page](https://console.cloud.google.com/apis/credentials), and create an OAuth client ID
|
||||
|
||||
<img class="screenshot-full" src="/img/sso/google/create-oauth.png" alt="ToolJet - Google create client id" height="420"/>
|
||||
<br /><br /><br />
|
||||
|
||||
You'll be asked to select user type in consent screen. To allow only users within your organization, select 'Internal', otherwise,
|
||||
select 'External'.
|
||||
|
||||
<img class="screenshot-full" src="/img/sso/google/oauth-type.png" alt="ToolJet - OAuth user type" height="420"/>
|
||||
<br /><br /><br />
|
||||
|
||||
You'll be led to an app registration page where you can set OAuth scopes. Select 'Add or remove scopes' and add the scopes
|
||||
userinfo.email and userinfo.profile as shown in the image. This will allow ToolJet to store the email and name of the
|
||||
user who is signing in
|
||||
|
||||
<img class="screenshot-full" src="/img/sso/google/scope.png" alt="ToolJet - OAuth scope" height="420"/>
|
||||
<br /><br /><br />
|
||||
|
||||
Set the domain on which ToolJet is hosted as an authorized domain
|
||||
|
||||
<img class="screenshot-full" src="/img/sso/google/authorized-urls.png" alt="ToolJet - Google authorized domain" height="420"/>
|
||||
<br /><br /><br />
|
||||
|
||||
Lastly, supply the environment variable `SSO_GOOGLE_OAUTH2_CLIENT_ID` to your deployment. This value will be available from your [Google cloud console credentials page](https://console.cloud.google.com/apis/credentials)
|
||||
|
||||
:::info
|
||||
|
||||
### Restrict to your domain
|
||||
Set the environment variable `RESTRICTED_DOMAIN` to ensure that ToolJet verifies the domain of the user who signs in via SSO, on the server side.
|
||||
If you're setting this environment variable, please make sure that the value does not contain any protocols, sub domains or slashes. It should
|
||||
simply be `yourdomain.com`.
|
||||
:::
|
||||
|
||||
<br />
|
||||
The Google sign-in button will now be available in your ToolJet login screen.
|
||||
|
|
@ -4,7 +4,7 @@ sidebar_position: 8
|
|||
|
||||
# Mobile layout
|
||||
|
||||
Mobile layout is activated when the width of window is less than 600px.
|
||||
Mobile layout is activated when the width of window is less than 600px.
|
||||
|
||||
:::tip
|
||||
Widgets can be shown on desktop, mobile, or both.
|
||||
|
|
@ -12,11 +12,11 @@ Widgets can be shown on desktop, mobile, or both.
|
|||
|
||||
<img class="screenshot-full" src="/img/tutorial/mobile-layout/mobile-layout.gif" alt="ToolJet - widgets list" height="420"/>
|
||||
|
||||
## Adding existing widget to mobile layout
|
||||
Click on the widget to open inspector. Scroll down to the `layout` section and enable mobile layout. The width of the widget will be adjusted to fit the mobile layout.
|
||||
## Adding existing widget to mobile layout
|
||||
Click on the widget to open inspector. Scroll down to the `layout` section and enable mobile layout. The width of the widget will be adjusted to fit the mobile layout.
|
||||
|
||||
## Adding a new widget to mobile layout
|
||||
Switch the layout to mobile by clicking the button on the top navigation bar. Drag and drop a widget to the canvas. This widget will not be shown on desktop layout unless enabled from the widget inspector via the "Show on desktop?" button manually.
|
||||
Switch the layout to mobile by clicking the button on the top navigation bar. Drag and drop a widget to the canvas. This widget will not be shown on desktop layout unless enabled from the widget inspector via the "Show on desktop" button manually.
|
||||
|
||||
:::tip
|
||||
Width of the widgets will be automatically adjusted to fit the screen while viewing the application in app viewer.
|
||||
|
|
|
|||
19
docs/docs/widgets/number-input.md
Normal file
19
docs/docs/widgets/number-input.md
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
sidebar_position: 16
|
||||
---
|
||||
|
||||
# Number Input
|
||||
|
||||
Number Input widget lets users enter and change numbers.
|
||||
|
||||
:::tip
|
||||
Numbers can be changed by using the arrow keys.
|
||||
:::
|
||||
|
||||
<img class="screenshot-full" src="/img/widgets/number-input/number-input.gif" alt="ToolJet - Widget Reference - Number Input" height="420"/>
|
||||
|
||||
#### Properties
|
||||
|
||||
| properties | description |
|
||||
| ----------- | ----------- |
|
||||
| Placeholder | It specifies a hint that describes the expected value.|
|
||||
|
|
@ -49,6 +49,14 @@ Client-side pagination is enabled by default. The number of records per page is
|
|||
|
||||
Server-side pagination can be used to run a query whenever the page is changed. Go to events section of the inspector and change the action for `on page changed` event. Number of records per page needs to be handled in your query. If server-side pagination is enabled, `pageIndex` property will be exposed on the table object, this property will have the current page index. `pageIndex` can be used to query the next set of results when page is changed.
|
||||
|
||||
## Bulk selection
|
||||
|
||||
To let the user select one or more rows from the current page of a table, enable 'Bulk selection' from the inspector. The values of selected rows will be exposed as `selectedRows`.
|
||||
|
||||
## Highlight selected row
|
||||
|
||||
Activate this option on the inspector to have the last selected(clicked on) row to be highlighted.
|
||||
|
||||
## Search
|
||||
Client-side search is enabled by default and server-side search can be enabled from the events section of the inspector. Whenever the search text is changed, the `searchText` property of the table component is updated. If server-side search is enabled, `on search` event is fired after the content of `searchText` property is changed. `searchText` can be used to run a specific query to search for the records in your datasource.
|
||||
|
||||
|
|
|
|||
BIN
docs/static/img/sso/google/authorized-urls.png
vendored
Normal file
BIN
docs/static/img/sso/google/authorized-urls.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
BIN
docs/static/img/sso/google/create-oauth.png
vendored
Normal file
BIN
docs/static/img/sso/google/create-oauth.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
BIN
docs/static/img/sso/google/create-project.png
vendored
Normal file
BIN
docs/static/img/sso/google/create-project.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
BIN
docs/static/img/sso/google/oauth-type.png
vendored
Normal file
BIN
docs/static/img/sso/google/oauth-type.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
BIN
docs/static/img/sso/google/scope.png
vendored
Normal file
BIN
docs/static/img/sso/google/scope.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 187 KiB |
BIN
docs/static/img/widgets/number-input/number-input.gif
vendored
Normal file
BIN
docs/static/img/widgets/number-input/number-input.gif
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 719 KiB |
|
|
@ -1,37 +0,0 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true
|
||||
},
|
||||
|
||||
parser: 'babel-eslint',
|
||||
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
jsx: true
|
||||
},
|
||||
ecmaVersion: 12,
|
||||
sourceType: 'module'
|
||||
},
|
||||
extends: [
|
||||
'plugin:react/recommended',
|
||||
'prettier',
|
||||
'airbnb-base/legacy'
|
||||
],
|
||||
plugins: ['html', 'react', 'prettier', 'babel'],
|
||||
rules: {
|
||||
"react/prop-types": 0,
|
||||
"no-underscore-dangle": ["error", { "allow": ["_self"] }],
|
||||
"max-len": 0,
|
||||
"no-bitwise": 0,
|
||||
"no-use-before-define": ["error", { "variables": false, "functions": false }],
|
||||
"no-nested-ternary": 0,
|
||||
"no-loop-func": 0,
|
||||
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect"
|
||||
}
|
||||
}
|
||||
};
|
||||
56
frontend/.eslintrc.json
Normal file
56
frontend/.eslintrc.json
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"amd": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:import/errors",
|
||||
"plugin:import/warnings",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:cypress/recommended"
|
||||
],
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["react", "prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": [
|
||||
"error",
|
||||
{
|
||||
"semi": true,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 120,
|
||||
"singleQuote": true,
|
||||
"arrowParens": "always",
|
||||
"proseWrap": "preserve"
|
||||
}
|
||||
],
|
||||
"react/prop-types": 0,
|
||||
"react/display-name": "off",
|
||||
"no-unused-vars": [2, { "args": "after-used", "argsIgnorePattern": "reject" }],
|
||||
"react/no-deprecated": 0,
|
||||
"no-prototype-builtins": 0
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
},
|
||||
"import/resolver": "webpack"
|
||||
},
|
||||
"globals": {
|
||||
"fetch": true,
|
||||
"process": true,
|
||||
"module": true,
|
||||
"__dirname": true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,16 +1,12 @@
|
|||
const webpackPreprocessor = require('@cypress/webpack-preprocessor')
|
||||
|
||||
module.exports = (on, config) => {
|
||||
if (config.testingType === 'component') {
|
||||
const { startDevServer } = require('@cypress/webpack-dev-server')
|
||||
const { startDevServer } = require('@cypress/webpack-dev-server');
|
||||
|
||||
// Your project's Webpack configuration
|
||||
const webpackConfig = require('../../webpack.config.js')
|
||||
const webpackConfig = require('../../webpack.config.js');
|
||||
|
||||
on('dev-server:start', (options) =>
|
||||
startDevServer({ options, webpackConfig })
|
||||
)
|
||||
on('dev-server:start', (options) => startDevServer({ options, webpackConfig }));
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
return config;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
import './commands';
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
|
|
|
|||
5390
frontend/package-lock.json
generated
5390
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -3,7 +3,6 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"babel-plugin-import": "^1.13.3",
|
||||
"@babel/core": "^7.4.3",
|
||||
"@babel/plugin-proposal-class-properties": "^7.10.4",
|
||||
"@babel/preset-env": "^7.4.3",
|
||||
|
|
@ -18,13 +17,15 @@
|
|||
"array-move": "^3.0.1",
|
||||
"babel-loader": "^8.0.5",
|
||||
"babel-plugin-console-source": "^2.0.5",
|
||||
"babel-plugin-import": "^1.13.3",
|
||||
"bootstrap": "^4.6.0",
|
||||
"classnames": "^2.3.1",
|
||||
"dompurify": "^2.2.7",
|
||||
"draft-js": "^0.11.7",
|
||||
"html-webpack-plugin": "^5.3.2",
|
||||
"draft-js-export-html": "^1.4.1",
|
||||
"fuse.js": "^6.4.6",
|
||||
"history": "^4.9.0",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"immutability-helper": "^3.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.1",
|
||||
|
|
@ -32,7 +33,6 @@
|
|||
"papaparse": "^5.3.0",
|
||||
"plotly.js-basic-dist-min": "^1.58.4",
|
||||
"query-string": "^6.13.6",
|
||||
"re-resizable": "^6.9.0",
|
||||
"react": "^16.14.0",
|
||||
"react-bootstrap": "^1.5.2",
|
||||
"react-color": "^2.19.3",
|
||||
|
|
@ -48,7 +48,6 @@
|
|||
"react-loading-skeleton": "^2.2.0",
|
||||
"react-plotly.js": "^2.5.1",
|
||||
"react-qr-reader": "^2.2.1",
|
||||
"react-resizable": "^1.11.1",
|
||||
"react-rnd": "^10.3.0",
|
||||
"react-router-dom": "^5.0.0",
|
||||
"react-scripts": "3.4.3",
|
||||
|
|
@ -62,8 +61,9 @@
|
|||
"semver": "^5.7.1",
|
||||
"tinycolor2": "^1.4.2",
|
||||
"uuid": "8.3.2",
|
||||
"webpack-cli": "^3.3.0",
|
||||
"yup": "^0.27.0"
|
||||
"yup": "^0.27.0",
|
||||
"webpack": "^5.55.1",
|
||||
"webpack-cli": "^4.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cypress/react": "^5.9.0",
|
||||
|
|
@ -71,13 +71,23 @@
|
|||
"@cypress/webpack-preprocessor": "^5.9.0",
|
||||
"@svgr/webpack": "^5.5.0",
|
||||
"cypress": "^7.4.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-import-resolver-webpack": "^0.13.1",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"eslint-plugin-import": "^2.24.2",
|
||||
"eslint-plugin-prettier": "^3.4.1",
|
||||
"eslint-plugin-react": "^7.25.2",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"path": "^0.12.7",
|
||||
"webpack": "^4.29.6",
|
||||
"prettier": "^2.3.2",
|
||||
"webpack-dev-server": "^3.11.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "webpack-dev-server --open --port 8082 --host 0.0.0.0",
|
||||
"build": "webpack -p && cp -a ./assets/. ./build/assets/",
|
||||
"start": "webpack serve --port 8082 --host 0.0.0.0",
|
||||
"build": "webpack --mode production && cp -a ./assets/. ./build/assets/",
|
||||
"lint": "eslint . '**/*.{js,jsx}'",
|
||||
"format": "eslint . --fix '**/*.{js,jsx}'",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
|
|
@ -88,4 +98,4 @@
|
|||
"production": [],
|
||||
"development": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ import 'react-toastify/dist/ReactToastify.css';
|
|||
import { ManageOrgUsers } from '@/ManageOrgUsers';
|
||||
import { SettingsPage } from '../SettingsPage/SettingsPage';
|
||||
import { OnboardingModal } from '@/Onboarding/OnboardingModal';
|
||||
import {ForgotPassword} from '@/ForgotPassword'
|
||||
import { ForgotPassword } from '@/ForgotPassword';
|
||||
import { ResetPassword } from '@/ResetPassword';
|
||||
import { lt } from 'semver';
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ class App extends React.Component {
|
|||
currentUser: null,
|
||||
fetchedMetadata: false,
|
||||
onboarded: true,
|
||||
darkMode: localStorage.getItem('darkMode') === 'true'
|
||||
darkMode: localStorage.getItem('darkMode') === 'true',
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -40,56 +40,107 @@ class App extends React.Component {
|
|||
logout = () => {
|
||||
authenticationService.logout();
|
||||
history.push('/login');
|
||||
}
|
||||
};
|
||||
|
||||
switchDarkMode = (newMode) => {
|
||||
this.setState({ darkMode: newMode });
|
||||
localStorage.setItem('darkMode', newMode);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { currentUser, fetchedMetadata, updateAvailable, onboarded, darkMode } = this.state;
|
||||
|
||||
if(currentUser && fetchedMetadata === false) {
|
||||
if (currentUser && fetchedMetadata === false) {
|
||||
tooljetService.fetchMetaData().then((data) => {
|
||||
this.setState({ fetchedMetadata: true, onboarded: data.onboarded });
|
||||
|
||||
if(lt(data.installed_version, data.latest_version) && data.version_ignored === false) {
|
||||
if (lt(data.installed_version, data.latest_version) && data.version_ignored === false) {
|
||||
this.setState({ updateAvailable: true });
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Router history={history}>
|
||||
<div className={`main-wrapper ${darkMode ? 'theme-dark' : ''}`}>
|
||||
{updateAvailable && <div className="alert alert-info alert-dismissible" role="alert">
|
||||
<h3 className="mb-1">Update available</h3>
|
||||
<p>A new version of ToolJet has been released.</p>
|
||||
<div className="btn-list">
|
||||
<a href="https://docs.tooljet.io/docs/setup/updating" target="_blank" className="btn btn-info">Read release notes & update</a>
|
||||
<a onClick={() => { tooljetService.skipVersion(); this.setState({ updateAvailable: false }); }} className="btn">Skip this version</a>
|
||||
{updateAvailable && (
|
||||
<div className="alert alert-info alert-dismissible" role="alert">
|
||||
<h3 className="mb-1">Update available</h3>
|
||||
<p>A new version of ToolJet has been released.</p>
|
||||
<div className="btn-list">
|
||||
<a
|
||||
href="https://docs.tooljet.io/docs/setup/updating"
|
||||
target="_blank"
|
||||
className="btn btn-info"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Read release notes & update
|
||||
</a>
|
||||
<a
|
||||
onClick={() => {
|
||||
tooljetService.skipVersion();
|
||||
this.setState({ updateAvailable: false });
|
||||
}}
|
||||
className="btn"
|
||||
>
|
||||
Skip this version
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>}
|
||||
)}
|
||||
|
||||
{!onboarded &&
|
||||
<OnboardingModal />
|
||||
}
|
||||
{!onboarded && <OnboardingModal />}
|
||||
|
||||
<ToastContainer />
|
||||
|
||||
<PrivateRoute exact path="/" component={HomePage} switchDarkMode={this.switchDarkMode} darkMode={darkMode}/>
|
||||
<Route path="/login" component={LoginPage}/>
|
||||
<PrivateRoute exact path="/" component={HomePage} switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
|
||||
<Route path="/login" component={LoginPage} />
|
||||
<Route path="/signup" component={SignupPage} />
|
||||
<Route path = "/forgot-password" component ={ForgotPassword} />
|
||||
<Route path = "/reset-password" component ={ResetPassword} />
|
||||
<Route path="/forgot-password" component={ForgotPassword} />
|
||||
<Route path="/reset-password" component={ResetPassword} />
|
||||
<Route path="/invitations/:token" component={InvitationPage} />
|
||||
<PrivateRoute exact path="/apps/:id" component={Editor} switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
|
||||
<PrivateRoute exact path="/applications/:id/versions/:versionId" component={Viewer} switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
|
||||
<PrivateRoute exact path="/applications/:slug" component={Viewer} switchDarkMode={this.switchDarkMode} darkMode={darkMode}/>
|
||||
<PrivateRoute exact path="/oauth2/authorize" component={Authorize} switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
|
||||
<PrivateRoute exact path="/users" component={ManageOrgUsers} switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
|
||||
<PrivateRoute exact path="/settings" component={SettingsPage} switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
|
||||
<PrivateRoute
|
||||
exact
|
||||
path="/apps/:id"
|
||||
component={Editor}
|
||||
switchDarkMode={this.switchDarkMode}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
<PrivateRoute
|
||||
exact
|
||||
path="/applications/:id/versions/:versionId"
|
||||
component={Viewer}
|
||||
switchDarkMode={this.switchDarkMode}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
<PrivateRoute
|
||||
exact
|
||||
path="/applications/:slug"
|
||||
component={Viewer}
|
||||
switchDarkMode={this.switchDarkMode}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
<PrivateRoute
|
||||
exact
|
||||
path="/oauth2/authorize"
|
||||
component={Authorize}
|
||||
switchDarkMode={this.switchDarkMode}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
<PrivateRoute
|
||||
exact
|
||||
path="/users"
|
||||
component={ManageOrgUsers}
|
||||
switchDarkMode={this.switchDarkMode}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
<PrivateRoute
|
||||
exact
|
||||
path="/settings"
|
||||
component={SettingsPage}
|
||||
switchDarkMode={this.switchDarkMode}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
</div>
|
||||
</Router>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,51 +2,39 @@ export const ActionTypes = [
|
|||
{
|
||||
name: 'Show Alert',
|
||||
id: 'show-alert',
|
||||
options: [
|
||||
{ name: 'message', type: 'text', default: 'Message !' }
|
||||
]
|
||||
options: [{ name: 'message', type: 'text', default: 'Message !' }],
|
||||
},
|
||||
{
|
||||
name: 'Run Query',
|
||||
id: 'run-query',
|
||||
options: [
|
||||
{ queryId: '' }
|
||||
]
|
||||
options: [{ queryId: '' }],
|
||||
},
|
||||
{
|
||||
name: 'Open Webpage',
|
||||
id: 'open-webpage',
|
||||
options: [
|
||||
{ name: 'url', type: 'text', default: 'https://example.com' }
|
||||
]
|
||||
options: [{ name: 'url', type: 'text', default: 'https://example.com' }],
|
||||
},
|
||||
{
|
||||
name: 'Go to app',
|
||||
id: 'go-to-app',
|
||||
options: [
|
||||
{ name: 'app', type: 'text', default: '' },
|
||||
{ name: 'queryParams', type: 'code', default: '[]' }
|
||||
]
|
||||
{ name: 'queryParams', type: 'code', default: '[]' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Show Modal',
|
||||
id: 'show-modal',
|
||||
options: [
|
||||
{ name: 'modal', type: 'text', default: '' }
|
||||
]
|
||||
options: [{ name: 'modal', type: 'text', default: '' }],
|
||||
},
|
||||
{
|
||||
name: 'Close Modal',
|
||||
id: 'close-modal',
|
||||
options: [
|
||||
{ name: 'modal', type: 'text', default: '' }
|
||||
]
|
||||
options: [{ name: 'modal', type: 'text', default: '' }],
|
||||
},
|
||||
{
|
||||
name: 'Copy to clipboard',
|
||||
id: 'copy-to-clipboard',
|
||||
options: [
|
||||
{ name: 'copy-to-clipboard', type: 'text', default: '' }
|
||||
]
|
||||
}
|
||||
options: [{ name: 'copy-to-clipboard', type: 'text', default: '' }],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ import { Modal } from './Components/Modal';
|
|||
import { Chart } from './Components/Chart';
|
||||
import { Map } from './Components/Map/Map';
|
||||
import { QrScanner } from './Components/QrScanner/QrScanner';
|
||||
import { ToggleSwitch } from './Components/Toggle'
|
||||
import { RadioButton } from './Components/RadioButton'
|
||||
import { StarRating } from './Components/StarRating'
|
||||
import { ToggleSwitch } from './Components/Toggle';
|
||||
import { RadioButton } from './Components/RadioButton';
|
||||
import { StarRating } from './Components/StarRating';
|
||||
import { renderTooltip } from '../_helpers/appUtils';
|
||||
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
|
||||
import '@/_styles/custom.scss';
|
||||
|
|
@ -45,12 +45,11 @@ const AllComponents = {
|
|||
QrScanner,
|
||||
ToggleSwitch,
|
||||
RadioButton,
|
||||
StarRating
|
||||
StarRating,
|
||||
};
|
||||
|
||||
export const Box = function Box({
|
||||
id,
|
||||
mode,
|
||||
width,
|
||||
height,
|
||||
yellow,
|
||||
|
|
@ -66,12 +65,13 @@ export const Box = function Box({
|
|||
changeCanDrag,
|
||||
containerProps,
|
||||
darkMode,
|
||||
removeComponent
|
||||
removeComponent,
|
||||
}) {
|
||||
const backgroundColor = yellow ? 'yellow' : '';
|
||||
|
||||
let styles = {
|
||||
height: '100%',
|
||||
padding: '1px',
|
||||
};
|
||||
|
||||
if (inCanvas) {
|
||||
|
|
@ -86,49 +86,49 @@ export const Box = function Box({
|
|||
<OverlayTrigger
|
||||
placement="top"
|
||||
delay={{ show: 500, hide: 0 }}
|
||||
trigger={!inCanvas? ['hover', 'focus']: null}
|
||||
overlay={(props) => renderTooltip({props, text: `${component.description}`})}
|
||||
trigger={!inCanvas ? ['hover', 'focus'] : null}
|
||||
overlay={(props) => renderTooltip({ props, text: `${component.description}` })}
|
||||
>
|
||||
<div style={{ ...styles, backgroundColor }} role={preview ? 'BoxPreview' : 'Box'}>
|
||||
{inCanvas ? (
|
||||
<ComponentToRender
|
||||
onComponentClick={onComponentClick}
|
||||
onComponentOptionChanged={onComponentOptionChanged}
|
||||
currentState={currentState}
|
||||
onEvent={onEvent}
|
||||
id={id}
|
||||
paramUpdated={paramUpdated}
|
||||
width={width}
|
||||
changeCanDrag={changeCanDrag}
|
||||
onComponentOptionsChanged={onComponentOptionsChanged}
|
||||
height={height}
|
||||
component={component}
|
||||
containerProps={containerProps}
|
||||
darkMode={darkMode}
|
||||
removeComponent={removeComponent}
|
||||
></ComponentToRender>
|
||||
) : (
|
||||
<div className="m-1" style={{ height: '100%' }}>
|
||||
<div
|
||||
className="component-image-holder p-2 d-flex flex-column justify-content-center"
|
||||
style={{ height: '100%' }}
|
||||
>
|
||||
<center>
|
||||
<div
|
||||
style={{
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
backgroundSize: 'contain',
|
||||
backgroundImage: `url(/assets/images/icons/widgets/${component.name.toLowerCase()}.svg)`,
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}}
|
||||
></div>
|
||||
</center>
|
||||
<span className="component-title">{component.displayName}</span>
|
||||
<div style={{ ...styles, backgroundColor }} role={preview ? 'BoxPreview' : 'Box'}>
|
||||
{inCanvas ? (
|
||||
<ComponentToRender
|
||||
onComponentClick={onComponentClick}
|
||||
onComponentOptionChanged={onComponentOptionChanged}
|
||||
currentState={currentState}
|
||||
onEvent={onEvent}
|
||||
id={id}
|
||||
paramUpdated={paramUpdated}
|
||||
width={width}
|
||||
changeCanDrag={changeCanDrag}
|
||||
onComponentOptionsChanged={onComponentOptionsChanged}
|
||||
height={height}
|
||||
component={component}
|
||||
containerProps={containerProps}
|
||||
darkMode={darkMode}
|
||||
removeComponent={removeComponent}
|
||||
></ComponentToRender>
|
||||
) : (
|
||||
<div className="m-1" style={{ height: '100%' }}>
|
||||
<div
|
||||
className="component-image-holder p-2 d-flex flex-column justify-content-center"
|
||||
style={{ height: '100%' }}
|
||||
>
|
||||
<center>
|
||||
<div
|
||||
style={{
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
backgroundSize: 'contain',
|
||||
backgroundImage: `url(/assets/images/icons/widgets/${component.name.toLowerCase()}.svg)`,
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}}
|
||||
></div>
|
||||
</center>
|
||||
<span className="component-title">{component.displayName}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export const BoxDragPreview = memo(function BoxDragPreview({ item, currentLayout
|
|||
[tickTock]
|
||||
);
|
||||
|
||||
const layouts = item.layouts;
|
||||
const layouts = item.layouts;
|
||||
let { width, height } = layouts ? item.layouts[currentLayout] : {};
|
||||
|
||||
if (item.id === undefined) {
|
||||
|
|
@ -20,10 +20,18 @@ export const BoxDragPreview = memo(function BoxDragPreview({ item, currentLayout
|
|||
}
|
||||
|
||||
return (
|
||||
<div style={{ height, width, border: 'solid 1px rgb(70, 165, 253)' }}>
|
||||
<div style={{
|
||||
background: '#438fd7', opacity: '0.7', height: '100%', width: '100%'
|
||||
}}></div>
|
||||
<div
|
||||
className="resizer-active draggable-box"
|
||||
style={{ height, width, border: 'solid 1px rgb(70, 165, 253)', padding: '2px' }}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
background: '#438fd7',
|
||||
opacity: '0.7',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,13 +2,11 @@ import React, { useState } from 'react';
|
|||
import CodeMirror from '@uiw/react-codemirror';
|
||||
import 'codemirror/theme/duotone-light.css';
|
||||
import { componentTypes } from '../Components/components';
|
||||
import { DataSourceTypes } from '../DataSourceManager/DataSourceTypes';
|
||||
import { DataSourceTypes } from '../DataSourceManager/SourceComponents';
|
||||
import { debounce } from 'lodash';
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
export function CodeBuilder({
|
||||
initialValue, onChange, components, dataQueries
|
||||
}) {
|
||||
export function CodeBuilder({ initialValue, onChange, components, dataQueries }) {
|
||||
const [showDropdown, setShowDropdown] = useState(false);
|
||||
const [cursorPosition, setCursorPosition] = useState(0);
|
||||
const [currentValue, setCurrentValue] = useState(initialValue);
|
||||
|
|
@ -130,7 +128,7 @@ export function CodeBuilder({
|
|||
mode: 'javascript',
|
||||
lineWrapping: true,
|
||||
scrollbarStyle: null,
|
||||
lineNumbers: false
|
||||
lineNumbers: false,
|
||||
}}
|
||||
/>
|
||||
{showDropdown && (
|
||||
|
|
|
|||
|
|
@ -57,10 +57,12 @@ export function CodeHinter({
|
|||
});
|
||||
useEffect(() => {
|
||||
setRealState(currentState);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentState.components]);
|
||||
|
||||
let suggestions = useMemo(() => {
|
||||
return getSuggestionKeys(realState);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [realState.components, realState.queries]);
|
||||
|
||||
function valueChanged(editor, onChange, suggestions, ignoreBraces) {
|
||||
|
|
|
|||
|
|
@ -1,33 +1,27 @@
|
|||
import * as React from 'react'
|
||||
import { mount } from '@cypress/react'
|
||||
import { CodeHinter } from './CodeHinter'
|
||||
import * as React from 'react';
|
||||
import { mount } from '@cypress/react';
|
||||
import { CodeHinter } from './CodeHinter';
|
||||
|
||||
it('Codehinter', () => {
|
||||
mount(<CodeHinter
|
||||
currentState={{
|
||||
queries: {
|
||||
postgres: {
|
||||
data: []
|
||||
}
|
||||
},
|
||||
components: {
|
||||
mount(
|
||||
<CodeHinter
|
||||
currentState={{
|
||||
queries: {
|
||||
postgres: {
|
||||
data: [],
|
||||
},
|
||||
},
|
||||
components: {},
|
||||
globals: {},
|
||||
}}
|
||||
initialValue={''}
|
||||
theme="duotone-light"
|
||||
mode="javascript"
|
||||
lineNumbers={true}
|
||||
className="query-hinter"
|
||||
onChange={(value) => console.log(value)}
|
||||
/>
|
||||
);
|
||||
|
||||
},
|
||||
globals: {
|
||||
|
||||
}
|
||||
}}
|
||||
initialValue={''}
|
||||
theme="duotone-light"
|
||||
mode="javascript"
|
||||
lineNumbers={true}
|
||||
className="query-hinter"
|
||||
onChange={(value) => {}}
|
||||
/>)
|
||||
|
||||
cy.get('.code-hinter')
|
||||
.click()
|
||||
.type('{{')
|
||||
.contains('{{}}') // autocomplete for dynamic variables
|
||||
})
|
||||
|
||||
cy.get('.code-hinter').click().type('{{').contains('{{}}'); // autocomplete for dynamic variables
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,16 +6,15 @@ export function getSuggestionKeys(currentState) {
|
|||
_.keys(currentState).forEach((key) => {
|
||||
_.keys(currentState[key]).forEach((key2) => {
|
||||
_.keys(currentState[key][key2]).forEach((key3) => {
|
||||
suggestions.push(`${key}.${key2}.${key3}`)
|
||||
})
|
||||
})
|
||||
suggestions.push(`${key}.${key2}.${key3}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
export function generateHints(word, suggestions) {
|
||||
|
||||
if(word === '') {
|
||||
if (word === '') {
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
|
|
@ -38,25 +37,25 @@ export function computeCurrentWord(editor, _cursorPosition, ignoreBraces = false
|
|||
|
||||
export function makeOverlay(style) {
|
||||
return {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
token: function (stream, state) {
|
||||
var ch;
|
||||
if (stream.match("{{")) {
|
||||
if (stream.match('{{')) {
|
||||
while ((ch = stream.next()) != null)
|
||||
if (ch == "}" && stream.next() == "}") {
|
||||
stream.eat("}");
|
||||
if (ch == '}' && stream.next() == '}') {
|
||||
stream.eat('}');
|
||||
return style;
|
||||
}
|
||||
}
|
||||
while (stream.next() != null && !stream.match("{{", false)) { }
|
||||
// eslint-disable-next-line no-empty
|
||||
while (stream.next() != null && !stream.match('{{', false)) {}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function onBeforeChange(editor, change, ignoreBraces = false) {
|
||||
|
||||
if(!ignoreBraces) {
|
||||
|
||||
if (!ignoreBraces) {
|
||||
const cursor = editor.getCursor();
|
||||
const line = cursor.line;
|
||||
const ch = cursor.ch;
|
||||
|
|
@ -64,33 +63,30 @@ export function onBeforeChange(editor, change, ignoreBraces = false) {
|
|||
const isLastCharacterBrace = value.slice(ch - 1, value.length) === '{';
|
||||
|
||||
if (isLastCharacterBrace && change.origin === '+input' && change.text[0] === '{') {
|
||||
change.text[0] = '{}}'
|
||||
change.text[0] = '{}}';
|
||||
// editor.setCursor({ line: 0, ch: ch })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return change;
|
||||
}
|
||||
|
||||
export function canShowHint(editor, ignoreBraces = false) {
|
||||
|
||||
if(!editor.hasFocus()) return false;
|
||||
if (!editor.hasFocus()) return false;
|
||||
|
||||
const cursor = editor.getCursor();
|
||||
const line = cursor.line;
|
||||
const ch = cursor.ch;
|
||||
const value = editor.getLine(line);
|
||||
|
||||
if(ignoreBraces && value.length > 0) return true;
|
||||
if (ignoreBraces && value.length > 0) return true;
|
||||
|
||||
return value.slice(ch, ch + 2) === '}}';
|
||||
}
|
||||
|
||||
export function handleChange(editor, onChange, suggestions, ignoreBraces = false) {
|
||||
|
||||
let state = editor.state.matchHighlighter;
|
||||
editor.addOverlay(state.overlay = makeOverlay(state.options.style));
|
||||
editor.addOverlay((state.overlay = makeOverlay(state.options.style)));
|
||||
|
||||
const cursor = editor.getCursor();
|
||||
const currentWord = computeCurrentWord(editor, cursor.ch, ignoreBraces);
|
||||
|
|
@ -103,11 +99,11 @@ export function handleChange(editor, onChange, suggestions, ignoreBraces = false
|
|||
return {
|
||||
from: { line: cursor.line, ch: cursor.ch - currentWord.length },
|
||||
to: cursor,
|
||||
list: hints
|
||||
}
|
||||
}
|
||||
list: hints,
|
||||
};
|
||||
},
|
||||
};
|
||||
if (canShowHint(editor, ignoreBraces)) {
|
||||
editor.showHint(options);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
|
||||
var tinycolor = require("tinycolor2");
|
||||
var tinycolor = require('tinycolor2');
|
||||
|
||||
export const Button = function Button({
|
||||
id, width, height, component, onComponentClick, currentState
|
||||
}) {
|
||||
export const Button = function Button({ id, width, height, component, onComponentClick, currentState }) {
|
||||
console.log('currentState', currentState);
|
||||
|
||||
const [loadingState, setLoadingState] = useState(false);
|
||||
|
|
@ -15,6 +13,7 @@ export const Button = function Button({
|
|||
const newState = resolveReferences(loadingStateProperty.value, currentState, false);
|
||||
setLoadingState(newState);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentState]);
|
||||
|
||||
const text = component.definition.properties.text.value;
|
||||
|
|
@ -23,12 +22,15 @@ export const Button = function Button({
|
|||
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
|
||||
const disabledState = component.definition.styles?.disabledState?.value ?? false;
|
||||
|
||||
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
const parsedDisabledState =
|
||||
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
let parsedWidgetVisibility = widgetVisibility;
|
||||
|
||||
|
||||
try {
|
||||
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
|
||||
} catch (err) { console.log(err); }
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
const computedStyles = {
|
||||
backgroundColor,
|
||||
|
|
@ -36,7 +38,7 @@ export const Button = function Button({
|
|||
width,
|
||||
height,
|
||||
display: parsedWidgetVisibility ? '' : 'none',
|
||||
'--tblr-btn-color-darker': tinycolor(backgroundColor).darken(8).toString()
|
||||
'--tblr-btn-color-darker': tinycolor(backgroundColor).darken(8).toString(),
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -44,7 +46,10 @@ export const Button = function Button({
|
|||
disabled={parsedDisabledState}
|
||||
className={`jet-button btn btn-primary p-1 ${loadingState === true ? ' btn-loading' : ''}`}
|
||||
style={computedStyles}
|
||||
onClick={(event) => {event.stopPropagation(); onComponentClick(id, component)}}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component);
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -4,14 +4,9 @@ import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
|
|||
// Use plotly basic bundle
|
||||
import Plotly from 'plotly.js-basic-dist-min';
|
||||
import createPlotlyComponent from 'react-plotly.js/factory';
|
||||
const Plot = createPlotlyComponent(Plotly)
|
||||
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
|
||||
export const Chart = function Chart({
|
||||
id, width, height, component, onComponentClick, currentState, darkMode
|
||||
}) {
|
||||
const Plot = createPlotlyComponent(Plotly);
|
||||
|
||||
export const Chart = function Chart({ id, width, height, component, onComponentClick, currentState, darkMode }) {
|
||||
const [loadingState, setLoadingState] = useState(false);
|
||||
const [chartData, setChartData] = useState([]);
|
||||
|
||||
|
|
@ -19,28 +14,31 @@ export const Chart = function Chart({
|
|||
const disabledState = component.definition.styles?.disabledState?.value ?? false;
|
||||
|
||||
let parsedWidgetVisibility = widgetVisibility;
|
||||
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
|
||||
const parsedDisabledState =
|
||||
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
|
||||
try {
|
||||
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
|
||||
} catch (err) { console.log(err); }
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const loadingStateProperty = component.definition.properties.loadingState;
|
||||
if (loadingStateProperty && currentState) {
|
||||
const newState = resolveReferences(loadingStateProperty.value, currentState, false);
|
||||
setLoadingState(newState);
|
||||
setLoadingState(newState);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentState]);
|
||||
|
||||
const computedStyles = {
|
||||
width,
|
||||
height,
|
||||
display: parsedWidgetVisibility ? '' : 'none',
|
||||
background: darkMode ? '#1f2936' : 'white'
|
||||
background: darkMode ? '#1f2936' : 'white',
|
||||
};
|
||||
|
||||
|
||||
// darkMode ? '#1f2936' : 'white'
|
||||
const dataProperty = component.definition.properties.data;
|
||||
const dataString = dataProperty ? dataProperty.value : [];
|
||||
|
|
@ -66,73 +64,84 @@ export const Chart = function Chart({
|
|||
title: {
|
||||
text: title,
|
||||
font: {
|
||||
color: fontColor
|
||||
}
|
||||
color: fontColor,
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
text: title,
|
||||
font: {
|
||||
color: fontColor
|
||||
}
|
||||
color: fontColor,
|
||||
},
|
||||
},
|
||||
xaxis: {
|
||||
showgrid: showGridLines,
|
||||
showline: true,
|
||||
color: fontColor
|
||||
color: fontColor,
|
||||
},
|
||||
yaxis: {
|
||||
showgrid: showGridLines,
|
||||
showline: true,
|
||||
color: fontColor
|
||||
}
|
||||
}
|
||||
showgrid: showGridLines,
|
||||
showline: true,
|
||||
color: fontColor,
|
||||
},
|
||||
};
|
||||
|
||||
const data = resolveReferences(dataString, currentState, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
let rawData = data || [];
|
||||
if(typeof rawData === 'string') {
|
||||
if (typeof rawData === 'string') {
|
||||
try {
|
||||
rawData = JSON.parse(dataString);
|
||||
} catch (err) { rawData = []; }
|
||||
} catch (err) {
|
||||
rawData = [];
|
||||
}
|
||||
}
|
||||
|
||||
if(!Array.isArray(rawData)) { rawData = []; }
|
||||
if (!Array.isArray(rawData)) {
|
||||
rawData = [];
|
||||
}
|
||||
|
||||
let newData = [];
|
||||
|
||||
if(chartType === 'pie') {
|
||||
newData = [{
|
||||
type: chartType,
|
||||
values: rawData.map((item) => item["value"]),
|
||||
labels: rawData.map((item) => item["label"]),
|
||||
}];
|
||||
if (chartType === 'pie') {
|
||||
newData = [
|
||||
{
|
||||
type: chartType,
|
||||
values: rawData.map((item) => item['value']),
|
||||
labels: rawData.map((item) => item['label']),
|
||||
},
|
||||
];
|
||||
} else {
|
||||
newData = [{
|
||||
type: chartType || 'line',
|
||||
x: rawData.map((item) => item["x"]),
|
||||
y: rawData.map((item) => item["y"]),
|
||||
marker: { color: markerColor }
|
||||
}];
|
||||
newData = [
|
||||
{
|
||||
type: chartType || 'line',
|
||||
x: rawData.map((item) => item['x']),
|
||||
y: rawData.map((item) => item['y']),
|
||||
marker: { color: markerColor },
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
setChartData(newData);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [data, chartType]);
|
||||
|
||||
return (
|
||||
<div
|
||||
data-disabled={parsedDisabledState}
|
||||
style={computedStyles}
|
||||
onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component);
|
||||
}}
|
||||
>
|
||||
{loadingState === true ?
|
||||
{loadingState === true ? (
|
||||
<div style={{ width: '100%' }} className="p-2">
|
||||
<center>
|
||||
<div className="spinner-border mt-5" role="status"></div>
|
||||
</center>
|
||||
</div>
|
||||
:
|
||||
) : (
|
||||
<Plot
|
||||
data={chartData}
|
||||
layout={layout}
|
||||
|
|
@ -141,7 +150,7 @@ export const Chart = function Chart({
|
|||
// staticPlot: true
|
||||
}}
|
||||
/>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,22 +9,24 @@ export const Checkbox = function Checkbox({
|
|||
onComponentClick,
|
||||
currentState,
|
||||
onComponentOptionChanged,
|
||||
onEvent
|
||||
onEvent,
|
||||
}) {
|
||||
|
||||
const label = component.definition.properties.label.value;
|
||||
const textColorProperty = component.definition.styles.textColor;
|
||||
const textColor = textColorProperty ? textColorProperty.value : '#000';
|
||||
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
|
||||
const disabledState = component.definition.styles?.disabledState?.value ?? false;
|
||||
|
||||
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
const parsedDisabledState =
|
||||
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
|
||||
let parsedWidgetVisibility = widgetVisibility;
|
||||
|
||||
|
||||
try {
|
||||
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
|
||||
} catch (err) { console.log(err); }
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
function toggleValue(e) {
|
||||
const checked = e.target.checked;
|
||||
|
|
@ -37,7 +39,15 @@ export const Checkbox = function Checkbox({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="row" style={{ width, height, display:parsedWidgetVisibility ? '' : 'none' }} onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}>
|
||||
<div
|
||||
data-disabled={parsedDisabledState}
|
||||
className="row py-1"
|
||||
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component);
|
||||
}}
|
||||
>
|
||||
<label className="my-auto mx-2 form-check form-check-inline">
|
||||
<input
|
||||
className="form-check-input"
|
||||
|
|
@ -46,7 +56,9 @@ export const Checkbox = function Checkbox({
|
|||
toggleValue(e);
|
||||
}}
|
||||
/>
|
||||
<span className="form-check-label" style={{color: textColor}}>{label}</span>
|
||||
<span className="form-check-label" style={{ color: textColor }}>
|
||||
{label}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -10,43 +10,42 @@ export const Container = function Container({
|
|||
containerProps,
|
||||
width,
|
||||
currentState,
|
||||
removeComponent
|
||||
removeComponent,
|
||||
}) {
|
||||
|
||||
const backgroundColor = component.definition.styles.backgroundColor.value;
|
||||
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
|
||||
const disabledState = component.definition.styles?.disabledState?.value ?? false;
|
||||
|
||||
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
const parsedDisabledState =
|
||||
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
|
||||
let parsedWidgetVisibility = widgetVisibility;
|
||||
|
||||
|
||||
try {
|
||||
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
|
||||
} catch (err) { console.log(err); }
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
const computedStyles = {
|
||||
backgroundColor,
|
||||
width,
|
||||
height,
|
||||
display: parsedWidgetVisibility ? 'flex' : 'none'
|
||||
display: parsedWidgetVisibility ? 'flex' : 'none',
|
||||
};
|
||||
|
||||
const parentRef = useRef(null);
|
||||
|
||||
return (
|
||||
<div data-disabled={parsedDisabledState} className="jet-container" ref={parentRef} onClick={() => containerProps.onComponentClick(id, component)} style={computedStyles}>
|
||||
<SubContainer
|
||||
parent={id}
|
||||
{...containerProps}
|
||||
parentRef={parentRef}
|
||||
removeComponent={removeComponent}
|
||||
/>
|
||||
<SubCustomDragLayer
|
||||
parent={id}
|
||||
parentRef={parentRef}
|
||||
currentLayout={containerProps.currentLayout}
|
||||
/>
|
||||
<div
|
||||
data-disabled={parsedDisabledState}
|
||||
className="jet-container"
|
||||
ref={parentRef}
|
||||
onClick={() => containerProps.onComponentClick(id, component)}
|
||||
style={computedStyles}
|
||||
>
|
||||
<SubContainer parent={id} {...containerProps} parentRef={parentRef} removeComponent={removeComponent} />
|
||||
<SubCustomDragLayer parent={id} parentRef={parentRef} currentLayout={containerProps.currentLayout} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import Datetime from 'react-datetime';
|
||||
import 'react-datetime/css/react-datetime.css';
|
||||
import { resolveReferences, resolveWidgetFieldValue, validateWidget } from '@/_helpers/utils';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const Datepicker = function Datepicker({
|
||||
id,
|
||||
|
|
@ -11,7 +10,7 @@ export const Datepicker = function Datepicker({
|
|||
component,
|
||||
onComponentClick,
|
||||
currentState,
|
||||
onComponentOptionChanged
|
||||
onComponentOptionChanged,
|
||||
}) {
|
||||
console.log('currentState', currentState);
|
||||
|
||||
|
|
@ -22,71 +21,87 @@ export const Datepicker = function Datepicker({
|
|||
const disabledState = component.definition.styles?.disabledState?.value ?? false;
|
||||
const defaultValue = component.definition.properties?.defaultValue?.value ?? '';
|
||||
|
||||
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
const parsedDisabledState =
|
||||
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
|
||||
let parsedWidgetVisibility = widgetVisibility;
|
||||
|
||||
try {
|
||||
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
|
||||
} catch (err) { console.log(err); }
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
const enableTime = resolveReferences(enableTimeProp.value, currentState, false);
|
||||
|
||||
let enableDate = true;
|
||||
if (enableDateProp) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
enableDate = resolveReferences(enableDateProp.value, currentState, true);
|
||||
}
|
||||
|
||||
let dateFormat = formatProp
|
||||
let dateFormat = formatProp;
|
||||
try {
|
||||
dateFormat = resolveReferences(formatProp, currentState);
|
||||
} catch (err) { console.log(err); }
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
function onDateChange(event) {
|
||||
const value = event._isAMomentObject? event.format(dateFormat.value) : event;
|
||||
const value = event._isAMomentObject ? event.format(dateFormat.value) : event;
|
||||
setDateText(value);
|
||||
onComponentOptionChanged(component, 'value', value);
|
||||
}
|
||||
|
||||
let value = defaultValue;
|
||||
if (value && currentState)
|
||||
value = resolveReferences(value, currentState, '');
|
||||
if (value && currentState) value = resolveReferences(value, currentState, '');
|
||||
|
||||
const [dateText, setDateText] = useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
setDateText(value);
|
||||
onComponentOptionChanged(component, 'value', value);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [value]);
|
||||
|
||||
const validationData = validateWidget({
|
||||
validationObject: component.definition.validation,
|
||||
widgetValue: value,
|
||||
currentState
|
||||
})
|
||||
currentState,
|
||||
});
|
||||
|
||||
const { isValid, validationError } = validationData;
|
||||
|
||||
const currentValidState = currentState?.components[component?.name]?.isValid;
|
||||
|
||||
if(currentValidState !== isValid) {
|
||||
if (currentValidState !== isValid) {
|
||||
onComponentOptionChanged(component, 'isValid', isValid);
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-disabled={parsedDisabledState} style={{ width, height, display:parsedWidgetVisibility ? '' : 'none'}} onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}>
|
||||
<Datetime
|
||||
onChange={onDateChange}
|
||||
timeFormat={enableTime}
|
||||
<div
|
||||
data-disabled={parsedDisabledState}
|
||||
className="datepicker-widget"
|
||||
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component);
|
||||
}}
|
||||
>
|
||||
<Datetime
|
||||
onChange={onDateChange}
|
||||
timeFormat={enableTime}
|
||||
closeOnSelect={true}
|
||||
dateFormat={dateFormat.value}
|
||||
value={dateText}
|
||||
renderInput={(props) => {
|
||||
return <input
|
||||
{...props}
|
||||
value={dateText}
|
||||
className={`form-control ${!isValid ? 'is-invalid' : ''} validation-without-icon`}
|
||||
/>
|
||||
return (
|
||||
<input
|
||||
{...props}
|
||||
value={dateText}
|
||||
className={`input-field form-control ${!isValid ? 'is-invalid' : ''} validation-without-icon`}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<div className={`invalid-feedback ${isValid ? '' : 'd-flex'}`}>{validationError}</div>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export const DaterangePicker = function DaterangePicker({
|
|||
component,
|
||||
onComponentClick,
|
||||
currentState,
|
||||
onComponentOptionChanged
|
||||
onComponentOptionChanged,
|
||||
}) {
|
||||
console.log('currentState', currentState);
|
||||
|
||||
|
|
@ -21,18 +21,20 @@ export const DaterangePicker = function DaterangePicker({
|
|||
const formatProp = component.definition.properties.format;
|
||||
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
|
||||
const disabledState = component.definition.styles?.disabledState?.value ?? false;
|
||||
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
|
||||
const parsedDisabledState =
|
||||
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
|
||||
const [focusedInput, setFocusedInput] = useState(null);
|
||||
const [startDate, setStartDate] = useState(startDateProp ? startDateProp.value : null);
|
||||
const [endDate, setEndDate] = useState(endDateProp ? endDateProp.value : null);
|
||||
|
||||
let parsedWidgetVisibility = widgetVisibility;
|
||||
|
||||
|
||||
try {
|
||||
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
|
||||
} catch (err) { console.log(err); }
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
function onDateChange(dates) {
|
||||
const start = dates.startDate;
|
||||
|
|
@ -55,7 +57,14 @@ export const DaterangePicker = function DaterangePicker({
|
|||
}
|
||||
|
||||
return (
|
||||
<div style={{ width, height, display:parsedWidgetVisibility ? '' : 'none' }} onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}>
|
||||
<div
|
||||
className="daterange-picker-widget p-0"
|
||||
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component);
|
||||
}}
|
||||
>
|
||||
<DateRangePicker
|
||||
disabled={parsedDisabledState}
|
||||
startDate={startDate}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable react/no-string-refs */
|
||||
import React from 'react';
|
||||
import { Editor, EditorState, RichUtils, getDefaultKeyBinding } from 'draft-js';
|
||||
import 'draft-js/dist/Draft.css';
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export const DropDown = function DropDown({
|
|||
onComponentClick,
|
||||
currentState,
|
||||
onComponentOptionChanged,
|
||||
onEvent
|
||||
onEvent,
|
||||
}) {
|
||||
console.log('currentState', currentState);
|
||||
|
||||
|
|
@ -20,25 +20,32 @@ export const DropDown = function DropDown({
|
|||
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
|
||||
const disabledState = component.definition.styles?.disabledState?.value ?? false;
|
||||
|
||||
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
const parsedDisabledState =
|
||||
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
|
||||
let parsedValues = values;
|
||||
|
||||
try {
|
||||
parsedValues = resolveReferences(values, currentState, []);
|
||||
} catch (err) { console.log(err); }
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
let parsedDisplayValues = displayValues;
|
||||
|
||||
try {
|
||||
parsedDisplayValues = resolveReferences(displayValues, currentState, []);
|
||||
} catch (err) { console.log(err); }
|
||||
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
let parsedWidgetVisibility = widgetVisibility;
|
||||
|
||||
|
||||
try {
|
||||
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
|
||||
} catch (err) { console.log(err); }
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
let selectOptions = [];
|
||||
|
||||
|
|
@ -46,14 +53,15 @@ export const DropDown = function DropDown({
|
|||
selectOptions = [
|
||||
...parsedValues.map((value, index) => {
|
||||
return { name: parsedDisplayValues[index], value: value };
|
||||
})
|
||||
}),
|
||||
];
|
||||
} catch (err) { console.log(err); }
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
const currentValueProperty = component.definition.properties.value;
|
||||
const value = currentValueProperty ? currentValueProperty.value : '';
|
||||
const [currentValue, setCurrentValue] = useState('');
|
||||
|
||||
|
||||
let newValue = value;
|
||||
if (currentValueProperty && currentState) {
|
||||
|
|
@ -63,14 +71,14 @@ export const DropDown = function DropDown({
|
|||
const validationData = validateWidget({
|
||||
validationObject: component.definition.validation,
|
||||
widgetValue: currentValue,
|
||||
currentState
|
||||
})
|
||||
currentState,
|
||||
});
|
||||
|
||||
const { isValid, validationError } = validationData;
|
||||
|
||||
const currentValidState = currentState?.components[component?.name]?.isValid;
|
||||
|
||||
if(currentValidState !== isValid) {
|
||||
if (currentValidState !== isValid) {
|
||||
onComponentOptionChanged(component, 'isValid', isValid);
|
||||
}
|
||||
|
||||
|
|
@ -79,13 +87,23 @@ export const DropDown = function DropDown({
|
|||
}, [newValue]);
|
||||
|
||||
useEffect(() => {
|
||||
onComponentOptionChanged(component, 'value', currentValue).then(() => onEvent('onSelect', { component }) );
|
||||
onComponentOptionChanged(component, 'value', currentValue).then(() => onEvent('onSelect', { component }));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentValue]);
|
||||
|
||||
return (
|
||||
<div className="dropdown-widget row g-0" style={{ width, height, display:parsedWidgetVisibility ? '' : 'none' }} onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}>
|
||||
<div
|
||||
className="dropdown-widget row g-0"
|
||||
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component);
|
||||
}}
|
||||
>
|
||||
<div className="col-auto">
|
||||
<label style={{marginRight: label !== '' ? '1rem' : '0.001rem'}} className="form-label py-1">{label}</label>
|
||||
<label style={{ marginRight: label !== '' ? '1rem' : '0.001rem' }} className="form-label py-1">
|
||||
{label}
|
||||
</label>
|
||||
</div>
|
||||
<div className="col px-0">
|
||||
<SelectSearch
|
||||
|
|
|
|||
|
|
@ -2,33 +2,39 @@ import React from 'react';
|
|||
import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
|
||||
import LazyLoad from 'react-lazyload';
|
||||
|
||||
export const Image = function Image({
|
||||
id, width, height, component, onComponentClick, currentState
|
||||
}) {
|
||||
export const Image = function Image({ id, width, height, component, onComponentClick, currentState }) {
|
||||
const source = component.definition.properties.source.value;
|
||||
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
|
||||
const disabledState = component.definition.styles?.disabledState?.value ?? false;
|
||||
|
||||
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
const parsedDisabledState =
|
||||
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
|
||||
let data = resolveReferences(source, currentState, null);
|
||||
|
||||
let parsedWidgetVisibility = widgetVisibility;
|
||||
|
||||
|
||||
try {
|
||||
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
|
||||
} catch (err) { console.log(err); }
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
if (data === '') data = null;
|
||||
|
||||
function Placeholder() {
|
||||
return (
|
||||
<div className="skeleton-image" style={{ objectFit: 'contain', width, height }}></div>
|
||||
);
|
||||
return <div className="skeleton-image" style={{ objectFit: 'contain', width, height }}></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-disabled={parsedDisabledState} style={{display:parsedWidgetVisibility ? '' : 'none'}} onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}>
|
||||
<LazyLoad width={width} height={height} placeholder={<Placeholder/>} debounce={500}>
|
||||
<div
|
||||
data-disabled={parsedDisabledState}
|
||||
style={{ display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component);
|
||||
}}
|
||||
>
|
||||
<LazyLoad width={width} height={height} placeholder={<Placeholder />} debounce={500}>
|
||||
<img style={{ objectFit: 'contain' }} src={data} width={width} height={height} />
|
||||
</LazyLoad>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import { GoogleMap, LoadScript } from '@react-google-maps/api';
|
||||
import { Marker } from '@react-google-maps/api';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { GoogleMap, LoadScript, Marker, Autocomplete } from '@react-google-maps/api';
|
||||
import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
|
||||
import { Autocomplete } from '@react-google-maps/api';
|
||||
import { darkModeStyles } from './styles';
|
||||
|
||||
export const Map = function Map({
|
||||
|
|
@ -88,6 +86,7 @@ export const Map = function Map({
|
|||
]).then(() => onEvent('onBoundsChange', { component }));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const onLoad = useCallback(function onLoad(mapInstance) {
|
||||
setGmap(mapInstance);
|
||||
onComponentOptionsChanged(component, [['center', mapInstance.center?.toJSON()]]);
|
||||
|
|
@ -151,7 +150,7 @@ export const Map = function Map({
|
|||
{Array.isArray(markers) && (
|
||||
<>
|
||||
{markers.map((marker, index) => (
|
||||
<Marker position={marker} label={marker.label} onClick={(e) => handleMarkerClick(index)} />
|
||||
<Marker key={index} position={marker} label={marker.label} onClick={() => handleMarkerClick(index)} />
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,137 +1,137 @@
|
|||
export const darkModeStyles = [
|
||||
{
|
||||
"featureType": "all",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#ffffff"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "all",
|
||||
"elementType": "labels.text.stroke",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#000000"
|
||||
},
|
||||
{
|
||||
"lightness": 13
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "administrative",
|
||||
"elementType": "geometry.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#000000"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "administrative",
|
||||
"elementType": "geometry.stroke",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#144b53"
|
||||
},
|
||||
{
|
||||
"lightness": 14
|
||||
},
|
||||
{
|
||||
"weight": 1.4
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "landscape",
|
||||
"elementType": "all",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#08304b"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "poi",
|
||||
"elementType": "geometry",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#0c4152"
|
||||
},
|
||||
{
|
||||
"lightness": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road.highway",
|
||||
"elementType": "geometry.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#000000"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road.highway",
|
||||
"elementType": "geometry.stroke",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#0b434f"
|
||||
},
|
||||
{
|
||||
"lightness": 25
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road.arterial",
|
||||
"elementType": "geometry.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#000000"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road.arterial",
|
||||
"elementType": "geometry.stroke",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#0b3d51"
|
||||
},
|
||||
{
|
||||
"lightness": 16
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road.local",
|
||||
"elementType": "geometry",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#000000"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "transit",
|
||||
"elementType": "all",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#146474"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "water",
|
||||
"elementType": "all",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#021019"
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
{
|
||||
featureType: 'all',
|
||||
elementType: 'labels.text.fill',
|
||||
stylers: [
|
||||
{
|
||||
color: '#ffffff',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
featureType: 'all',
|
||||
elementType: 'labels.text.stroke',
|
||||
stylers: [
|
||||
{
|
||||
color: '#000000',
|
||||
},
|
||||
{
|
||||
lightness: 13,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
featureType: 'administrative',
|
||||
elementType: 'geometry.fill',
|
||||
stylers: [
|
||||
{
|
||||
color: '#000000',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
featureType: 'administrative',
|
||||
elementType: 'geometry.stroke',
|
||||
stylers: [
|
||||
{
|
||||
color: '#144b53',
|
||||
},
|
||||
{
|
||||
lightness: 14,
|
||||
},
|
||||
{
|
||||
weight: 1.4,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
featureType: 'landscape',
|
||||
elementType: 'all',
|
||||
stylers: [
|
||||
{
|
||||
color: '#08304b',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
featureType: 'poi',
|
||||
elementType: 'geometry',
|
||||
stylers: [
|
||||
{
|
||||
color: '#0c4152',
|
||||
},
|
||||
{
|
||||
lightness: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
featureType: 'road.highway',
|
||||
elementType: 'geometry.fill',
|
||||
stylers: [
|
||||
{
|
||||
color: '#000000',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
featureType: 'road.highway',
|
||||
elementType: 'geometry.stroke',
|
||||
stylers: [
|
||||
{
|
||||
color: '#0b434f',
|
||||
},
|
||||
{
|
||||
lightness: 25,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
featureType: 'road.arterial',
|
||||
elementType: 'geometry.fill',
|
||||
stylers: [
|
||||
{
|
||||
color: '#000000',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
featureType: 'road.arterial',
|
||||
elementType: 'geometry.stroke',
|
||||
stylers: [
|
||||
{
|
||||
color: '#0b3d51',
|
||||
},
|
||||
{
|
||||
lightness: 16,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
featureType: 'road.local',
|
||||
elementType: 'geometry',
|
||||
stylers: [
|
||||
{
|
||||
color: '#000000',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
featureType: 'transit',
|
||||
elementType: 'all',
|
||||
stylers: [
|
||||
{
|
||||
color: '#146474',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
featureType: 'water',
|
||||
elementType: 'all',
|
||||
stylers: [
|
||||
{
|
||||
color: '#021019',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -4,16 +4,9 @@ import Button from 'react-bootstrap/Button';
|
|||
import { SubCustomDragLayer } from '../SubCustomDragLayer';
|
||||
import { SubContainer } from '../SubContainer';
|
||||
import { ConfigHandle } from '../ConfigHandle';
|
||||
import { resolveWidgetFieldValue, resolveReferences } from '../../_helpers/utils';
|
||||
import { resolveWidgetFieldValue } from '../../_helpers/utils';
|
||||
|
||||
export const Modal = function Modal({
|
||||
id,
|
||||
component,
|
||||
height,
|
||||
mode,
|
||||
containerProps,
|
||||
currentState
|
||||
}) {
|
||||
export const Modal = function Modal({ id, component, height, containerProps, currentState, darkMode }) {
|
||||
const [show, showModal] = useState(false);
|
||||
const parentRef = useRef(null);
|
||||
|
||||
|
|
@ -25,12 +18,14 @@ export const Modal = function Modal({
|
|||
|
||||
const disabledState = component.definition.styles?.disabledState?.value ?? false;
|
||||
|
||||
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
const parsedDisabledState =
|
||||
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
|
||||
useEffect(() => {
|
||||
const componentState = containerProps.currentState.components[component.name];
|
||||
const canShowModel = componentState ? componentState.show : false;
|
||||
showModal(canShowModel);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [containerProps.currentState.components[component.name]]);
|
||||
|
||||
function hideModal() {
|
||||
|
|
@ -51,36 +46,26 @@ export const Modal = function Modal({
|
|||
animation={false}
|
||||
onEscapeKeyDown={() => showModal(false)}
|
||||
>
|
||||
{containerProps.mode === 'edit' &&
|
||||
<ConfigHandle
|
||||
id={id}
|
||||
component={component}
|
||||
configHandleClicked={containerProps.onComponentClick}
|
||||
/>
|
||||
}
|
||||
{containerProps.mode === 'edit' && (
|
||||
<ConfigHandle id={id} component={component} configHandleClicked={containerProps.onComponentClick} />
|
||||
)}
|
||||
<BootstrapModal.Header>
|
||||
<BootstrapModal.Title>
|
||||
{title}
|
||||
</BootstrapModal.Title>
|
||||
<BootstrapModal.Title>{title}</BootstrapModal.Title>
|
||||
<div>
|
||||
<Button variant="light" size="sm" onClick={hideModal}>
|
||||
<Button variant={darkMode ? 'secondary' : 'light'} size="sm" onClick={hideModal}>
|
||||
x
|
||||
</Button>
|
||||
</div>
|
||||
</BootstrapModal.Header>
|
||||
|
||||
<BootstrapModal.Body style={{ height }} ref={parentRef}>
|
||||
<SubContainer
|
||||
parent={id}
|
||||
{...containerProps}
|
||||
parentRef={parentRef}
|
||||
/>
|
||||
<SubCustomDragLayer
|
||||
snapToGrid={true}
|
||||
parentRef={parentRef}
|
||||
parent={id}
|
||||
currentLayout={containerProps.currentLayout}
|
||||
/>
|
||||
<SubContainer parent={id} {...containerProps} parentRef={parentRef} />
|
||||
<SubCustomDragLayer
|
||||
snapToGrid={true}
|
||||
parentRef={parentRef}
|
||||
parent={id}
|
||||
currentLayout={containerProps.currentLayout}
|
||||
/>
|
||||
</BootstrapModal.Body>
|
||||
</BootstrapModal>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export const Multiselect = function Multiselect({
|
|||
component,
|
||||
onComponentClick,
|
||||
currentState,
|
||||
onComponentOptionChanged
|
||||
onComponentOptionChanged,
|
||||
}) {
|
||||
console.log('currentState', currentState);
|
||||
|
||||
|
|
@ -19,7 +19,8 @@ export const Multiselect = function Multiselect({
|
|||
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
|
||||
const disabledState = component.definition.styles?.disabledState?.value ?? false;
|
||||
|
||||
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
const parsedDisabledState =
|
||||
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
|
||||
const parsedValues = JSON.parse(values);
|
||||
const parsedDisplayValues = JSON.parse(displayValues);
|
||||
|
|
@ -27,7 +28,7 @@ export const Multiselect = function Multiselect({
|
|||
const selectOptions = [
|
||||
...parsedValues.map((value, index) => {
|
||||
return { name: parsedDisplayValues[index], value: value };
|
||||
})
|
||||
}),
|
||||
];
|
||||
|
||||
const currentValueProperty = component.definition.properties.values;
|
||||
|
|
@ -40,19 +41,30 @@ export const Multiselect = function Multiselect({
|
|||
}
|
||||
|
||||
let parsedWidgetVisibility = widgetVisibility;
|
||||
|
||||
|
||||
try {
|
||||
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
|
||||
} catch (err) { console.log(err); }
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentValue(newValue);
|
||||
}, [newValue]);
|
||||
|
||||
return (
|
||||
<div className="multiselect-widget row g-0" style={{ width, height, display:parsedWidgetVisibility ? '' : 'none' }} onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}>
|
||||
<div
|
||||
className="multiselect-widget row g-0"
|
||||
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component);
|
||||
}}
|
||||
>
|
||||
<div className="col-auto">
|
||||
<label style={{marginRight: '1rem'}} className="form-label py-1">{label}</label>
|
||||
<label style={{ marginRight: '1rem' }} className="form-label py-1">
|
||||
{label}
|
||||
</label>
|
||||
</div>
|
||||
<div className="col px-0">
|
||||
<SelectSearch
|
||||
|
|
|
|||
|
|
@ -8,9 +8,8 @@ export const NumberInput = function NumberInput({
|
|||
component,
|
||||
onComponentClick,
|
||||
currentState,
|
||||
onComponentOptionChanged
|
||||
onComponentOptionChanged,
|
||||
}) {
|
||||
|
||||
const value = component.definition.properties.value ? component.definition.properties.value.value : '';
|
||||
const [number, setNumber] = useState(value);
|
||||
|
||||
|
|
@ -23,24 +22,31 @@ export const NumberInput = function NumberInput({
|
|||
useEffect(() => {
|
||||
setNumber(parseInt(newNumber));
|
||||
onComponentOptionChanged(component, 'value', parseInt(newNumber));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [newNumber]);
|
||||
|
||||
const placeholder = component.definition.properties.placeholder.value;
|
||||
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
|
||||
const disabledState = component.definition.styles?.disabledState?.value ?? false;
|
||||
|
||||
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
const parsedDisabledState =
|
||||
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
|
||||
let parsedWidgetVisibility = widgetVisibility;
|
||||
|
||||
|
||||
try {
|
||||
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
|
||||
} catch (err) { console.log(err); }
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
return (
|
||||
<input
|
||||
disabled={parsedDisabledState}
|
||||
onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component);
|
||||
}}
|
||||
onChange={(e) => {
|
||||
setNumber(parseInt(e.target.value));
|
||||
onComponentOptionChanged(component, 'value', parseInt(e.target.value));
|
||||
|
|
@ -48,7 +54,7 @@ export const NumberInput = function NumberInput({
|
|||
type="number"
|
||||
className="form-control"
|
||||
placeholder={placeholder}
|
||||
style={{ width, height, display:parsedWidgetVisibility ? '' : 'none' }}
|
||||
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
value={number}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,34 +1,41 @@
|
|||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
export default function ErrorModal() {
|
||||
|
||||
const [show, setShow] = React.useState(true)
|
||||
const [show, setShow] = React.useState(true);
|
||||
|
||||
const close = () => {
|
||||
setShow(false)
|
||||
}
|
||||
|
||||
return(
|
||||
setShow(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
show ?
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">QR Scanner is not working</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" onClick={close}></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Please make sure a camera is available on your device. Try closing your browser and opening it again, if it doesn't work, please contact support.
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn" data-bs-dismiss="modal" onClick={close}>Close</button>
|
||||
</div>
|
||||
{show ? (
|
||||
<div className="modal-dialog" role="document">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">QR Scanner is not working</h5>
|
||||
<button
|
||||
type="button"
|
||||
className="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
onClick={close}
|
||||
></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
Please make sure a camera is available on your device. Try closing your browser and opening it again, if
|
||||
it doesn't work, please contact support.
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn" data-bs-dismiss="modal" onClick={close}>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
''
|
||||
}
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,7 @@ import QrReader from 'react-qr-reader';
|
|||
import ErrorModal from './ErrorModal';
|
||||
import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
|
||||
|
||||
export const QrScanner = function QrScanner({
|
||||
component, onEvent, onComponentOptionChanged, currentState
|
||||
}) {
|
||||
|
||||
export const QrScanner = function QrScanner({ component, onEvent, onComponentOptionChanged, currentState }) {
|
||||
const handleError = async (errorMessage) => {
|
||||
console.log(errorMessage);
|
||||
setErrorOccured(true);
|
||||
|
|
@ -16,7 +13,7 @@ export const QrScanner = function QrScanner({
|
|||
if (data != null) {
|
||||
onEvent('onDetect', { component, data: data });
|
||||
onComponentOptionChanged(component, 'lastDetectedValue', data);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
let [errorOccured, setErrorOccured] = useState(false);
|
||||
|
|
@ -24,25 +21,20 @@ export const QrScanner = function QrScanner({
|
|||
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
|
||||
const disabledState = component.definition.styles?.disabledState?.value ?? false;
|
||||
|
||||
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
const parsedDisabledState =
|
||||
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
|
||||
let parsedWidgetVisibility = widgetVisibility;
|
||||
|
||||
|
||||
try {
|
||||
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
|
||||
} catch (err) { console.log(err); }
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-disabled={parsedDisabledState} style={{display:parsedWidgetVisibility ? '' : 'none'}}>
|
||||
{
|
||||
errorOccured ?
|
||||
<ErrorModal />
|
||||
:
|
||||
<QrReader
|
||||
onError={handleError}
|
||||
onScan={handleScan}
|
||||
/>
|
||||
}
|
||||
<div data-disabled={parsedDisabledState} style={{ display: parsedWidgetVisibility ? '' : 'none' }}>
|
||||
{errorOccured ? <ErrorModal /> : <QrReader onError={handleError} onScan={handleScan} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
|
||||
|
||||
|
||||
export const RadioButton = function RadioButton({
|
||||
id,
|
||||
width,
|
||||
|
|
@ -10,9 +9,8 @@ export const RadioButton = function RadioButton({
|
|||
onComponentClick,
|
||||
currentState,
|
||||
onComponentOptionChanged,
|
||||
onEvent
|
||||
onEvent,
|
||||
}) {
|
||||
|
||||
const label = component.definition.properties.label.value;
|
||||
const textColorProperty = component.definition.styles.textColor;
|
||||
const textColor = textColorProperty ? textColorProperty.value : '#000';
|
||||
|
|
@ -23,25 +21,34 @@ export const RadioButton = function RadioButton({
|
|||
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
|
||||
const disabledState = component.definition.styles?.disabledState?.value ?? false;
|
||||
|
||||
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
const parsedDisabledState =
|
||||
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
|
||||
let parsedValues = values;
|
||||
|
||||
try {
|
||||
parsedValues = resolveReferences(values, currentState, []);
|
||||
} catch (err) { console.log(err); }
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
let parsedDisplayValues = displayValues;
|
||||
|
||||
try {
|
||||
parsedDisplayValues = resolveReferences(displayValues, currentState, []);
|
||||
} catch (err) { console.log(err); }
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
let parsedDefaultValue = defaultValue;
|
||||
|
||||
try {
|
||||
parsedDefaultValue = resolveReferences(defaultValue, currentState, []);
|
||||
} catch (err) { console.log(err); }
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
const value = currentState?.components[component?.name]?.value ?? parsedDefaultValue;
|
||||
|
||||
let selectOptions = [];
|
||||
|
||||
|
|
@ -49,33 +56,59 @@ export const RadioButton = function RadioButton({
|
|||
selectOptions = [
|
||||
...parsedValues.map((value, index) => {
|
||||
return { name: parsedDisplayValues[index], value: value };
|
||||
})
|
||||
}),
|
||||
];
|
||||
} catch (err) { console.log(err); }
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
let parsedWidgetVisibility = widgetVisibility;
|
||||
|
||||
|
||||
try {
|
||||
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
|
||||
} catch (err) { console.log(err); }
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
|
||||
function onSelect(event) {
|
||||
const selection = event.target.value
|
||||
function onSelect(selection) {
|
||||
onComponentOptionChanged(component, 'value', selection);
|
||||
if (selection) {
|
||||
onEvent('onSelectionChange', { component });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
onComponentOptionChanged(component, 'value', parsedDefaultValue);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [parsedDefaultValue]);
|
||||
|
||||
return (
|
||||
<div data-disabled={parsedDisabledState} className="row" style={{ width, height, display:parsedWidgetVisibility ? '' : 'none' }} onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}>
|
||||
<span className="form-check-label form-check-label col-auto py-1" style={{color: textColor}}>{label}</span>
|
||||
<div className="col py-1" onChange={(e) => onSelect(e)}>
|
||||
<div
|
||||
data-disabled={parsedDisabledState}
|
||||
className="row"
|
||||
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component);
|
||||
}}
|
||||
>
|
||||
<span className="form-check-label col-auto py-1" style={{ color: textColor }}>
|
||||
{label}
|
||||
</span>
|
||||
<div className="col py-1">
|
||||
{selectOptions.map((option, index) => (
|
||||
<label key={index} class="form-check form-check-inline">
|
||||
<input class="form-check-input" defaultChecked={parsedDefaultValue === option.value} type="radio" value={option.value} name="radio-options" />
|
||||
<span className="form-check-label" style={{color: textColor}}>{option.name}</span>
|
||||
<label key={index} className="form-check form-check-inline">
|
||||
<input
|
||||
className="form-check-input"
|
||||
checked={value === option.value}
|
||||
type="radio"
|
||||
value={option.value}
|
||||
name={`${id}-radio-options`}
|
||||
onChange={() => onSelect(option.value)}
|
||||
/>
|
||||
<span className="form-check-label" style={{ color: textColor }}>
|
||||
{option.name}
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react';
|
||||
import { Editor, EditorState } from "draft-js";
|
||||
import "draft-js/dist/Draft.css";
|
||||
import 'draft-js/dist/Draft.css';
|
||||
import { DraftEditor } from './DraftEditor';
|
||||
import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
|
||||
|
||||
|
|
@ -11,33 +10,37 @@ export const RichTextEditor = function RichTextEditor({
|
|||
component,
|
||||
onComponentClick,
|
||||
currentState,
|
||||
onComponentOptionChanged
|
||||
onComponentOptionChanged,
|
||||
}) {
|
||||
|
||||
const placeholder = component.definition.properties.placeholder.value;
|
||||
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
|
||||
const disabledState = component.definition.styles?.disabledState?.value ?? false;
|
||||
|
||||
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
const parsedDisabledState =
|
||||
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
|
||||
let parsedWidgetVisibility = widgetVisibility;
|
||||
|
||||
|
||||
try {
|
||||
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
|
||||
} catch (err) { console.log(err); }
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
function handleChange(html) {
|
||||
onComponentOptionChanged(component, 'value', html);
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-disabled={parsedDisabledState} style={{ width: `${width}px`, height: `${height}px`, display:parsedWidgetVisibility ? '' : 'none' }} onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}>
|
||||
<DraftEditor
|
||||
handleChange={handleChange}
|
||||
height={height}
|
||||
width={width}
|
||||
placeholder={placeholder}
|
||||
></DraftEditor>
|
||||
<div
|
||||
data-disabled={parsedDisabledState}
|
||||
style={{ width: `${width}px`, height: `${height}px`, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component);
|
||||
}}
|
||||
>
|
||||
<DraftEditor handleChange={handleChange} height={height} width={width} placeholder={placeholder}></DraftEditor>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,47 +1,44 @@
|
|||
import React from 'react'
|
||||
import React from 'react';
|
||||
|
||||
export default ({ fill = '#ffb400'}) => {
|
||||
export default ({ fill = '#ffb400' }) => {
|
||||
return (
|
||||
<svg height="20" width="20" fill={fill} version="1.1" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 630 630" xmlSpace="preserve">
|
||||
<path d="M610.089,233.999c-4.591-14.132-16.807-24.432-31.512-26.567l-164.157-23.856L341.006,34.827
|
||||
<svg
|
||||
height="20"
|
||||
width="20"
|
||||
fill={fill}
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 630 630"
|
||||
xmlSpace="preserve"
|
||||
>
|
||||
<path
|
||||
d="M610.089,233.999c-4.591-14.132-16.807-24.432-31.512-26.567l-164.157-23.856L341.006,34.827
|
||||
c-6.577-13.325-20.147-21.76-35.005-21.76c-14.86,0-28.43,8.435-35.005,21.76l-73.416,148.749L33.424,207.432
|
||||
c-14.705,2.136-26.921,12.436-31.512,26.567s-0.762,29.645,9.879,40.017l118.786,115.787l-28.043,163.492
|
||||
c-2.513,14.646,3.507,29.446,15.529,38.18c12.021,8.734,27.958,9.885,41.112,2.972l146.825-77.192l146.825,77.192
|
||||
c5.713,3.004,11.949,4.485,18.162,4.485c8.092,0,16.149-2.515,22.95-7.455c12.021-8.734,18.041-23.536,15.529-38.18
|
||||
l-28.041-163.494l118.784-115.789C610.851,263.643,614.68,248.131,610.089,233.999z M412.235,348.222
|
||||
c-9.202,8.967-13.399,21.889-11.227,34.552l18.139,105.762l-94.979-49.934c-3.162-1.664-6.498-2.847-9.909-3.584V157.04
|
||||
l39.232,79.493c5.686,11.522,16.676,19.508,29.391,21.354l106.192,15.431L412.235,348.222z"/>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
l39.232,79.493c5.686,11.522,16.676,19.508,29.391,21.354l106.192,15.431L412.235,348.222z"
|
||||
/>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,44 +1,41 @@
|
|||
import React from 'react'
|
||||
import React from 'react';
|
||||
|
||||
export default ({ fill = '#ffb400'}) => {
|
||||
export default ({ fill = '#ffb400' }) => {
|
||||
return (
|
||||
<svg height="20" width="20" fill={fill} version="1.1" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 500 500" xmlSpace="preserve">
|
||||
<path d="M471.563,173.778l-145.5-20.8l-64.4-132c-8-15.4-30-12.2-35.3,0l-64.4,132l-145.6,20.8c-16.4,1-21.6,20.9-10.4,33.2
|
||||
<svg
|
||||
height="20"
|
||||
width="20"
|
||||
fill={fill}
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 500 500"
|
||||
xmlSpace="preserve"
|
||||
>
|
||||
<path
|
||||
d="M471.563,173.778l-145.5-20.8l-64.4-132c-8-15.4-30-12.2-35.3,0l-64.4,132l-145.6,20.8c-16.4,1-21.6,20.9-10.4,33.2
|
||||
l105,102.9l-25,144.5c-2.9,17.8,16.7,27.8,28.1,20.8l129.9-68.6l129.9,67.6c13.6,7,29.8-2.8,28.1-19.7l-25-144.6l105-102.9
|
||||
C494.663,193.478,485.563,175.478,471.563,173.778z M342.663,288.078c-4.2,5.2-6.2,11.4-5.2,17.7l19.7,116.4l-103.9-55.1
|
||||
c-6.7-2.8-13-2.8-18.7,0l-103.9,55.1l19.7-116.4c1-7.3-1-13.5-5.2-17.7l-84.1-82.1l116.4-16.6c6.2-1,11.4-4.2,14.6-10.4l52-105
|
||||
l52,105c3.1,5.2,8.3,9.4,14.6,10.4l116.2,16.6L342.663,288.078z"/>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
l52,105c3.1,5.2,8.3,9.4,14.6,10.4l116.2,16.6L342.663,288.078z"
|
||||
/>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,44 +1,41 @@
|
|||
import React from 'react'
|
||||
import React from 'react';
|
||||
|
||||
export default ({ fill = '#ffb400'}) => {
|
||||
export default ({ fill = '#ffb400' }) => {
|
||||
return (
|
||||
<svg version="1.1" fill={fill} height="20" width="20" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 60 60" xmlSpace="preserve">
|
||||
<path d="M55.818,21.578c-0.118-0.362-0.431-0.626-0.808-0.681L36.92,18.268L28.83,1.876c-0.168-0.342-0.516-0.558-0.896-0.558
|
||||
<svg
|
||||
version="1.1"
|
||||
fill={fill}
|
||||
height="20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 60 60"
|
||||
xmlSpace="preserve"
|
||||
>
|
||||
<path
|
||||
d="M55.818,21.578c-0.118-0.362-0.431-0.626-0.808-0.681L36.92,18.268L28.83,1.876c-0.168-0.342-0.516-0.558-0.896-0.558
|
||||
s-0.729,0.216-0.896,0.558l-8.091,16.393l-18.09,2.629c-0.377,0.055-0.689,0.318-0.808,0.681c-0.117,0.361-0.02,0.759,0.253,1.024
|
||||
l13.091,12.76l-3.091,18.018c-0.064,0.375,0.09,0.754,0.397,0.978c0.309,0.226,0.718,0.255,1.053,0.076l16.182-8.506l16.18,8.506
|
||||
c0.146,0.077,0.307,0.115,0.466,0.115c0.207,0,0.413-0.064,0.588-0.191c0.308-0.224,0.462-0.603,0.397-0.978l-3.09-18.017
|
||||
l13.091-12.761C55.838,22.336,55.936,21.939,55.818,21.578z"/>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
l13.091-12.761C55.838,22.336,55.936,21.939,55.818,21.578z"
|
||||
/>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -20,16 +20,21 @@ export const StarRating = function StarRating({
|
|||
const allowHalfStar = component.definition.properties.allowHalfStar.value ?? false;
|
||||
const textColorProperty = component.definition.styles.textColor;
|
||||
const color = textColorProperty ? textColorProperty.value : '#ffb400';
|
||||
const labelColorProperty = component.definition.styles.labelColor;
|
||||
const labelColor = labelColorProperty ? labelColorProperty.value : '#333';
|
||||
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
|
||||
const disabledState = component.definition.styles?.disabledState?.value ?? false;
|
||||
|
||||
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
const parsedDisabledState =
|
||||
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
|
||||
let parsedWidgetVisibility = widgetVisibility;
|
||||
|
||||
|
||||
try {
|
||||
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
|
||||
} catch (err) { console.log(err); }
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
const tooltips = component.definition.properties.tooltips.value ?? [];
|
||||
const _tooltips = resolveReferences(tooltips, currentState, []) ?? [];
|
||||
|
|
@ -54,12 +59,14 @@ export const StarRating = function StarRating({
|
|||
React.useEffect(() => {
|
||||
setRatingIndex(defaultSelected - 1);
|
||||
onComponentOptionChanged(component, 'value', defaultSelected);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [defaultSelected]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setTimeout(() => {
|
||||
onComponentOptionChanged(component, 'value', defaultSelected);
|
||||
}, 1000)
|
||||
}, 1000);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
function handleClick(idx) {
|
||||
|
|
@ -84,9 +91,17 @@ export const StarRating = function StarRating({
|
|||
};
|
||||
|
||||
return (
|
||||
<div data-disabled={parsedDisabledState} className="star-rating" onClick={event => {event.stopPropagation(); onComponentClick(id, component)}} style={{display:parsedWidgetVisibility ? '' : 'none'}}>
|
||||
<div
|
||||
data-disabled={parsedDisabledState}
|
||||
className="star-rating"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component);
|
||||
}}
|
||||
style={{ display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
>
|
||||
{/* TODO: Add label color defination property instead of hardcoded color*/}
|
||||
<span className="label form-check-label form-check-label col-auto" style={{ color: '#000' }}>
|
||||
<span className="label form-check-label form-check-label col-auto" style={{ color: labelColor }}>
|
||||
{label}
|
||||
</span>
|
||||
{animatedStars.map((props, index) => (
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ const Star = ({
|
|||
React.useEffect(() => {
|
||||
setIcon(isHalfStar ? halfStar : star);
|
||||
setOutlineIcon(isHalfStar ? halfStar : starOutline);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [color]);
|
||||
|
||||
const ref = React.useRef(null);
|
||||
|
|
|
|||
|
|
@ -1,26 +1,29 @@
|
|||
import React from 'react';
|
||||
import SelectSearch from 'react-select-search';
|
||||
|
||||
export const CustomSelect = ({ options, value, multiple, disabled, onChange }) => {
|
||||
|
||||
function renderValue(valueProps) {
|
||||
if(valueProps) {
|
||||
return valueProps.value.split(', ').map((value) => <span {...valueProps} className="badge bg-blue-lt p-2 mx-1">{value}</span>);
|
||||
}
|
||||
export const CustomSelect = ({ options, value, multiple, onChange }) => {
|
||||
function renderValue(valueProps) {
|
||||
if (valueProps) {
|
||||
return valueProps.value.split(', ').map((value, index) => (
|
||||
<span key={index} {...valueProps} className="badge bg-blue-lt p-2 mx-1">
|
||||
{value}
|
||||
</span>
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="custom-select">
|
||||
<SelectSearch
|
||||
options={options}
|
||||
printOptions="on-focus"
|
||||
value={value}
|
||||
renderValue={renderValue}
|
||||
search={false}
|
||||
onChange={onChange}
|
||||
multiple={multiple}
|
||||
placeholder="Select.."
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<div className="custom-select">
|
||||
<SelectSearch
|
||||
options={options}
|
||||
printOptions="on-focus"
|
||||
value={value}
|
||||
renderValue={renderValue}
|
||||
search={false}
|
||||
onChange={onChange}
|
||||
multiple={multiple}
|
||||
placeholder="Select.."
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,81 +1,51 @@
|
|||
import React from 'react';
|
||||
import Datetime from 'react-datetime';
|
||||
import moment from 'moment';
|
||||
|
||||
import 'react-datetime/css/react-datetime.css';
|
||||
import '@/_styles/custom.scss';
|
||||
import moment from 'moment'
|
||||
|
||||
export const Datepicker = function Datepicker({value, onChange, readOnly, isTimeChecked, dateFormat}) {
|
||||
|
||||
const [date, setDate] = React.useState(value)
|
||||
export const Datepicker = function Datepicker({ value, onChange, readOnly, isTimeChecked, dateFormat }) {
|
||||
const [date, setDate] = React.useState(value);
|
||||
|
||||
const dateChange = (e) => {
|
||||
if(isTimeChecked) {
|
||||
setDate(e.format(`${dateFormat} LT`))
|
||||
} else {
|
||||
setDate(e.format(dateFormat))
|
||||
}
|
||||
const dateChange = (e) => {
|
||||
if (isTimeChecked) {
|
||||
setDate(e.format(`${dateFormat} LT`));
|
||||
} else {
|
||||
setDate(e.format(dateFormat));
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isTimeChecked) {
|
||||
setDate(moment(value, 'DD-MM-YYYY').format(dateFormat));
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
const _date = isTimeChecked ? moment(value, `${dateFormat} LT`) : moment(value, dateFormat)
|
||||
|
||||
if(!isTimeChecked) {
|
||||
setDate(moment(value, 'DD-MM-YYYY').format(dateFormat))
|
||||
}
|
||||
|
||||
if(isTimeChecked) {
|
||||
setDate(moment(value, 'DD-MM-YYYY LT' ).format(`${dateFormat} LT`))
|
||||
}
|
||||
|
||||
},[isTimeChecked, readOnly, dateFormat])
|
||||
|
||||
|
||||
let inputProps = {
|
||||
disabled: !readOnly,
|
||||
};
|
||||
|
||||
const [isDatepickerOpen, setIsDatepickerOpen] = React.useState(false)
|
||||
|
||||
const onDatepickerClose = () => {
|
||||
onChange(date)
|
||||
setIsDatepickerOpen((prev) => !prev)
|
||||
if (isTimeChecked) {
|
||||
setDate(moment(value, 'DD-MM-YYYY LT').format(`${dateFormat} LT`));
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isTimeChecked, readOnly, dateFormat]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const myElement = document.querySelector('.cell-type-datepicker')
|
||||
let inputProps = {
|
||||
disabled: !readOnly,
|
||||
};
|
||||
|
||||
myElement.parentNode.style.position = 'absolute'
|
||||
myElement.style.position = 'relative'
|
||||
myElement.style.marginTop = '2px'
|
||||
myElement.style.left = '50%'
|
||||
myElement.style.width = '250px'
|
||||
myElement.style.transform = 'translate(-50%, -25%)'
|
||||
|
||||
|
||||
return () => {
|
||||
myElement.parentNode.style.position = ''
|
||||
myElement.style.position = ''
|
||||
myElement.style.marginTop = ''
|
||||
myElement.style.left = ''
|
||||
myElement.style.width = ''
|
||||
myElement.style.transform = ''
|
||||
}
|
||||
|
||||
},[isDatepickerOpen])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Datetime
|
||||
inputProps={inputProps}
|
||||
timeFormat={isTimeChecked}
|
||||
className='cell-type-datepicker'
|
||||
dateFormat={dateFormat}
|
||||
value={date}
|
||||
onChange={dateChange}
|
||||
onClose={onDatepickerClose}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
const onDatepickerClose = () => {
|
||||
onChange(date);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Datetime
|
||||
inputProps={inputProps}
|
||||
timeFormat={isTimeChecked}
|
||||
className="cell-type-datepicker"
|
||||
dateFormat={dateFormat}
|
||||
value={date}
|
||||
onChange={dateChange}
|
||||
onClose={onDatepickerClose}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
53
frontend/src/Editor/Components/Table/GlobalFilter.jsx
Normal file
53
frontend/src/Editor/Components/Table/GlobalFilter.jsx
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import React from 'react';
|
||||
// Table Search
|
||||
export const GlobalFilter = ({
|
||||
preGlobalFilteredRows,
|
||||
globalFilter,
|
||||
useAsyncDebounce,
|
||||
setGlobalFilter,
|
||||
onComponentOptionChanged,
|
||||
component,
|
||||
serverSideSearch,
|
||||
onEvent,
|
||||
}) => {
|
||||
const count = preGlobalFilteredRows.length;
|
||||
const [value, setValue] = React.useState(globalFilter);
|
||||
const onChange = useAsyncDebounce((filterValue) => {
|
||||
setGlobalFilter(filterValue || undefined);
|
||||
}, 200);
|
||||
|
||||
const handleSearchTextChange = (text) => {
|
||||
setValue(text);
|
||||
onChange(text);
|
||||
|
||||
onComponentOptionChanged(component, 'searchText', text).then(() => {
|
||||
if (serverSideSearch === true) {
|
||||
onEvent('onSearch', { component, data: {} });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="ms-2 d-inline-block">
|
||||
Search:{' '}
|
||||
<input
|
||||
type="text"
|
||||
className="global-search-field"
|
||||
defaultValue={value || ''}
|
||||
onBlur={(e) => {
|
||||
handleSearchTextChange(e.target.value);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleSearchTextChange(e.target.value);
|
||||
}
|
||||
}}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={`${count} records`}
|
||||
style={{
|
||||
border: '0',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -7,7 +7,7 @@ export const Pagination = function Pagination({
|
|||
autoCanNextPage,
|
||||
autoPageCount,
|
||||
autoPageOptions,
|
||||
lastActivePageIndex
|
||||
lastActivePageIndex,
|
||||
}) {
|
||||
const [pageIndex, setPageIndex] = useState(lastActivePageIndex ?? 1);
|
||||
const [pageCount, setPageCount] = useState(autoPageCount);
|
||||
|
|
@ -17,13 +17,12 @@ export const Pagination = function Pagination({
|
|||
}, [autoPageCount]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
if(serverSide && lastActivePageIndex > 0) {
|
||||
setPageCount(lastActivePageIndex)
|
||||
} else if(serverSide || lastActivePageIndex === 0) {
|
||||
setPageIndex(1)
|
||||
if (serverSide && lastActivePageIndex > 0) {
|
||||
setPageCount(lastActivePageIndex);
|
||||
} else if (serverSide || lastActivePageIndex === 0) {
|
||||
setPageIndex(1);
|
||||
}
|
||||
}, [serverSide, lastActivePageIndex ])
|
||||
}, [serverSide, lastActivePageIndex]);
|
||||
|
||||
function gotoPage(page) {
|
||||
setPageIndex(page);
|
||||
|
|
@ -42,48 +41,35 @@ export const Pagination = function Pagination({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="pagination">
|
||||
{!serverSide
|
||||
&& <button className="btn btn-sm btn-light mx-2" onClick={() => gotoPage(1)}>
|
||||
{'<<'}
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
className="btn btn-light btn-sm"
|
||||
onClick={() => goToPreviousPage()}
|
||||
disabled={pageIndex === 1}
|
||||
>
|
||||
{'<'}
|
||||
</button>{' '}
|
||||
<small className="p-1 mx-2">
|
||||
|
||||
{serverSide &&
|
||||
<strong>
|
||||
{pageIndex}
|
||||
</strong>
|
||||
}
|
||||
{!serverSide &&
|
||||
<strong>
|
||||
{pageIndex} of {autoPageOptions.length}
|
||||
</strong>
|
||||
}
|
||||
</small>
|
||||
<button
|
||||
className="btn btn-light btn-sm"
|
||||
onClick={() => goToNextPage()}
|
||||
disabled={!autoCanNextPage && !serverSide}
|
||||
>
|
||||
{'>'}
|
||||
</button>{' '}
|
||||
|
||||
{!serverSide
|
||||
&& <button
|
||||
className="btn btn-light btn-sm mx-2"
|
||||
onClick={() => gotoPage(pageCount)}
|
||||
>
|
||||
{'>>'}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<div className="pagination">
|
||||
{!serverSide && (
|
||||
<button className="btn btn-sm btn-light mx-2" onClick={() => gotoPage(1)}>
|
||||
{'<<'}
|
||||
</button>
|
||||
)}
|
||||
<button className="btn btn-light btn-sm" onClick={() => goToPreviousPage()} disabled={pageIndex === 1}>
|
||||
{'<'}
|
||||
</button>{' '}
|
||||
<small className="p-1 mx-2">
|
||||
{serverSide && <strong>{pageIndex}</strong>}
|
||||
{!serverSide && (
|
||||
<strong>
|
||||
{pageIndex} of {autoPageOptions.length}
|
||||
</strong>
|
||||
)}
|
||||
</small>
|
||||
<button
|
||||
className="btn btn-light btn-sm"
|
||||
onClick={() => goToNextPage()}
|
||||
disabled={!autoCanNextPage && !serverSide}
|
||||
>
|
||||
{'>'}
|
||||
</button>{' '}
|
||||
{!serverSide && (
|
||||
<button className="btn btn-light btn-sm mx-2" onClick={() => gotoPage(pageCount)}>
|
||||
{'>>'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,20 +1,30 @@
|
|||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
export const Radio = ({ options, value, onChange, readOnly }) => {
|
||||
|
||||
value = value === undefined ? [] : value;
|
||||
options = Array.isArray(options) ? options : [];
|
||||
|
||||
return (
|
||||
<div className="radio row">
|
||||
<div>
|
||||
{options.map((option) =>
|
||||
<label class="form-check form-check-inline" onClick={() => { if(!readOnly) onChange(option.value); } }>
|
||||
<input class="form-check-input" type="radio" checked={option.value === value} disabled={readOnly && (option.value !== value)}/>
|
||||
<span class ="form-check-label">{option.name}</span>
|
||||
{options.map((option, index) => (
|
||||
<label
|
||||
key={index}
|
||||
className="form-check form-check-inline"
|
||||
onClick={() => {
|
||||
if (!readOnly) onChange(option.value);
|
||||
}}
|
||||
>
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="radio"
|
||||
checked={option.value === value}
|
||||
disabled={readOnly && option.value !== value}
|
||||
/>
|
||||
<span className="form-check-label">{option.name}</span>
|
||||
</label>
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,62 +1,69 @@
|
|||
import React, { useState } from 'react';
|
||||
|
||||
export const Tags = ({ value, onChange }) => {
|
||||
export const Tags = ({ value, onChange, readOnly }) => {
|
||||
const isValid = Array.isArray(value);
|
||||
if (!isValid) console.warn('[Tags]: value provided is not an array');
|
||||
value = isValid ? value : [];
|
||||
|
||||
value = value || [];
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
|
||||
const [ showForm, setShowForm ] = useState(false);
|
||||
|
||||
function addTag(text) {
|
||||
if(text !== '') {
|
||||
value.push(text);
|
||||
onChange(value);
|
||||
} else {
|
||||
setShowForm(false);
|
||||
}
|
||||
function addTag(text) {
|
||||
if (text !== '') {
|
||||
value.push(text);
|
||||
onChange(value);
|
||||
} else {
|
||||
setShowForm(false);
|
||||
}
|
||||
}
|
||||
|
||||
function removeTag(text) {
|
||||
const newValue = value.filter(tag => tag !== text);
|
||||
onChange(newValue);
|
||||
setShowForm(false);
|
||||
}
|
||||
function removeTag(text) {
|
||||
const newValue = value.filter((tag) => tag !== text);
|
||||
onChange(newValue);
|
||||
setShowForm(false);
|
||||
}
|
||||
|
||||
function handleFormKeyDown(e) {
|
||||
if(e.key === 'Enter') {
|
||||
addTag(e.target.value)
|
||||
}
|
||||
}
|
||||
|
||||
function renderTag(text) {
|
||||
return <span className="col-auto badge bg-blue-lt p-2 mx-1 tag mb-2">
|
||||
{text}
|
||||
<span className="badge badge-pill bg-red-lt remove-tag-button" onClick={() => removeTag(text)}>
|
||||
x
|
||||
</span>
|
||||
</span>;
|
||||
function handleFormKeyDown(e) {
|
||||
if (e.key === 'Enter') {
|
||||
addTag(e.target.value);
|
||||
}
|
||||
}
|
||||
|
||||
function renderTag(text) {
|
||||
return (
|
||||
<div className="tags row">
|
||||
{value.map((item) => {
|
||||
return renderTag(item)
|
||||
})}
|
||||
|
||||
{!showForm &&
|
||||
<span className="col-auto badge bg-green-lt mx-1 add-tag-button" onClick={() => setShowForm(true)}>{'+'}</span>
|
||||
}
|
||||
|
||||
{showForm &&
|
||||
<span className="col-auto badge bg-green-lt mx-1">
|
||||
<input
|
||||
type="text"
|
||||
autoFocus
|
||||
className="form-control-plaintext"
|
||||
onBlur={(e) => addTag(e.target.value)}
|
||||
onKeyDown={handleFormKeyDown}
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<span className="col-auto badge bg-blue-lt p-2 mx-1 tag mb-2">
|
||||
{text}
|
||||
{!readOnly && (
|
||||
<span className="badge badge-pill bg-red-lt remove-tag-button" onClick={() => removeTag(text)}>
|
||||
x
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="tags row">
|
||||
{value.map((item) => {
|
||||
return renderTag(item);
|
||||
})}
|
||||
|
||||
{!showForm && !readOnly && (
|
||||
<span className="col-auto badge bg-green-lt mx-1 add-tag-button" onClick={() => setShowForm(true)}>
|
||||
{'+'}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{showForm && (
|
||||
<span className="col-auto badge bg-green-lt mx-1">
|
||||
<input
|
||||
type="text"
|
||||
autoFocus
|
||||
className="form-control-plaintext"
|
||||
onBlur={(e) => addTag(e.target.value)}
|
||||
onKeyDown={handleFormKeyDown}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,24 +1,26 @@
|
|||
import React, { useState } from 'react';
|
||||
|
||||
export const Toggle = ({readOnly, value, onChange, activeColor, options }) => {
|
||||
const [on, setOn] = useState(() => value)
|
||||
export const Toggle = ({ readOnly, value, onChange, activeColor }) => {
|
||||
const [on, setOn] = useState(() => value);
|
||||
|
||||
const toggle = () => {
|
||||
setOn((prev) => !prev)
|
||||
onChange(!on)
|
||||
}
|
||||
setOn((prev) => !prev);
|
||||
onChange(!on);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="radio row g-0">
|
||||
<label className="form-check form-switch form-check-inline">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
checked={on}
|
||||
style={ on ? { backgroundColor: activeColor} : {}}
|
||||
onClick={() => {if(!readOnly) toggle()}}
|
||||
/>
|
||||
</label>
|
||||
<label className="form-check form-switch form-check-inline">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
checked={on}
|
||||
style={on ? { backgroundColor: activeColor } : {}}
|
||||
onClick={() => {
|
||||
if (!readOnly) toggle();
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
|
||||
import DOMPurify from 'dompurify';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
|
||||
export const Text = function Text({
|
||||
id, width, height, component, onComponentClick, currentState
|
||||
}) {
|
||||
export const Text = function Text({ id, width, height, component, onComponentClick, currentState }) {
|
||||
const text = component.definition.properties.text.value;
|
||||
const color = component.definition.styles.textColor.value;
|
||||
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
|
||||
const disabledState = component.definition.styles?.disabledState?.value ?? false;
|
||||
|
||||
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
const parsedDisabledState =
|
||||
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
|
||||
const [loadingState, setLoadingState] = useState(false);
|
||||
|
||||
|
|
@ -21,6 +19,7 @@ export const Text = function Text({
|
|||
const newState = resolveReferences(loadingStateProperty.value, currentState, false);
|
||||
setLoadingState(newState);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentState]);
|
||||
|
||||
let data = text;
|
||||
|
|
@ -37,21 +36,31 @@ export const Text = function Text({
|
|||
}
|
||||
|
||||
let parsedWidgetVisibility = widgetVisibility;
|
||||
|
||||
|
||||
try {
|
||||
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
|
||||
} catch (err) { console.log(err); }
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
const computedStyles = {
|
||||
color,
|
||||
width,
|
||||
height,
|
||||
display: parsedWidgetVisibility ? 'flex' : 'none',
|
||||
alignItems: 'center'
|
||||
alignItems: 'center',
|
||||
};
|
||||
|
||||
return (
|
||||
<div data-disabled={parsedDisabledState} className="text-widget" style={computedStyles} onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}>
|
||||
<div
|
||||
data-disabled={parsedDisabledState}
|
||||
className="text-widget"
|
||||
style={computedStyles}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component);
|
||||
}}
|
||||
>
|
||||
{!loadingState && <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(data) }} />}
|
||||
{loadingState === true && (
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -8,9 +8,8 @@ export const TextArea = function TextArea({
|
|||
component,
|
||||
onComponentClick,
|
||||
currentState,
|
||||
onComponentOptionChanged
|
||||
onComponentOptionChanged,
|
||||
}) {
|
||||
|
||||
const value = component.definition.properties.value ? component.definition.properties.value.value : '';
|
||||
const [text, setText] = useState(value);
|
||||
|
||||
|
|
@ -23,24 +22,31 @@ export const TextArea = function TextArea({
|
|||
useEffect(() => {
|
||||
setText(newText);
|
||||
onComponentOptionChanged(component, 'value', newText);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [newText]);
|
||||
|
||||
|
||||
const placeholder = component.definition.properties.placeholder.value;
|
||||
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
|
||||
const disabledState = component.definition.styles?.disabledState?.value ?? false;
|
||||
|
||||
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
const parsedDisabledState =
|
||||
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
|
||||
let parsedWidgetVisibility = widgetVisibility;
|
||||
|
||||
|
||||
try {
|
||||
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
|
||||
} catch (err) { console.log(err); }
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
return (
|
||||
<textarea
|
||||
disabled={parsedDisabledState}
|
||||
onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component);
|
||||
}}
|
||||
onChange={(e) => {
|
||||
setText(e.target.value);
|
||||
onComponentOptionChanged(component, 'value', e.target.value);
|
||||
|
|
@ -48,7 +54,7 @@ export const TextArea = function TextArea({
|
|||
type="text"
|
||||
className="form-control"
|
||||
placeholder={placeholder}
|
||||
style={{ width, height, display:parsedWidgetVisibility ? '' : 'none' }}
|
||||
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
value={text}
|
||||
></textarea>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ export const TextInput = function TextInput({
|
|||
useEffect(() => {
|
||||
setText(newText);
|
||||
onComponentOptionChanged(component, 'value', newText);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [newText]);
|
||||
|
||||
const validationData = validateWidget({
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@ import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
|
|||
|
||||
class Switch extends React.Component {
|
||||
render() {
|
||||
const { on, onClick, onChange, disabledState } = this.props;
|
||||
const { on, onClick, onChange, disabledState, color } = this.props;
|
||||
|
||||
return (
|
||||
<label className="form-check form-switch form-check-inline">
|
||||
<label className="form-switch form-check-inline">
|
||||
<input
|
||||
style={{ backgroundColor: on ? `${color}` : 'white' }}
|
||||
disabled={disabledState}
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
|
|
@ -32,6 +34,8 @@ export const ToggleSwitch = ({
|
|||
const [on, setOn] = React.useState(false);
|
||||
const label = component.definition.properties.label.value;
|
||||
const textColorProperty = component.definition.styles.textColor;
|
||||
const toggleSwitchColorProperty = component.definition.styles.toggleSwitchColor;
|
||||
const toggleSwitchColor = toggleSwitchColorProperty ? toggleSwitchColorProperty.value : '#3c92dc';
|
||||
const textColor = textColorProperty ? textColorProperty.value : '#000';
|
||||
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
|
||||
const disabledState = component.definition.styles?.disabledState?.value ?? false;
|
||||
|
|
@ -57,18 +61,24 @@ export const ToggleSwitch = ({
|
|||
|
||||
return (
|
||||
<div
|
||||
className="row py-1"
|
||||
className="row"
|
||||
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component);
|
||||
}}
|
||||
>
|
||||
<span className="form-check-label form-check-label col-auto" style={{ color: textColor }}>
|
||||
<span className="form-check-label form-check-label col-auto my-auto" style={{ color: textColor }}>
|
||||
{label}
|
||||
</span>
|
||||
<div className="col">
|
||||
<Switch disabledState={parsedDisabledState} on={on} onClick={toggle} onChange={toggleValue} />
|
||||
<div className="col px-1 py-0 my-auto">
|
||||
<Switch
|
||||
disabledState={parsedDisabledState}
|
||||
on={on}
|
||||
onClick={toggle}
|
||||
onChange={toggleValue}
|
||||
color={toggleSwitchColor}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -18,10 +18,12 @@ export const componentTypes = [
|
|||
showDownloadButton: { type: 'toggle', displayName: 'Show download button' },
|
||||
showFilterButton: { type: 'toggle', displayName: 'Show filter button' },
|
||||
showBulkUpdateActions: { type: 'toggle', displayName: 'Show bulk update actions' },
|
||||
showBulkSelector: { type: 'toggle', displayName: 'Bulk selection' },
|
||||
highlightSelectedRow: { type: 'toggle', displayName: 'Highlight selected row' },
|
||||
},
|
||||
others: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop? ' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile?' },
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop ' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
|
||||
},
|
||||
defaultSize: {
|
||||
width: 810,
|
||||
|
|
@ -35,15 +37,19 @@ export const componentTypes = [
|
|||
},
|
||||
styles: {
|
||||
textColor: { type: 'color', displayName: 'Text Color' },
|
||||
tableType: { type: 'select', displayName: 'Table type', options: [
|
||||
{ name: 'Bordered', value: '' },
|
||||
{ name: 'Borderless', value: 'table-borderless' },
|
||||
{ name: 'Classic', value: 'table-classic' },
|
||||
{ name: 'Striped', value: 'table-striped' },
|
||||
{ name: 'Striped & bordered', value: 'table-striped table-bordered' }
|
||||
] },
|
||||
visibility: {type: 'code', displayName: 'Visibility'},
|
||||
disabledState: {type: 'code', displayName: 'Disable'}
|
||||
tableType: {
|
||||
type: 'select',
|
||||
displayName: 'Table type',
|
||||
options: [
|
||||
{ name: 'Bordered', value: '' },
|
||||
{ name: 'Borderless', value: 'table-borderless' },
|
||||
{ name: 'Classic', value: 'table-classic' },
|
||||
{ name: 'Striped', value: 'table-striped' },
|
||||
{ name: 'Striped & bordered', value: 'table-striped table-bordered' },
|
||||
],
|
||||
},
|
||||
visibility: { type: 'code', displayName: 'Visibility' },
|
||||
disabledState: { type: 'code', displayName: 'Disable' },
|
||||
},
|
||||
exposedVariables: {
|
||||
selectedRow: {},
|
||||
|
|
@ -51,6 +57,7 @@ export const componentTypes = [
|
|||
dataUpdates: [],
|
||||
pageIndex: 0,
|
||||
searchText: '',
|
||||
selectedRows: [],
|
||||
},
|
||||
definition: {
|
||||
others: {
|
||||
|
|
@ -78,12 +85,14 @@ export const componentTypes = [
|
|||
],
|
||||
},
|
||||
showBulkUpdateActions: { value: true },
|
||||
showBulkSelector: { value: false },
|
||||
highlightSelectedRow: { value: false },
|
||||
},
|
||||
events: [],
|
||||
styles: {
|
||||
textColor: { value: '' },
|
||||
visibility: {value: '{{true}}'},
|
||||
disabledState: {value: '{{false}}'}
|
||||
textColor: { value: undefined },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -97,8 +106,8 @@ export const componentTypes = [
|
|||
height: 30,
|
||||
},
|
||||
others: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop? ' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile?' },
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
|
||||
},
|
||||
properties: {
|
||||
text: { type: 'code', displayName: 'Button Text' },
|
||||
|
|
@ -110,8 +119,8 @@ export const componentTypes = [
|
|||
styles: {
|
||||
backgroundColor: { type: 'color', displayName: 'Background color' },
|
||||
textColor: { type: 'color', displayName: 'Text color' },
|
||||
visibility: {type: 'code', displayName: 'Visibility'},
|
||||
disabledState: {type: 'code', displayName: 'Disable'}
|
||||
visibility: { type: 'code', displayName: 'Visibility' },
|
||||
disabledState: { type: 'code', displayName: 'Disable' },
|
||||
},
|
||||
exposedVariables: {},
|
||||
definition: {
|
||||
|
|
@ -128,8 +137,8 @@ export const componentTypes = [
|
|||
styles: {
|
||||
backgroundColor: { value: '#3c92dc' },
|
||||
textColor: { value: '#fff' },
|
||||
visibility: {value: '{{true}}'},
|
||||
disabledState: {value: '{{false}}'}
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -143,8 +152,8 @@ export const componentTypes = [
|
|||
height: 400,
|
||||
},
|
||||
others: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop? ' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile?' },
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
|
||||
},
|
||||
properties: {
|
||||
title: { type: 'string', displayName: 'Title' },
|
||||
|
|
@ -164,8 +173,8 @@ export const componentTypes = [
|
|||
},
|
||||
events: {},
|
||||
styles: {
|
||||
visibility: {type: 'code', displayName: 'Visibility'},
|
||||
disabledState: {type: 'code', displayName: 'Disable'}
|
||||
visibility: { type: 'code', displayName: 'Visibility' },
|
||||
disabledState: { type: 'code', displayName: 'Disable' },
|
||||
},
|
||||
exposedVariables: {
|
||||
show: null,
|
||||
|
|
@ -183,16 +192,16 @@ export const componentTypes = [
|
|||
type: { value: `line` },
|
||||
data: {
|
||||
value: `[
|
||||
{ "x": 100, "y": "Jan"},
|
||||
{ "x": 80, "y": "Feb"},
|
||||
{ "x": 40, "y": "Mar"}
|
||||
{ "x": "Jan", "y": 100},
|
||||
{ "x": "Feb", "y": 80},
|
||||
{ "x": "Mar", "y": 40}
|
||||
]`,
|
||||
},
|
||||
},
|
||||
events: [],
|
||||
styles: {
|
||||
visibility: {value: '{{true}}'},
|
||||
disabledState: {value: '{{false}}'}
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -206,8 +215,8 @@ export const componentTypes = [
|
|||
height: 400,
|
||||
},
|
||||
others: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop? ' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile?' },
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
|
||||
},
|
||||
properties: {
|
||||
title: { type: 'string', displayName: 'Title' },
|
||||
|
|
@ -223,7 +232,7 @@ export const componentTypes = [
|
|||
},
|
||||
events: {},
|
||||
styles: {
|
||||
disabledState: {type: 'code', displayName: 'Disable'}
|
||||
disabledState: { type: 'code', displayName: 'Disable' },
|
||||
},
|
||||
exposedVariables: {
|
||||
show: null,
|
||||
|
|
@ -239,7 +248,7 @@ export const componentTypes = [
|
|||
},
|
||||
events: [],
|
||||
styles: {
|
||||
disabledState: {value: '{{false}}'}
|
||||
disabledState: { value: '{{false}}' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -249,37 +258,37 @@ export const componentTypes = [
|
|||
description: 'Text field for forms',
|
||||
component: 'TextInput',
|
||||
defaultSize: {
|
||||
width: 200,
|
||||
width: 210,
|
||||
height: 30,
|
||||
},
|
||||
others: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop? ' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile?' },
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
|
||||
},
|
||||
properties: {
|
||||
value: { type: 'code', displayName: 'Default value' },
|
||||
placeholder: { type: 'code', displayName: 'Placeholder' }
|
||||
placeholder: { type: 'code', displayName: 'Placeholder' },
|
||||
},
|
||||
validation: {
|
||||
regex: { type: 'code', displayName: 'Regex' },
|
||||
minLength: { type: 'code', displayName: 'Min length' },
|
||||
maxLength: { type: 'code', displayName: 'Max length' },
|
||||
customRule: { type: 'code', displayName: 'Custom validation' }
|
||||
customRule: { type: 'code', displayName: 'Custom validation' },
|
||||
},
|
||||
events: {},
|
||||
styles: {
|
||||
visibility: {type: 'code', displayName: 'Visibility'},
|
||||
disabledState: {type: 'code', displayName: 'Disable'}
|
||||
visibility: { type: 'code', displayName: 'Visibility' },
|
||||
disabledState: { type: 'code', displayName: 'Disable' },
|
||||
},
|
||||
exposedVariables: {
|
||||
value: '',
|
||||
value: '',
|
||||
},
|
||||
definition: {
|
||||
validation: {
|
||||
regex: { value: '' },
|
||||
minLength: { value: null },
|
||||
maxLength: { value: null },
|
||||
customRule: { value: null }
|
||||
customRule: { value: null },
|
||||
},
|
||||
others: {
|
||||
showOnDesktop: { value: true },
|
||||
|
|
@ -287,12 +296,12 @@ export const componentTypes = [
|
|||
},
|
||||
properties: {
|
||||
value: { value: '' },
|
||||
placeholder: { value: 'Placeholder text' }
|
||||
placeholder: { value: 'Placeholder text' },
|
||||
},
|
||||
events: [],
|
||||
styles: {
|
||||
visibility: {value: '{{true}}'},
|
||||
disabledState: {value: '{{false}}'}
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -302,21 +311,21 @@ export const componentTypes = [
|
|||
description: 'Number field for forms',
|
||||
component: 'NumberInput',
|
||||
defaultSize: {
|
||||
width: 200,
|
||||
width: 210,
|
||||
height: 30,
|
||||
},
|
||||
others: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop? ' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile?' },
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
|
||||
},
|
||||
properties: {
|
||||
value: { type: 'code', displayName: 'Default value' },
|
||||
placeholder: { type: 'code', displayName: 'Placeholder' }
|
||||
placeholder: { type: 'code', displayName: 'Placeholder' },
|
||||
},
|
||||
events: {},
|
||||
styles: {
|
||||
visibility: {type: 'code', displayName: 'Visibility'},
|
||||
disabledState: {type: 'code', displayName: 'Disable'}
|
||||
visibility: { type: 'code', displayName: 'Visibility' },
|
||||
disabledState: { type: 'code', displayName: 'Disable' },
|
||||
},
|
||||
exposedVariables: {
|
||||
value: 0,
|
||||
|
|
@ -328,12 +337,12 @@ export const componentTypes = [
|
|||
},
|
||||
properties: {
|
||||
value: { value: '' },
|
||||
placeholder: { value: '0' }
|
||||
placeholder: { value: '0' },
|
||||
},
|
||||
events: [],
|
||||
styles: {
|
||||
visibility: {value: '{{true}}'},
|
||||
disabledState: {value: '{{false}}'}
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -347,11 +356,11 @@ export const componentTypes = [
|
|||
height: 30,
|
||||
},
|
||||
validation: {
|
||||
customRule: { type: 'code', displayName: 'Custom validation' }
|
||||
customRule: { type: 'code', displayName: 'Custom validation' },
|
||||
},
|
||||
others: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop? ' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile?' },
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
|
||||
},
|
||||
properties: {
|
||||
defaultValue: { type: 'code', displayName: 'Default value' },
|
||||
|
|
@ -361,8 +370,8 @@ export const componentTypes = [
|
|||
},
|
||||
events: {},
|
||||
styles: {
|
||||
visibility: {type: 'code', displayName: 'Visibility'},
|
||||
disabledState: {type: 'code', displayName: 'Disable'}
|
||||
visibility: { type: 'code', displayName: 'Visibility' },
|
||||
disabledState: { type: 'code', displayName: 'Disable' },
|
||||
},
|
||||
exposedVariables: {
|
||||
value: '',
|
||||
|
|
@ -373,7 +382,7 @@ export const componentTypes = [
|
|||
showOnMobile: { value: false },
|
||||
},
|
||||
validation: {
|
||||
customRule: { value: null }
|
||||
customRule: { value: null },
|
||||
},
|
||||
properties: {
|
||||
defaultValue: { value: '' },
|
||||
|
|
@ -383,8 +392,8 @@ export const componentTypes = [
|
|||
},
|
||||
events: [],
|
||||
styles: {
|
||||
visibility: {value: '{{true}}'},
|
||||
disabledState: {value: '{{false}}'}
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -394,12 +403,12 @@ export const componentTypes = [
|
|||
description: 'A single checkbox',
|
||||
component: 'Checkbox',
|
||||
defaultSize: {
|
||||
width: 200,
|
||||
width: 150,
|
||||
height: 30,
|
||||
},
|
||||
others: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop? ' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile?' },
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
|
||||
},
|
||||
properties: {
|
||||
label: { type: 'code', displayName: 'Label' },
|
||||
|
|
@ -410,8 +419,8 @@ export const componentTypes = [
|
|||
},
|
||||
styles: {
|
||||
textColor: { type: 'color', displayName: 'Text Color' },
|
||||
visibility: {type: 'code', displayName: 'Visibility'},
|
||||
disabledState: {type: 'code', displayName: 'Disable'}
|
||||
visibility: { type: 'code', displayName: 'Visibility' },
|
||||
disabledState: { type: 'code', displayName: 'Disable' },
|
||||
},
|
||||
exposedVariables: {},
|
||||
definition: {
|
||||
|
|
@ -425,8 +434,8 @@ export const componentTypes = [
|
|||
events: [],
|
||||
styles: {
|
||||
textColor: { value: '#000' },
|
||||
visibility: {value: '{{true}}'},
|
||||
disabledState: {value: '{{false}}'}
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -436,12 +445,12 @@ export const componentTypes = [
|
|||
description: 'Radio buttons',
|
||||
component: 'RadioButton',
|
||||
defaultSize: {
|
||||
width: 200,
|
||||
width: 210,
|
||||
height: 30,
|
||||
},
|
||||
others: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop? ' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile?' },
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
|
||||
},
|
||||
properties: {
|
||||
label: { type: 'code', displayName: 'Label' },
|
||||
|
|
@ -454,8 +463,8 @@ export const componentTypes = [
|
|||
},
|
||||
styles: {
|
||||
textColor: { type: 'color', displayName: 'Text Color' },
|
||||
visibility: {type: 'code', displayName: 'Visibility'},
|
||||
disabledState: {type: 'code', displayName: 'Disable'}
|
||||
visibility: { type: 'code', displayName: 'Visibility' },
|
||||
disabledState: { type: 'code', displayName: 'Disable' },
|
||||
},
|
||||
exposedVariables: {},
|
||||
definition: {
|
||||
|
|
@ -473,8 +482,8 @@ export const componentTypes = [
|
|||
events: [],
|
||||
styles: {
|
||||
textColor: { value: '#000' },
|
||||
visibility: {value: '{{true}}'},
|
||||
disabledState: {value: '{{false}}'}
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -484,12 +493,12 @@ export const componentTypes = [
|
|||
description: 'Toggle Switch',
|
||||
component: 'ToggleSwitch',
|
||||
defaultSize: {
|
||||
width: 130,
|
||||
width: 150,
|
||||
height: 30,
|
||||
},
|
||||
others: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop? ' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile?' },
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
|
||||
},
|
||||
properties: {
|
||||
label: { type: 'code', displayName: 'Label' },
|
||||
|
|
@ -499,8 +508,9 @@ export const componentTypes = [
|
|||
},
|
||||
styles: {
|
||||
textColor: { type: 'color', displayName: 'Text Color' },
|
||||
visibility: {type: 'code', displayName: 'Visibility'},
|
||||
disabledState: {type: 'code', displayName: 'Disable'}
|
||||
toggleSwitchColor: { type: 'color', displayName: 'Toggle Switch Color' },
|
||||
visibility: { type: 'code', displayName: 'Visibility' },
|
||||
disabledState: { type: 'code', displayName: 'Disable' },
|
||||
},
|
||||
exposedVariables: {},
|
||||
definition: {
|
||||
|
|
@ -514,8 +524,9 @@ export const componentTypes = [
|
|||
events: [],
|
||||
styles: {
|
||||
textColor: { value: '#000' },
|
||||
visibility: {value: '{{true}}'},
|
||||
disabledState: {value: '{{false}}'}
|
||||
toggleSwitchColor: { value: '#3c92dc' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -525,12 +536,12 @@ export const componentTypes = [
|
|||
description: 'Text area form field',
|
||||
component: 'TextArea',
|
||||
defaultSize: {
|
||||
width: 250,
|
||||
width: 240,
|
||||
height: 100,
|
||||
},
|
||||
others: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop? ' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile?' },
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
|
||||
},
|
||||
properties: {
|
||||
value: { type: 'code', displayName: 'Default value' },
|
||||
|
|
@ -538,8 +549,8 @@ export const componentTypes = [
|
|||
},
|
||||
events: {},
|
||||
styles: {
|
||||
visibility: {type: 'code', displayName: 'Visibility'},
|
||||
disabledState: {type: 'code', displayName: 'Disable'}
|
||||
visibility: { type: 'code', displayName: 'Visibility' },
|
||||
disabledState: { type: 'code', displayName: 'Disable' },
|
||||
},
|
||||
exposedVariables: {
|
||||
value: {},
|
||||
|
|
@ -555,8 +566,8 @@ export const componentTypes = [
|
|||
},
|
||||
events: [],
|
||||
styles: {
|
||||
visibility: {value: '{{true}}'},
|
||||
disabledState: {value: '{{false}}'}
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -567,19 +578,19 @@ export const componentTypes = [
|
|||
component: 'DaterangePicker',
|
||||
defaultSize: {
|
||||
width: 300,
|
||||
height: 32,
|
||||
height: 30,
|
||||
},
|
||||
others: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop? ' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile?' },
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
|
||||
},
|
||||
properties: {
|
||||
format: { type: 'code', displayName: 'Format' },
|
||||
},
|
||||
events: {},
|
||||
styles: {
|
||||
visibility: {type: 'code', displayName: 'Visibility'},
|
||||
disabledState: {type: 'code', displayName: 'Disable'}
|
||||
visibility: { type: 'code', displayName: 'Visibility' },
|
||||
disabledState: { type: 'code', displayName: 'Disable' },
|
||||
},
|
||||
exposedVariables: {
|
||||
endDate: {},
|
||||
|
|
@ -595,8 +606,8 @@ export const componentTypes = [
|
|||
},
|
||||
events: [],
|
||||
styles: {
|
||||
visibility: {value: '{{true}}'},
|
||||
disabledState: {value: '{{false}}'}
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -606,22 +617,22 @@ export const componentTypes = [
|
|||
description: 'Display markdown or HTML',
|
||||
component: 'Text',
|
||||
others: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop? ' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile?' },
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
|
||||
},
|
||||
properties: {
|
||||
text: { type: 'code', displayName: 'Text' },
|
||||
loadingState: { type: 'code', displayName: 'Show loading state' },
|
||||
},
|
||||
defaultSize: {
|
||||
width: 200,
|
||||
width: 120,
|
||||
height: 30,
|
||||
},
|
||||
events: [],
|
||||
styles: {
|
||||
textColor: { type: 'color', displayName: 'Text Color' },
|
||||
visibility: {type: 'code', displayName: 'Visibility'},
|
||||
disabledState: {type: 'code', displayName: 'Disable'}
|
||||
visibility: { type: 'code', displayName: 'Visibility' },
|
||||
disabledState: { type: 'code', displayName: 'Disable' },
|
||||
},
|
||||
exposedVariables: {},
|
||||
definition: {
|
||||
|
|
@ -637,8 +648,8 @@ export const componentTypes = [
|
|||
events: [],
|
||||
styles: {
|
||||
textColor: { value: '#000' },
|
||||
visibility: {value: '{{true}}'},
|
||||
disabledState: {value: '{{false}}'}
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -647,13 +658,13 @@ export const componentTypes = [
|
|||
displayName: 'Image',
|
||||
description: 'Display an Image',
|
||||
defaultSize: {
|
||||
width: 200,
|
||||
height: 200,
|
||||
width: 210,
|
||||
height: 210,
|
||||
},
|
||||
component: 'Image',
|
||||
others: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop? ' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile?' },
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
|
||||
},
|
||||
properties: {
|
||||
source: { type: 'code', displayName: 'URL' },
|
||||
|
|
@ -662,8 +673,8 @@ export const componentTypes = [
|
|||
onClick: { displayName: 'On click' },
|
||||
},
|
||||
styles: {
|
||||
visibility: {type: 'code', displayName: 'Visibility'},
|
||||
disabledState: {type: 'code', displayName: 'Disable'}
|
||||
visibility: { type: 'code', displayName: 'Visibility' },
|
||||
disabledState: { type: 'code', displayName: 'Disable' },
|
||||
},
|
||||
exposedVariables: {},
|
||||
definition: {
|
||||
|
|
@ -677,8 +688,8 @@ export const componentTypes = [
|
|||
},
|
||||
events: [],
|
||||
styles: {
|
||||
visibility: {value: '{{true}}'},
|
||||
disabledState: {value: '{{false}}'}
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -687,21 +698,20 @@ export const componentTypes = [
|
|||
displayName: 'Container',
|
||||
description: 'Wrapper for multiple components',
|
||||
defaultSize: {
|
||||
width: 200,
|
||||
width: 210,
|
||||
height: 200,
|
||||
},
|
||||
component: 'Container',
|
||||
others: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop? ' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile?' },
|
||||
},
|
||||
properties: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
|
||||
},
|
||||
properties: {},
|
||||
events: {},
|
||||
styles: {
|
||||
backgroundColor: { type: 'color' },
|
||||
visibility: {type: 'code', displayName: 'Visibility'},
|
||||
disabledState: {type: 'code', displayName: 'Disable'}
|
||||
visibility: { type: 'code', displayName: 'Visibility' },
|
||||
disabledState: { type: 'code', displayName: 'Disable' },
|
||||
},
|
||||
exposedVariables: {},
|
||||
definition: {
|
||||
|
|
@ -715,8 +725,8 @@ export const componentTypes = [
|
|||
events: [],
|
||||
styles: {
|
||||
backgroundColor: { value: '#fff' },
|
||||
visibility: {value: '{{true}}'},
|
||||
disabledState: {value:'{{false}}'}
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -725,16 +735,16 @@ export const componentTypes = [
|
|||
displayName: 'Dropdown',
|
||||
description: 'Select one value from options',
|
||||
defaultSize: {
|
||||
width: 210,
|
||||
width: 240,
|
||||
height: 30,
|
||||
},
|
||||
component: 'DropDown',
|
||||
others: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop? ' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile?' },
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
|
||||
},
|
||||
validation: {
|
||||
customRule: { type: 'code', displayName: 'Custom validation' }
|
||||
customRule: { type: 'code', displayName: 'Custom validation' },
|
||||
},
|
||||
properties: {
|
||||
label: { type: 'code', displayName: 'Label' },
|
||||
|
|
@ -746,8 +756,8 @@ export const componentTypes = [
|
|||
onSelect: { displayName: 'On select' },
|
||||
},
|
||||
styles: {
|
||||
visibility: {type: 'code', displayName: 'Visibility'},
|
||||
disabledState: {type: 'code', displayName: 'Disable'}
|
||||
visibility: { type: 'code', displayName: 'Visibility' },
|
||||
disabledState: { type: 'code', displayName: 'Disable' },
|
||||
},
|
||||
exposedVariables: {
|
||||
value: null,
|
||||
|
|
@ -758,7 +768,7 @@ export const componentTypes = [
|
|||
showOnMobile: { value: false },
|
||||
},
|
||||
validation: {
|
||||
customRule: { value: null }
|
||||
customRule: { value: null },
|
||||
},
|
||||
properties: {
|
||||
label: { value: 'Select' },
|
||||
|
|
@ -769,8 +779,8 @@ export const componentTypes = [
|
|||
},
|
||||
events: [],
|
||||
styles: {
|
||||
visibility: {value: '{{true}}'},
|
||||
disabledState: {value: '{{false}}'}
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -779,13 +789,13 @@ export const componentTypes = [
|
|||
displayName: 'Multiselect',
|
||||
description: 'Select multiple values from options',
|
||||
defaultSize: {
|
||||
width: 210,
|
||||
width: 240,
|
||||
height: 30,
|
||||
},
|
||||
component: 'Multiselect',
|
||||
others: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop? ' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile?' },
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
|
||||
},
|
||||
properties: {
|
||||
label: { type: 'code', displayName: 'Label' },
|
||||
|
|
@ -797,8 +807,8 @@ export const componentTypes = [
|
|||
onSelect: { displayName: 'On select' },
|
||||
},
|
||||
styles: {
|
||||
visibility: {type: 'code', displayName: 'Visibility'},
|
||||
disabledState: {type: 'code', displayName: 'Disable'}
|
||||
visibility: { type: 'code', displayName: 'Visibility' },
|
||||
disabledState: { type: 'code', displayName: 'Disable' },
|
||||
},
|
||||
exposedVariables: {
|
||||
values: {},
|
||||
|
|
@ -817,8 +827,8 @@ export const componentTypes = [
|
|||
},
|
||||
events: [],
|
||||
styles: {
|
||||
visibility: {value: '{{true}}'},
|
||||
disabledState: {value: '{{false}}'}
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -832,16 +842,16 @@ export const componentTypes = [
|
|||
height: 210,
|
||||
},
|
||||
others: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop? ' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile?' },
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
|
||||
},
|
||||
properties: {
|
||||
placeholder: { type: 'code', displayName: 'Placeholder' },
|
||||
},
|
||||
events: {},
|
||||
styles: {
|
||||
visibility: {type: 'code', displayName: 'Visibility'},
|
||||
disabledState: {type: 'code', displayName: 'Disable'}
|
||||
visibility: { type: 'code', displayName: 'Visibility' },
|
||||
disabledState: { type: 'code', displayName: 'Disable' },
|
||||
},
|
||||
exposedVariables: {
|
||||
value: {},
|
||||
|
|
@ -856,8 +866,8 @@ export const componentTypes = [
|
|||
},
|
||||
events: [],
|
||||
styles: {
|
||||
visibility: {value: '{{true}}'},
|
||||
disabledState: {value: '{{false}}'}
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -867,12 +877,12 @@ export const componentTypes = [
|
|||
description: 'Display Google Maps',
|
||||
component: 'Map',
|
||||
defaultSize: {
|
||||
width: 400,
|
||||
height: 400,
|
||||
width: 420,
|
||||
height: 420,
|
||||
},
|
||||
others: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop? ' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile?' },
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
|
||||
},
|
||||
properties: {
|
||||
initialLocation: {
|
||||
|
|
@ -903,8 +913,8 @@ export const componentTypes = [
|
|||
onMarkerClick: { displayName: 'On marker click' },
|
||||
},
|
||||
styles: {
|
||||
visibility: {type: 'code', displayName: 'Visibility'},
|
||||
disabledState: {type: 'code', displayName: 'Disable'}
|
||||
visibility: { type: 'code', displayName: 'Visibility' },
|
||||
disabledState: { type: 'code', displayName: 'Disable' },
|
||||
},
|
||||
exposedVariables: {
|
||||
center: {},
|
||||
|
|
@ -925,8 +935,8 @@ export const componentTypes = [
|
|||
addNewMarkers: { value: '{{false}}' },
|
||||
events: [],
|
||||
styles: {
|
||||
visibility: {value: '{{true}}'},
|
||||
disabledState: {value: '{{false}}'}
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -940,16 +950,16 @@ export const componentTypes = [
|
|||
height: 300,
|
||||
},
|
||||
others: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop? ' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile?' },
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
|
||||
},
|
||||
properties: {},
|
||||
events: {
|
||||
onDetect: { displayName: 'On detect' },
|
||||
},
|
||||
styles: {
|
||||
visibility: {type: 'code', displayName: 'Visibility'},
|
||||
disabledState: {type: 'code', displayName: 'Disable'}
|
||||
visibility: { type: 'code', displayName: 'Visibility' },
|
||||
disabledState: { type: 'code', displayName: 'Disable' },
|
||||
},
|
||||
exposedVariables: {
|
||||
lastDetectedValue: '',
|
||||
|
|
@ -962,8 +972,8 @@ export const componentTypes = [
|
|||
properties: {},
|
||||
events: [],
|
||||
styles: {
|
||||
visibility: {value: '{{true}}'},
|
||||
disabledState: {value: '{{false}}'}
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -973,12 +983,12 @@ export const componentTypes = [
|
|||
description: 'Star rating',
|
||||
component: 'StarRating',
|
||||
defaultSize: {
|
||||
width: 220,
|
||||
width: 240,
|
||||
height: 30,
|
||||
},
|
||||
others: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop? ' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile?' },
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
|
||||
},
|
||||
properties: {
|
||||
label: { type: 'code', displayName: 'Label' },
|
||||
|
|
@ -992,8 +1002,9 @@ export const componentTypes = [
|
|||
},
|
||||
styles: {
|
||||
textColor: { type: 'color', displayName: 'Star Color' },
|
||||
visibility: {type: 'code', displayName: 'Visibility'},
|
||||
disabledState: {type: 'code', displayName: 'Disable'}
|
||||
labelColor: { type: 'color', displayName: 'Label Color' },
|
||||
visibility: { type: 'code', displayName: 'Visibility' },
|
||||
disabledState: { type: 'code', displayName: 'Disable' },
|
||||
},
|
||||
exposedVariables: {
|
||||
value: 0,
|
||||
|
|
@ -1014,8 +1025,9 @@ export const componentTypes = [
|
|||
events: [],
|
||||
styles: {
|
||||
textColor: { value: '#ffb400' },
|
||||
visibility: {value: '{{true}}'},
|
||||
disabledState: {value: '{{false}}'}
|
||||
labelColor: { value: '#333' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,39 +1,35 @@
|
|||
import React from 'react';
|
||||
|
||||
export const ConfigHandle = function ConfigHandle({
|
||||
id,
|
||||
component,
|
||||
configHandleClicked,
|
||||
dragRef,
|
||||
removeComponent
|
||||
}) {
|
||||
|
||||
return <div className="config-handle" ref={dragRef}>
|
||||
<span
|
||||
style={{cursor: 'move'}}
|
||||
onClick={(e) => { e.preventDefault(); e.stopPropagation(); configHandleClicked(id, component) }}
|
||||
className="badge badge bg-azure-lt"
|
||||
role="button"
|
||||
>
|
||||
<img
|
||||
style={{cursor: 'pointer'}}
|
||||
src="/assets/images/icons/menu.svg"
|
||||
width="8"
|
||||
height="8"
|
||||
style={{marginRight: '5px'}}
|
||||
/>
|
||||
{component.name}
|
||||
|
||||
</span>
|
||||
<img
|
||||
style={{cursor: 'pointer'}}
|
||||
src="/assets/images/icons/trash.svg"
|
||||
width="12"
|
||||
role="button"
|
||||
className="mx-2"
|
||||
height="12"
|
||||
onClick={() => removeComponent({id})}
|
||||
style={{marginRight: '5px'}}
|
||||
export const ConfigHandle = function ConfigHandle({ id, component, configHandleClicked, dragRef, removeComponent }) {
|
||||
return (
|
||||
<div className="config-handle" ref={dragRef}>
|
||||
<span
|
||||
style={{ cursor: 'move' }}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
configHandleClicked(id, component);
|
||||
}}
|
||||
className="badge badge bg-azure-lt"
|
||||
role="button"
|
||||
>
|
||||
<img
|
||||
style={{ cursor: 'pointer', marginRight: '5px' }}
|
||||
src="/assets/images/icons/menu.svg"
|
||||
width="8"
|
||||
height="8"
|
||||
/>
|
||||
{component.name}
|
||||
</span>
|
||||
<img
|
||||
style={{ cursor: 'pointer', marginRight: '5px' }}
|
||||
src="/assets/images/icons/trash.svg"
|
||||
width="12"
|
||||
role="button"
|
||||
className="mx-2"
|
||||
height="12"
|
||||
onClick={() => removeComponent({ id })}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ import update from 'immutability-helper';
|
|||
import { componentTypes } from './Components/components';
|
||||
import { computeComponentName } from '@/_helpers/utils';
|
||||
|
||||
|
||||
|
||||
function uuidv4() {
|
||||
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16));
|
||||
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
|
||||
(c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)
|
||||
);
|
||||
}
|
||||
|
||||
export const Container = ({
|
||||
|
|
@ -31,13 +31,12 @@ export const Container = ({
|
|||
deviceWindowWidth,
|
||||
scaleValue,
|
||||
selectedComponent,
|
||||
darkMode
|
||||
darkMode,
|
||||
}) => {
|
||||
|
||||
const styles = {
|
||||
width: currentLayout === 'mobile' ? deviceWindowWidth : 1292,
|
||||
height: 2400,
|
||||
position: 'absolute'
|
||||
position: 'absolute',
|
||||
};
|
||||
|
||||
const components = appDefinition.components;
|
||||
|
|
@ -55,8 +54,8 @@ export const Container = ({
|
|||
setBoxes(
|
||||
update(boxes, {
|
||||
[id]: {
|
||||
$merge: { layouts }
|
||||
}
|
||||
$merge: { layouts },
|
||||
},
|
||||
})
|
||||
);
|
||||
console.log('new boxes - 1', boxes);
|
||||
|
|
@ -67,17 +66,18 @@ export const Container = ({
|
|||
useEffect(() => {
|
||||
console.log('new boxes - 2', boxes);
|
||||
appDefinitionChanged({ ...appDefinition, components: boxes });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [boxes]);
|
||||
|
||||
const { draggingState } = useDragLayer((monitor) => {
|
||||
if(monitor.isDragging()) {
|
||||
if(!monitor.getItem().parent) {
|
||||
return { draggingState: true }
|
||||
if (monitor.isDragging()) {
|
||||
if (!monitor.getItem().parent) {
|
||||
return { draggingState: true };
|
||||
} else {
|
||||
return { draggingState: false }
|
||||
return { draggingState: false };
|
||||
}
|
||||
} else {
|
||||
return { draggingState: false }
|
||||
return { draggingState: false };
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -89,8 +89,7 @@ export const Container = ({
|
|||
() => ({
|
||||
accept: ItemTypes.BOX,
|
||||
drop(item, monitor) {
|
||||
|
||||
if(item.parent) {
|
||||
if (item.parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -112,7 +111,7 @@ export const Container = ({
|
|||
let deltaX = 0;
|
||||
let deltaY = 0;
|
||||
|
||||
if(delta) {
|
||||
if (delta) {
|
||||
deltaX = delta.x;
|
||||
deltaY = delta.y;
|
||||
}
|
||||
|
|
@ -135,13 +134,12 @@ export const Container = ({
|
|||
...boxes[id]['layouts'][item.currentLayout],
|
||||
top: top,
|
||||
left: left,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
setBoxes(newBoxes);
|
||||
|
||||
} else {
|
||||
// This is a new component
|
||||
componentMeta = componentTypes.find((component) => component.component === item.component.component);
|
||||
|
|
@ -153,8 +151,8 @@ export const Container = ({
|
|||
const offsetFromLeftOfWindow = canvasBoundingRect.left;
|
||||
const currentOffset = monitor.getSourceClientOffset();
|
||||
|
||||
left = Math.round(currentOffset.x + (currentOffset.x * (1 - zoomLevel)) - offsetFromLeftOfWindow);
|
||||
top = Math.round(currentOffset.y + (currentOffset.y * (1 - zoomLevel)) - offsetFromTopOfWindow);
|
||||
left = Math.round(currentOffset.x + currentOffset.x * (1 - zoomLevel) - offsetFromLeftOfWindow);
|
||||
top = Math.round(currentOffset.y + currentOffset.y * (1 - zoomLevel) - offsetFromTopOfWindow);
|
||||
|
||||
id = uuidv4();
|
||||
|
||||
|
|
@ -162,7 +160,7 @@ export const Container = ({
|
|||
[left, top] = doSnapToGrid(left, top);
|
||||
}
|
||||
|
||||
if(item.currentLayout === 'mobile') {
|
||||
if (item.currentLayout === 'mobile') {
|
||||
componentData.definition.others.showOnDesktop.value = false;
|
||||
componentData.definition.others.showOnMobile.value = true;
|
||||
}
|
||||
|
|
@ -177,14 +175,14 @@ export const Container = ({
|
|||
left: left,
|
||||
width: componentMeta.defaultSize.width,
|
||||
height: componentMeta.defaultSize.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
}),
|
||||
[moveBox]
|
||||
);
|
||||
|
|
@ -199,10 +197,10 @@ export const Container = ({
|
|||
top: 100,
|
||||
left: 0,
|
||||
width: 445,
|
||||
height: 500
|
||||
height: 500,
|
||||
};
|
||||
|
||||
let { left, top, width, height } = boxes[id]['layouts'][currentLayout] || defaultData;
|
||||
let { left, top, width, height } = boxes[id]['layouts'][currentLayout] || defaultData;
|
||||
|
||||
top = y;
|
||||
left = x;
|
||||
|
|
@ -210,6 +208,8 @@ export const Container = ({
|
|||
width = width + deltaWidth;
|
||||
height = height + deltaHeight;
|
||||
|
||||
// [width, height] = doSnapToGrid(width, height)
|
||||
|
||||
let newBoxes = {
|
||||
...boxes,
|
||||
[id]: {
|
||||
|
|
@ -218,10 +218,13 @@ export const Container = ({
|
|||
...boxes[id]['layouts'],
|
||||
[currentLayout]: {
|
||||
...boxes[id]['layouts'][currentLayout],
|
||||
width, height, top, left
|
||||
}
|
||||
}
|
||||
}
|
||||
width,
|
||||
height,
|
||||
top,
|
||||
left,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
setBoxes(newBoxes);
|
||||
|
|
@ -239,74 +242,77 @@ export const Container = ({
|
|||
...boxes[id].component.definition,
|
||||
properties: {
|
||||
...boxes[id].component.definition.properties,
|
||||
[param]: value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
[param]: value,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={drop} style={styles} className={`real-canvas ${isDragging || isResizing ? ' show-grid' : ''}`}>
|
||||
<div ref={drop} style={styles} className={`real-canvas ${isDragging || isResizing ? 'show-grid' : ''}`}>
|
||||
{Object.keys(boxes).map((key) => {
|
||||
|
||||
const box = boxes[key];
|
||||
const canShowInCurrentLayout = box.component.definition.others[currentLayout === 'mobile' ? 'showOnMobile' : 'showOnDesktop'].value;
|
||||
const canShowInCurrentLayout =
|
||||
box.component.definition.others[currentLayout === 'mobile' ? 'showOnMobile' : 'showOnDesktop'].value;
|
||||
|
||||
if(!box.parent && canShowInCurrentLayout) {
|
||||
return <DraggableBox
|
||||
onComponentClick={onComponentClick}
|
||||
onEvent={onEvent}
|
||||
onComponentOptionChanged={onComponentOptionChanged}
|
||||
onComponentOptionsChanged={onComponentOptionsChanged}
|
||||
key={key}
|
||||
currentState={currentState}
|
||||
onResizeStop={onResizeStop}
|
||||
paramUpdated={paramUpdated}
|
||||
id={key}
|
||||
{...boxes[key]}
|
||||
mode={mode}
|
||||
resizingStatusChanged={(status) => setIsResizing(status)}
|
||||
inCanvas={true}
|
||||
zoomLevel={zoomLevel}
|
||||
configHandleClicked={configHandleClicked}
|
||||
removeComponent={removeComponent}
|
||||
currentLayout={currentLayout}
|
||||
scaleValue={scaleValue}
|
||||
deviceWindowWidth={deviceWindowWidth}
|
||||
isSelectedComponent={selectedComponent? selectedComponent.id === key : false}
|
||||
darkMode={darkMode}
|
||||
containerProps={{
|
||||
mode,
|
||||
snapToGrid,
|
||||
onComponentClick,
|
||||
onEvent,
|
||||
appDefinition,
|
||||
appDefinitionChanged,
|
||||
currentState,
|
||||
onComponentOptionChanged,
|
||||
onComponentOptionsChanged,
|
||||
appLoading,
|
||||
zoomLevel,
|
||||
configHandleClicked,
|
||||
removeComponent,
|
||||
currentLayout,
|
||||
scaleValue,
|
||||
deviceWindowWidth,
|
||||
selectedComponent,
|
||||
darkMode
|
||||
}}
|
||||
/>
|
||||
if (!box.parent && canShowInCurrentLayout) {
|
||||
return (
|
||||
<DraggableBox
|
||||
onComponentClick={onComponentClick}
|
||||
onEvent={onEvent}
|
||||
onComponentOptionChanged={onComponentOptionChanged}
|
||||
onComponentOptionsChanged={onComponentOptionsChanged}
|
||||
key={key}
|
||||
currentState={currentState}
|
||||
onResizeStop={onResizeStop}
|
||||
paramUpdated={paramUpdated}
|
||||
id={key}
|
||||
{...boxes[key]}
|
||||
mode={mode}
|
||||
resizingStatusChanged={(status) => setIsResizing(status)}
|
||||
inCanvas={true}
|
||||
zoomLevel={zoomLevel}
|
||||
configHandleClicked={configHandleClicked}
|
||||
removeComponent={removeComponent}
|
||||
currentLayout={currentLayout}
|
||||
scaleValue={scaleValue}
|
||||
deviceWindowWidth={deviceWindowWidth}
|
||||
isSelectedComponent={selectedComponent ? selectedComponent.id === key : false}
|
||||
darkMode={darkMode}
|
||||
containerProps={{
|
||||
mode,
|
||||
snapToGrid,
|
||||
onComponentClick,
|
||||
onEvent,
|
||||
appDefinition,
|
||||
appDefinitionChanged,
|
||||
currentState,
|
||||
onComponentOptionChanged,
|
||||
onComponentOptionsChanged,
|
||||
appLoading,
|
||||
zoomLevel,
|
||||
configHandleClicked,
|
||||
removeComponent,
|
||||
currentLayout,
|
||||
scaleValue,
|
||||
deviceWindowWidth,
|
||||
selectedComponent,
|
||||
darkMode,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
)}
|
||||
})}
|
||||
{Object.keys(boxes).length === 0 && !appLoading && !isDragging && (
|
||||
<div className="mx-auto w-50 p-5 bg-light no-components-box" style={{ marginTop: '10%'}}>
|
||||
<center className="text-muted">You haven't added any components yet. Drag components from the right sidebar and drop here.</center>
|
||||
<div className="mx-auto w-50 p-5 bg-light no-components-box" style={{ marginTop: '10%' }}>
|
||||
<center className="text-muted">
|
||||
You haven't added any components yet. Drag components from the right sidebar and drop here.
|
||||
</center>
|
||||
</div>
|
||||
)}
|
||||
{appLoading && (
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ const layerStyles = {
|
|||
left: 0,
|
||||
top: 0,
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
height: '100%',
|
||||
};
|
||||
|
||||
function getItemStyles(delta, item, initialOffset, currentOffset, currentLayout) {
|
||||
if (!initialOffset || !currentOffset) {
|
||||
return {
|
||||
display: 'none'
|
||||
display: 'none',
|
||||
};
|
||||
}
|
||||
let { x, y } = currentOffset;
|
||||
|
|
@ -28,18 +28,19 @@ function getItemStyles(delta, item, initialOffset, currentOffset, currentLayout)
|
|||
|
||||
const realCanvasDelta = realCanvasBoundingRect.x - canvasContainerBoundingRect.x;
|
||||
|
||||
if (id) { // Dragging within the canvas
|
||||
if (id) {
|
||||
// Dragging within the canvas
|
||||
|
||||
x = Math.round(item.layouts[currentLayout].left + delta.x);
|
||||
y = Math.round(item.layouts[currentLayout].top + delta.y);
|
||||
|
||||
} else { // New component being dragged from components sidebar
|
||||
} else {
|
||||
// New component being dragged from components sidebar
|
||||
const offsetFromTopOfWindow = realCanvasBoundingRect.top;
|
||||
const offsetFromLeftOfWindow = realCanvasBoundingRect.left;
|
||||
const zoomLevel = item.zoomLevel;
|
||||
|
||||
x = Math.round(currentOffset.x + (currentOffset.x * (1 - zoomLevel)) - offsetFromLeftOfWindow);
|
||||
y = Math.round(currentOffset.y + (currentOffset.y * (1 - zoomLevel)) - offsetFromTopOfWindow);
|
||||
x = Math.round(currentOffset.x + currentOffset.x * (1 - zoomLevel) - offsetFromLeftOfWindow);
|
||||
y = Math.round(currentOffset.y + currentOffset.y * (1 - zoomLevel) - offsetFromTopOfWindow);
|
||||
}
|
||||
|
||||
[x, y] = snapToGrid(x, y);
|
||||
|
|
@ -49,24 +50,22 @@ function getItemStyles(delta, item, initialOffset, currentOffset, currentLayout)
|
|||
const transform = `translate(${x}px, ${y}px)`;
|
||||
return {
|
||||
transform,
|
||||
WebkitTransform: transform
|
||||
WebkitTransform: transform,
|
||||
};
|
||||
}
|
||||
export const CustomDragLayer = ({ currentLayout }) => {
|
||||
const {
|
||||
itemType, isDragging, item, initialOffset, currentOffset, delta
|
||||
} = useDragLayer((monitor) => ({
|
||||
const { itemType, isDragging, item, initialOffset, currentOffset, delta } = useDragLayer((monitor) => ({
|
||||
item: monitor.getItem(),
|
||||
itemType: monitor.getItemType(),
|
||||
initialOffset: monitor.getInitialSourceClientOffset(),
|
||||
currentOffset: monitor.getSourceClientOffset(),
|
||||
isDragging: monitor.isDragging(),
|
||||
delta: monitor.getDifferenceFromInitialOffset()
|
||||
delta: monitor.getDifferenceFromInitialOffset(),
|
||||
}));
|
||||
function renderItem() {
|
||||
switch (itemType) {
|
||||
case ItemTypes.BOX:
|
||||
return <BoxDragPreview item={item} currentLayout={currentLayout}/>;
|
||||
return <BoxDragPreview item={item} currentLayout={currentLayout} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
@ -78,9 +77,7 @@ export const CustomDragLayer = ({ currentLayout }) => {
|
|||
|
||||
return (
|
||||
<div style={layerStyles}>
|
||||
<div style={getItemStyles(delta, item, initialOffset, currentOffset, currentLayout)}>
|
||||
{renderItem()}
|
||||
</div>
|
||||
<div style={getItemStyles(delta, item, initialOffset, currentOffset, currentLayout)}>{renderItem()}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,10 +3,9 @@ import { datasourceService, authenticationService } from '@/_services';
|
|||
import Modal from 'react-bootstrap/Modal';
|
||||
import Button from 'react-bootstrap/Button';
|
||||
import { toast } from 'react-toastify';
|
||||
import { dataBaseSources, apiSources, DataSourceTypes } from './DataSourceTypes';
|
||||
import { defaultOptions } from './DefaultOptions';
|
||||
import { TestConnection } from './TestConnection';
|
||||
import { SourceComponents } from './SourceComponents';
|
||||
import { DataBaseSources, ApiSources, DataSourceTypes, SourceComponents } from './SourceComponents';
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
import config from 'config';
|
||||
|
||||
|
|
@ -204,7 +203,7 @@ class DataSourceManager extends React.Component {
|
|||
<div>
|
||||
<div className="row row-deck">
|
||||
<h4 className="text-muted mb-2">DATABASES</h4>
|
||||
{dataBaseSources.map((dataSource) => (
|
||||
{DataBaseSources.map((dataSource) => (
|
||||
<div className="col-md-2" key={dataSource.name}>
|
||||
<div className="card mb-3" role="button" onClick={() => this.selectDataSource(dataSource)}>
|
||||
<div className="card-body">
|
||||
|
|
@ -227,7 +226,7 @@ class DataSourceManager extends React.Component {
|
|||
</div>
|
||||
<div className="row row-deck mt-2">
|
||||
<h4 className="text-muted mb-2">APIS</h4>
|
||||
{apiSources.map((dataSource) => (
|
||||
{ApiSources.map((dataSource) => (
|
||||
<div className="col-md-2" key={dataSource.name}>
|
||||
<div className="card" role="button" onClick={() => this.selectDataSource(dataSource)}>
|
||||
<div className="card-body">
|
||||
|
|
@ -292,7 +291,11 @@ class DataSourceManager extends React.Component {
|
|||
|
||||
<div className="col">
|
||||
<small>
|
||||
<a href={`https://docs.tooljet.io/docs/data-sources/${selectedDataSource.kind}`} target="_blank">
|
||||
<a
|
||||
href={`https://docs.tooljet.io/docs/data-sources/${selectedDataSource.kind}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Read documentation
|
||||
</a>
|
||||
</small>
|
||||
|
|
|
|||
|
|
@ -1,226 +0,0 @@
|
|||
export const dataBaseSources = [
|
||||
{
|
||||
name: 'PostgreSQL',
|
||||
kind: 'postgresql',
|
||||
options: {
|
||||
host: { type: 'string' },
|
||||
port: { type: 'string' },
|
||||
database: { type: 'string' },
|
||||
username: { type: 'string' },
|
||||
password: { type: 'string', encrypted: true }
|
||||
},
|
||||
exposedVariables: {
|
||||
isLoading: {},
|
||||
data: {},
|
||||
rawData: {}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'MySQL',
|
||||
kind: 'mysql',
|
||||
exposedVariables: {
|
||||
isLoading: {},
|
||||
data: {},
|
||||
rawData: {}
|
||||
},
|
||||
options: {
|
||||
host: { type: 'string' },
|
||||
port: { type: 'string' },
|
||||
database: { type: 'string' },
|
||||
username: { type: 'string' },
|
||||
password: { type: 'string', encrypted: true }
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'SQL Server',
|
||||
kind: 'mssql',
|
||||
exposedVariables: {
|
||||
isLoading: {},
|
||||
data: {},
|
||||
rawData: {}
|
||||
},
|
||||
options: {
|
||||
host: { type: 'string' },
|
||||
port: { type: 'string' },
|
||||
database: { type: 'string' },
|
||||
username: { type: 'string' },
|
||||
password: { type: 'string', encrypted: true }
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'MongoDB',
|
||||
kind: 'mongodb',
|
||||
exposedVariables: {
|
||||
isLoading: {},
|
||||
data: {},
|
||||
rawData: {}
|
||||
},
|
||||
options: {
|
||||
host: { type: 'string' },
|
||||
port: { type: 'string' },
|
||||
username: { type: 'string' },
|
||||
password: { type: 'string', encrypted: true },
|
||||
connection_type: { type: 'options'},
|
||||
connection_string: { type: 'string', encrypted: true }
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Firestore',
|
||||
kind: 'firestore',
|
||||
exposedVariables: {
|
||||
isLoading: {},
|
||||
data: [],
|
||||
rawData: []
|
||||
},
|
||||
options: {
|
||||
gcp_key: { type: 'string', encrypted: true }
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'DynamoDB',
|
||||
kind: 'dynamodb',
|
||||
exposedVariables: {
|
||||
isLoading: {},
|
||||
data: {},
|
||||
rawData: {}
|
||||
},
|
||||
options: {
|
||||
region: { type: 'string' },
|
||||
access_key: { type: 'string' },
|
||||
secret_key: { type: 'string', encrypted: true }
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Elasticsearch',
|
||||
kind: 'elasticsearch',
|
||||
exposedVariables: {
|
||||
isLoading: {},
|
||||
data: {},
|
||||
rawData: {}
|
||||
},
|
||||
options: {
|
||||
host: { type: 'string' },
|
||||
port: { type: 'string' },
|
||||
username: { type: 'string' },
|
||||
password: { type: 'string', encrypted: true }
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Redis',
|
||||
kind: 'redis',
|
||||
exposedVariables: {
|
||||
isLoading: {},
|
||||
data: {},
|
||||
rawData: {}
|
||||
},
|
||||
options: {
|
||||
host: { type: 'string' },
|
||||
port: { type: 'string' },
|
||||
username: { type: 'string' },
|
||||
password: { type: 'string', encrypted: true }
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export const apiSources = [
|
||||
{
|
||||
name: 'Rest API',
|
||||
kind: 'restapi',
|
||||
options: {
|
||||
url: { type: 'string' },
|
||||
auth_type: { type: 'string' },
|
||||
grant_type: { type: 'string' },
|
||||
add_token_to: { type: 'string' },
|
||||
header_prefix: { type: 'string' },
|
||||
access_token_url: { type: 'string' },
|
||||
client_id: { type: 'string' },
|
||||
client_secret: { type: 'string', encrypted: true },
|
||||
scopes: { type: 'string' },
|
||||
auth_url: { type: 'string' },
|
||||
client_auth: { type: 'string' },
|
||||
headers: { type: 'array' },
|
||||
custom_auth_params: { type: 'array' }
|
||||
},
|
||||
exposedVariables: {
|
||||
isLoading: {},
|
||||
data: {},
|
||||
rawData: {}
|
||||
},
|
||||
customTesting: true
|
||||
},
|
||||
{
|
||||
name: 'GraphQL',
|
||||
kind: 'graphql',
|
||||
options: {
|
||||
url: { type: 'string' },
|
||||
headers: { type: 'array' },
|
||||
url_params: { type: 'array' },
|
||||
body: { type: 'array' },
|
||||
},
|
||||
exposedVariables: {
|
||||
isLoading: {},
|
||||
data: {},
|
||||
rawData: {}
|
||||
},
|
||||
customTesting: true
|
||||
},
|
||||
{
|
||||
name: 'Stripe',
|
||||
kind: 'stripe',
|
||||
exposedVariables: {
|
||||
isLoading: {},
|
||||
data: {},
|
||||
rawData: {}
|
||||
},
|
||||
options: {
|
||||
api_key: { type: 'string', encrypted: true }
|
||||
},
|
||||
customTesting: true
|
||||
},
|
||||
{
|
||||
name: 'Airtable',
|
||||
kind: 'airtable',
|
||||
exposedVariables: {
|
||||
isLoading: {},
|
||||
data: {},
|
||||
rawData: {}
|
||||
},
|
||||
options: {
|
||||
api_key: { type: 'string', encrypted: true }
|
||||
},
|
||||
customTesting: true
|
||||
},
|
||||
{
|
||||
name: 'Google Sheets',
|
||||
kind: 'googlesheets',
|
||||
exposedVariables: {
|
||||
isLoading: {},
|
||||
data: {},
|
||||
rawData: {}
|
||||
},
|
||||
options: {
|
||||
api_key: { type: 'string', encrypted: true }
|
||||
},
|
||||
customTesting: true,
|
||||
hideSave: true
|
||||
},
|
||||
{
|
||||
name: 'Slack',
|
||||
kind: 'slack',
|
||||
exposedVariables: {
|
||||
isLoading: {},
|
||||
data: {},
|
||||
rawData: {}
|
||||
},
|
||||
options: {
|
||||
api_key: { type: 'string', encrypted: true }
|
||||
},
|
||||
customTesting: true,
|
||||
hideSave: true
|
||||
}
|
||||
];
|
||||
|
||||
export const DataSourceTypes = [
|
||||
...dataBaseSources,
|
||||
...apiSources
|
||||
];
|
||||
|
|
@ -5,7 +5,7 @@ export const defaultOptions = {
|
|||
database: { value: '' },
|
||||
username: { value: '' },
|
||||
password: { value: '' },
|
||||
ssl_enabled: { value: true }
|
||||
ssl_enabled: { value: true },
|
||||
},
|
||||
mysql: {
|
||||
host: { value: 'localhost' },
|
||||
|
|
@ -20,13 +20,13 @@ export const defaultOptions = {
|
|||
port: { value: 1433 },
|
||||
database: { value: '' },
|
||||
username: { value: '' },
|
||||
password: { value: '' }
|
||||
password: { value: '' },
|
||||
},
|
||||
redis: {
|
||||
host: { value: 'localhost' },
|
||||
port: { value: 6379 },
|
||||
username: { value: '' },
|
||||
password: { value: '' }
|
||||
password: { value: '' },
|
||||
},
|
||||
mongodb: {
|
||||
database: { value: '' },
|
||||
|
|
@ -35,7 +35,7 @@ export const defaultOptions = {
|
|||
username: { value: '' },
|
||||
password: { value: '' },
|
||||
connection_type: { value: 'manual' },
|
||||
connection_string: { value: ''}
|
||||
connection_string: { value: '' },
|
||||
},
|
||||
|
||||
elasticsearch: {
|
||||
|
|
@ -43,16 +43,16 @@ export const defaultOptions = {
|
|||
host: { value: 'localhost' },
|
||||
port: { value: 9200 },
|
||||
username: { value: '' },
|
||||
password: { value: '' }
|
||||
password: { value: '' },
|
||||
},
|
||||
stripe: {
|
||||
api_key: { value: '' }
|
||||
api_key: { value: '' },
|
||||
},
|
||||
airtable: {
|
||||
api_key: { value: '' }
|
||||
api_key: { value: '' },
|
||||
},
|
||||
firestore: {
|
||||
gcp_key: { value: '' }
|
||||
gcp_key: { value: '' },
|
||||
},
|
||||
restapi: {
|
||||
url: { value: '' },
|
||||
|
|
@ -67,22 +67,22 @@ export const defaultOptions = {
|
|||
auth_url: { value: '' },
|
||||
client_auth: { value: 'header' },
|
||||
headers: { value: [['', '']] },
|
||||
custom_auth_params: { value: [['', '']] }
|
||||
custom_auth_params: { value: [['', '']] },
|
||||
},
|
||||
graphql: {
|
||||
url: { value: '' },
|
||||
headers: { value: [['', '']] },
|
||||
url_params: { value: [['', '']] }
|
||||
url_params: { value: [['', '']] },
|
||||
},
|
||||
googlesheets: {
|
||||
access_type: { value: 'read' }
|
||||
access_type: { value: 'read' },
|
||||
},
|
||||
slack: {
|
||||
access_type: { value: 'read' }
|
||||
access_type: { value: 'read' },
|
||||
},
|
||||
dynamodb: {
|
||||
region: { value: ''},
|
||||
access_key: { value: ''},
|
||||
secret_key: { value: ''}
|
||||
}
|
||||
region: { value: '' },
|
||||
access_key: { value: '' },
|
||||
secret_key: { value: '' },
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,6 +4,19 @@
|
|||
"title": "Airtable datasource",
|
||||
"description": "A schema defining airtable datasource",
|
||||
"type": "object",
|
||||
"source": {
|
||||
"name": "Airtable",
|
||||
"kind": "airtable",
|
||||
"exposedVariables": {
|
||||
"isLoading": {},
|
||||
"data": {},
|
||||
"rawData": {}
|
||||
},
|
||||
"options": {
|
||||
"api_key": { "type": "string", "encrypted": true }
|
||||
},
|
||||
"customTesting": true
|
||||
},
|
||||
"properties": {
|
||||
"api_key": {
|
||||
"$label": "API key",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,20 @@
|
|||
"title": "Google Sheets datasource",
|
||||
"description": "A schema defining google sheets datasource",
|
||||
"type": "object",
|
||||
"source": {
|
||||
"name": "Google Sheets",
|
||||
"kind": "googlesheets",
|
||||
"exposedVariables": {
|
||||
"isLoading": {},
|
||||
"data": {},
|
||||
"rawData": {}
|
||||
},
|
||||
"options": {
|
||||
"api_key": { "type": "string", "encrypted": true }
|
||||
},
|
||||
"customTesting": true,
|
||||
"hideSave": true
|
||||
},
|
||||
"properties": {
|
||||
"sheets": {
|
||||
"$label": "",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,22 @@
|
|||
"title": "Graphql datasource",
|
||||
"description": "A schema defining graphql datasource",
|
||||
"type": "object",
|
||||
"source": {
|
||||
"name": "GraphQL",
|
||||
"kind": "graphql",
|
||||
"options": {
|
||||
"url": { "type": "string" },
|
||||
"headers": { "type": "array" },
|
||||
"url_params": { "type": "array" },
|
||||
"body": { "type": "array" }
|
||||
},
|
||||
"exposedVariables": {
|
||||
"isLoading": {},
|
||||
"data": {},
|
||||
"rawData": {}
|
||||
},
|
||||
"customTesting": true
|
||||
},
|
||||
"properties": {
|
||||
"url": {
|
||||
"$label": "URL",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,31 @@
|
|||
"title": "Restapi datasource",
|
||||
"description": "A schema defining restapi datasource",
|
||||
"type": "object",
|
||||
"source": {
|
||||
"name": "Rest API",
|
||||
"kind": "restapi",
|
||||
"options": {
|
||||
"url": { "type": "string" },
|
||||
"auth_type": { "type": "string" },
|
||||
"grant_type": { "type": "string" },
|
||||
"add_token_to": { "type": "string" },
|
||||
"header_prefix": { "type": "string" },
|
||||
"access_token_url": { "type": "string" },
|
||||
"client_id": { "type": "string" },
|
||||
"client_secret": { "type": "string", "encrypted": true },
|
||||
"scopes": { "type": "string" },
|
||||
"auth_url": { "type": "string" },
|
||||
"client_auth": { "type": "string" },
|
||||
"headers": { "type": "array" },
|
||||
"custom_auth_params": { "type": "array" }
|
||||
},
|
||||
"exposedVariables": {
|
||||
"isLoading": {},
|
||||
"data": {},
|
||||
"rawData": {}
|
||||
},
|
||||
"customTesting": true
|
||||
},
|
||||
"properties": {
|
||||
"url": {
|
||||
"$label": "URL",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,20 @@
|
|||
"title": "Slack datasource",
|
||||
"description": "A schema defining slack datasource",
|
||||
"type": "object",
|
||||
"source": {
|
||||
"name": "Slack",
|
||||
"kind": "slack",
|
||||
"exposedVariables": {
|
||||
"isLoading": {},
|
||||
"data": {},
|
||||
"rawData": {}
|
||||
},
|
||||
"options": {
|
||||
"api_key": { "type": "string", "encrypted": true }
|
||||
},
|
||||
"customTesting": true,
|
||||
"hideSave": true
|
||||
},
|
||||
"properties": {
|
||||
"sheets": {
|
||||
"$label": "",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,19 @@
|
|||
"title": "Stripe datasource",
|
||||
"description": "A schema defining stripe datasource",
|
||||
"type": "object",
|
||||
"source": {
|
||||
"name": "Stripe",
|
||||
"kind": "stripe",
|
||||
"exposedVariables": {
|
||||
"isLoading": {},
|
||||
"data": {},
|
||||
"rawData": {}
|
||||
},
|
||||
"options": {
|
||||
"api_key": { "type": "string", "encrypted": true }
|
||||
},
|
||||
"customTesting": true
|
||||
},
|
||||
"properties": {
|
||||
"api_key": {
|
||||
"$label": "API key",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,20 @@
|
|||
"title": "Dynamodb datasource",
|
||||
"description": "A schema defining dynamodb datasource",
|
||||
"type": "object",
|
||||
"source": {
|
||||
"name": "DynamoDB",
|
||||
"kind": "dynamodb",
|
||||
"exposedVariables": {
|
||||
"isLoading": {},
|
||||
"data": {},
|
||||
"rawData": {}
|
||||
},
|
||||
"options": {
|
||||
"region": { "type": "string" },
|
||||
"access_key": { "type": "string" },
|
||||
"secret_key": { "type": "string", "encrypted": true }
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"region": {
|
||||
"$label": "Region",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,21 @@
|
|||
"title": "Elastic search datasource",
|
||||
"description": "A schema defining elastic search datasource",
|
||||
"type": "object",
|
||||
"source": {
|
||||
"name": "Elasticsearch",
|
||||
"kind": "elasticsearch",
|
||||
"exposedVariables": {
|
||||
"isLoading": {},
|
||||
"data": {},
|
||||
"rawData": {}
|
||||
},
|
||||
"options": {
|
||||
"host": { "type": "string" },
|
||||
"port": { "type": "string" },
|
||||
"username": { "type": "string" },
|
||||
"password": { "type": "string", "encrypted": true }
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"host": {
|
||||
"$label": "Host",
|
||||
|
|
@ -21,13 +36,13 @@
|
|||
"$label": "Username",
|
||||
"$key": "username",
|
||||
"type": "text",
|
||||
"description": "Enter username field"
|
||||
"description": "Enter username"
|
||||
},
|
||||
"password": {
|
||||
"$label": "Password",
|
||||
"$key": "password",
|
||||
"type": "password",
|
||||
"description": "Enter password field"
|
||||
"description": "Enter password"
|
||||
}
|
||||
},
|
||||
"required": ["scheme", "host", "port", "password"]
|
||||
|
|
|
|||
|
|
@ -4,6 +4,18 @@
|
|||
"title": "Firestore datasource",
|
||||
"description": "A schema defining firestore datasource",
|
||||
"type": "object",
|
||||
"source": {
|
||||
"name": "Firestore",
|
||||
"kind": "firestore",
|
||||
"exposedVariables": {
|
||||
"isLoading": {},
|
||||
"data": [],
|
||||
"rawData": []
|
||||
},
|
||||
"options": {
|
||||
"gcp_key": { "type": "string", "encrypted": true }
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"gcp_key": {
|
||||
"$label": "Private key",
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue