Merge branch 'release/v0.7.4' into main

This commit is contained in:
navaneeth 2021-10-07 13:17:34 +05:30
commit baa577bc44
316 changed files with 22922 additions and 29070 deletions

View file

@ -1,2 +0,0 @@
frontend/node_modules/**
# **/*.js

View file

@ -1,8 +0,0 @@
{
"semi": true,
"trailingComma": "es5",
"printWidth": 120,
"singleQuote": true,
"arrowParens": "always",
"proseWrap": "preserve"
}

View file

@ -1 +1 @@
0.7.3
0.7.4

11
.vscode/extension.json vendored Normal file
View 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
View 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,
}

View file

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

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

View file

@ -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.
![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/tooljet/tooljet-ce)
![GitHub contributors](https://img.shields.io/github/contributors/tooljet/tooljet)
[![GitHub issues](https://img.shields.io/github/issues/ToolJet/ToolJet)](https://github.com/ToolJet/ToolJet/issues)
[![GitHub stars](https://img.shields.io/github/stars/ToolJet/ToolJet)](https://github.com/ToolJet/ToolJet/stargazers)
![GitHub closed issues](https://img.shields.io/github/issues-closed/tooljet/tooljet)
![GitHub pull requests](https://img.shields.io/github/issues-pr-raw/tooljet/tooljet)
![GitHub release (latest by date)](https://img.shields.io/github/v/release/tooljet/tooljet)
![GitHub commit activity](https://img.shields.io/github/commit-activity/m/tooljet/tooljet)
[![GitHub license](https://img.shields.io/github/license/ToolJet/ToolJet)](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.

View file

@ -0,0 +1,7 @@
apiVersion: networking.gke.io/v1beta1
kind: ManagedCertificate
metadata:
name: tj
spec:
domains:
- tooljet.your-domain.com

View 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"

View 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

View 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

View file

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

View file

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

View file

@ -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)
:::

View file

@ -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)
:::

View file

@ -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)
:::

View file

@ -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)
:::

View file

@ -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)
:::

View file

@ -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)
:::

View file

@ -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)
:::

View 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`

View file

@ -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).
:::

View file

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

View file

@ -0,0 +1,5 @@
{
"label": "Single Sign-on",
"position": 6,
"collapsed": true
}

51
docs/docs/sso/google.md Normal file
View 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.

View file

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

View 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.|

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
docs/static/img/sso/google/scope.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 719 KiB

View file

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

View file

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

View file

@ -14,7 +14,7 @@
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
import './commands';
// Alternatively you can use CommonJS syntax:
// require('./commands')

File diff suppressed because it is too large Load diff

View file

@ -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": []
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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 && (

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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>
);
};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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}}' },
},
},
},

View file

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

View file

@ -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&apos;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&apos;t added any components yet. Drag components from the right sidebar and drop here.
</center>
</div>
)}
{appLoading && (

View file

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

View file

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

View file

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

View file

@ -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: '' },
},
};

View file

@ -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",

View file

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

View file

@ -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",

View file

@ -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",

View file

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

View file

@ -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",

View file

@ -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",

View file

@ -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"]

View file

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