Merge branch 'release/v0.9.1'
2
.version
|
|
@ -1 +1 @@
|
|||
0.9.0
|
||||
0.9.1
|
||||
|
|
@ -74,4 +74,4 @@ Kindly read our [Contributing Guide](CONTRIBUTING.md) to learn and understand ab
|
|||
</a>
|
||||
|
||||
## Licence
|
||||
ToolJet © 2021, ToolJet Solutions Inc - Released under the GNU General Public License v3.0.
|
||||
ToolJet © 2021, ToolJet Solutions Inc - Released under the GNU Affero General Public License v3.0.
|
||||
|
|
|
|||
18
app.json
|
|
@ -5,7 +5,7 @@
|
|||
"repository": "https://github.com/tooljet/tooljet",
|
||||
"logo": "https://app.tooljet.io/assets/images/logo.svg",
|
||||
"success_url": "/",
|
||||
"scripts":{
|
||||
"scripts": {
|
||||
"predeploy": "npm install --prefix server && npm run build --prefix server"
|
||||
},
|
||||
"env": {
|
||||
|
|
@ -28,6 +28,18 @@
|
|||
"SECRET_KEY_BASE": {
|
||||
"description": "Used by ToolJet server as the input secret to the application's key generator.",
|
||||
"value": ""
|
||||
},
|
||||
"NODE_OPTIONS": {
|
||||
"description": "Node options configured to increase node memory to support app build",
|
||||
"value": "--max-old-space-size=4096"
|
||||
},
|
||||
"SSO_GOOGLE_OAUTH2_CLIENT_ID": {
|
||||
"description": "Google OAuth2 client id to enable single sign-on",
|
||||
"value": ""
|
||||
},
|
||||
"SSO_DISABLE_SIGNUP": {
|
||||
"description": "Disable sign-up via SSO",
|
||||
"value": ""
|
||||
}
|
||||
},
|
||||
"formation": {
|
||||
|
|
@ -36,7 +48,9 @@
|
|||
}
|
||||
},
|
||||
"image": "heroku/nodejs",
|
||||
"addons": ["heroku-postgresql"],
|
||||
"addons": [
|
||||
"heroku-postgresql"
|
||||
],
|
||||
"buildpacks": [
|
||||
{
|
||||
"url": "heroku/nodejs"
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ source "amazon-ebs" "ubuntu" {
|
|||
instance_type = "${var.instance_type}"
|
||||
region = "${var.ami_region}"
|
||||
ami_regions = "${var.ami_regions}"
|
||||
ami_groups = "${var.ami_groups}"
|
||||
source_ami_filter {
|
||||
filters = {
|
||||
name = "ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*"
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@ variable "ami_region" {
|
|||
default = "us-west-2"
|
||||
}
|
||||
|
||||
variable "ami_groups" {
|
||||
type = list(string)
|
||||
default = ["all"]
|
||||
}
|
||||
|
||||
variable "ami_regions" {
|
||||
type = list(string)
|
||||
default = ["us-west-1", "us-east-1", "us-east-2", "eu-west-2", "eu-central-1", "ap-northeast-1", "ap-southeast-1", "ap-northeast-3", "ap-south-1", "ap-northeast-2", "ap-southeast-2", "ca-central-1", "eu-west-1", "eu-north-1", "sa-east-1"]
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ WORKDIR /app
|
|||
ENV PATH /app/node_modules/.bin:$PATH
|
||||
|
||||
# Fix for heap limit allocation issue
|
||||
ENV NODE_OPTIONS="--max-old-space-size=2048"
|
||||
ENV NODE_OPTIONS="--max-old-space-size=4096"
|
||||
|
||||
# install app dependencies
|
||||
COPY ./frontend/package.json ./frontend/package-lock.json ./
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ WORKDIR /app
|
|||
ENV PATH /app/node_modules/.bin:$PATH
|
||||
|
||||
# Fix for heap limit allocation issue
|
||||
ENV NODE_OPTIONS="--max-old-space-size=2048"
|
||||
ENV NODE_OPTIONS="--max-old-space-size=4096"
|
||||
|
||||
|
||||
# install app dependencies
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
FROM node:14.17.3-buster
|
||||
|
||||
# Fix for JS heap limit allocation issue
|
||||
ENV NODE_OPTIONS="--max-old-space-size=2048"
|
||||
ENV NODE_OPTIONS="--max-old-space-size=4096"
|
||||
|
||||
RUN apt update && apt install -y \
|
||||
build-essential \
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ FROM node:14.17.3-buster
|
|||
ENV NODE_ENV=production
|
||||
|
||||
# Fix for JS heap limit allocation issue
|
||||
ENV NODE_OPTIONS="--max-old-space-size=2048"
|
||||
ENV NODE_OPTIONS="--max-old-space-size=4096"
|
||||
|
||||
RUN apt update && apt install -y \
|
||||
build-essential \
|
||||
|
|
|
|||
40
docs/docs/actions/generate-file.md
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
sidebar_label: Generate file
|
||||
---
|
||||
|
||||
# Generate file
|
||||
|
||||
This action allows you to construct files on the fly and let users download it.
|
||||
Presently, the only file type supported is `CSV`.
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| Type | Type of file to be generated |
|
||||
| File name | Name of the file to be generated |
|
||||
| Data | Data that will be used to construct the file. Its format will depend on the file type, as specified in the following section |
|
||||
|
||||
### Data format for CSV
|
||||
|
||||
For `CSV` file type, the data field should be supplied with an array objects. ToolJet assumes that the keys of each of
|
||||
these objects are the same and that they represent the column headers of the csv file.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
{{
|
||||
[
|
||||
{ name: 'John', email: 'john@tooljet.com' },
|
||||
{ name: 'Sarah', email: 'sarah@tooljet.com' },
|
||||
]
|
||||
}}
|
||||
```
|
||||
|
||||
Supplying the above snippet will generate a csv file which looks like this:
|
||||
|
||||
```csv
|
||||
name,email
|
||||
John,john@tooljet.com
|
||||
Sarah,sarah@tooljet.com
|
||||
```
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
---
|
||||
sidebar_position: 1
|
||||
sidebar_label: Set localStorage
|
||||
---
|
||||
|
||||
|
|
|
|||
35
docs/docs/data-sources/aws-s3.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
sidebar_position: 12
|
||||
---
|
||||
|
||||
# Amazon S3
|
||||
|
||||
ToolJet can connect to Amazon S3 buckets and perform various operation on them.
|
||||
|
||||
## Connection
|
||||
|
||||
To add a new S3 source, click on the Add or edit datasource icon on the left sidebar of the app editor and click on `Add datasource` button. Select AWS S3 from the modal that pops up.
|
||||
|
||||
ToolJet requires the following to connect to your DynamoDB.
|
||||
|
||||
- Region
|
||||
- Access key
|
||||
- Secret key
|
||||
|
||||
It is recommended to create a new IAM user for the database so that you can control the access levels of ToolJet.
|
||||
|
||||
<img src="/img/datasource-reference/aws-s3-connect.png" alt="ToolJet - AWS S3 connection" height="250"/>
|
||||
|
||||
Click on 'Test connection' button to verify if the credentials are correct and that the database is accessible to ToolJet server. Click on 'Save' button to save the datasource.
|
||||
|
||||
## Querying AWS S3
|
||||
|
||||
Click on + button of the query manager at the bottom panel of the editor and select the datasource added in the previous step as the datasource. Select the operation that you want to perform and click 'Save' to save the query.
|
||||
|
||||
<img src="/img/datasource-reference/aws-s3-query.png" alt="ToolJet - AWS S3 query" height="250"/>
|
||||
|
||||
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
|
||||
:::
|
||||
30
docs/docs/data-sources/gcs.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
sidebar_position: 13
|
||||
---
|
||||
|
||||
# Google Cloud Storage
|
||||
|
||||
ToolJet can connect to GCS buckets and perform various operation on them.
|
||||
|
||||
## Connection
|
||||
|
||||
To add a new GCS source, click on the Add or edit datasource icon on the left sidebar of the app editor and click on `Add datasource` button. Select GCS from the modal that pops up.
|
||||
|
||||
ToolJet requires the json private key of a service account to be able to connect to GCS.
|
||||
You can follow the [google documentation](https://cloud.google.com/docs/authentication/getting-started) to get started.
|
||||
|
||||
<img src="/img/datasource-reference/gcs-connect.png" alt="ToolJet - GCS connection" height="250"/>
|
||||
|
||||
Click on 'Test connection' button to verify if the credentials are correct and that the database is accessible to ToolJet server. Click on 'Save' button to save the datasource.
|
||||
|
||||
## Querying GCS
|
||||
|
||||
Click on + button of the query manager at the bottom panel of the editor and select the datasource added in the previous step as the datasource. Select the operation that you want to perform and click 'Save' to save the query.
|
||||
|
||||
<img src="/img/datasource-reference/gcs-query.png" alt="ToolJet - GCS query" height="250"/>
|
||||
|
||||
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
|
||||
:::
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"label": "Deployment",
|
||||
"position": 4,
|
||||
"collapsed": false
|
||||
"collapsed": true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 7
|
||||
sidebar_position: 8
|
||||
---
|
||||
|
||||
# Deploying ToolJet client
|
||||
|
|
@ -11,7 +11,7 @@ ToolJet client is a standalone application and can be deployed on static website
|
|||
:::tip
|
||||
You should set the environment variable `TOOLJET_SERVER_URL` ( URL of the server ) while building the frontend.
|
||||
|
||||
For example: `TOOLJET_SERVER_URL=https://server.tooljet.io npm run build && firebase deploy`
|
||||
For example: `NODE_ENV=production TOOLJET_SERVER_URL=https://server.tooljet.com npm run build && firebase deploy`
|
||||
:::
|
||||
|
||||
1. Initialize firebase project
|
||||
|
|
@ -24,6 +24,67 @@ For example: `TOOLJET_SERVER_URL=https://server.tooljet.io npm run build && fire
|
|||
firebase deploy
|
||||
```
|
||||
|
||||
## Deploying ToolJet client with Google Cloud Storage
|
||||
|
||||
:::tip
|
||||
If you want to run ToolJet on your local machine, please checkout the setup section of the contributing guide: [link](/docs/contributing-guide/setup/docker)
|
||||
You should set the environment variable `TOOLJET_SERVER_URL` ( URL of the server ) while building the frontend.
|
||||
|
||||
|
||||
For example: `NODE_ENV=production TOOLJET_SERVER_URL=https://server.tooljet.io npm run build`
|
||||
:::
|
||||
|
||||
#### Using Load balancer
|
||||
|
||||
Tooljet client can be hosted from Cloud Storage bucket just like hosting any other static website.
|
||||
Follow the instructions from google documentation [here](https://cloud.google.com/storage/docs/hosting-static-website).
|
||||
|
||||
Summarising the steps below:
|
||||
1. Create a bucket and upload files within the build folder such that the `index.html` is at the bucket root.
|
||||
|
||||
2. Edit permissions for the bucket to assign *New principal* as `allUsers` with role as `Storage Object Viewer` and permit for public access for the bucket.
|
||||
|
||||
3. Click on *Edit website configuration* from the [buckets browser](https://console.cloud.google.com/storage/browser?_ga=2.180838119.1530169400.1637242882-657891227.1637242882) and specify the main page as `index.html`
|
||||
|
||||
4. Follow the [instructions](https://cloud.google.com/storage/docs/hosting-static-website#lb-ssl) on creating a load balancer for hosting a static website.
|
||||
|
||||
5. Optionally, create Cloud CDN to use with the backend bucket assigned to the load balancer.
|
||||
|
||||
6. After the load balancer is created there will be an IP assigned to it. Try hitting it to check the website is being loaded.
|
||||
|
||||
7. Use the load balancer IP as the static IP for the A record of your domain.
|
||||
|
||||
#### Using Google App Engine
|
||||
|
||||
1. Upload the build folder onto a bucket
|
||||
|
||||
2. Upload `app.yaml` file onto bucket with the following config
|
||||
|
||||
```yaml
|
||||
runtime: python27
|
||||
api_version: 1
|
||||
threadsafe: true
|
||||
|
||||
handlers:
|
||||
- url: /
|
||||
static_files: build/index.html
|
||||
upload: build/index.html
|
||||
|
||||
- url: /(.*)
|
||||
static_files: build/\1
|
||||
upload: build/(.*)
|
||||
```
|
||||
|
||||
3. Activate cloud shell on your browser and create build folder
|
||||
```bash
|
||||
mkdir tooljet-assets
|
||||
```
|
||||
|
||||
4. Copy the uploaded files onto an assets folder which is to be served
|
||||
```bash
|
||||
gsutil rsync -r gs://your-bucket-name/path-to-assets ./tooljet-assets
|
||||
```
|
||||
|
||||
5. Deploy static assets to be served
|
||||
```bash
|
||||
cd tooljet-assets && gcloud app deploy
|
||||
```
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ Follow the steps below to deploy ToolJet on AWS EC2 instances.
|
|||
|
||||
3. Under the `Images` section, click on the `AMIs` button.
|
||||
|
||||
4. Now, from the AMI search page, select the search type as "Public Images" and input `AMI Name : tooljet_v0.5.11.ubuntu_bionic` in the search bar.
|
||||
4. Find the [ToolJet version](https://github.com/ToolJet/ToolJet/releases) you want to deploy. Now, from the AMI search page, select the search type as "Public Images" and input the version you'd want `AMI Name : tooljet_vX.X.X.ubuntu_bionic` in the search bar.
|
||||
|
||||
5. Select ToolJet's AMI and bootup an EC2 instance.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 8
|
||||
sidebar_position: 9
|
||||
---
|
||||
|
||||
# Environment variables
|
||||
|
|
@ -12,7 +12,7 @@ Both the ToolJet server and client requires some environment variables to start
|
|||
|
||||
| variable | description |
|
||||
| ------------ | --------------------------------------------------------------- |
|
||||
| TOOLJET_HOST | the public URL of ToolJet client ( eg: https://app.tooljet.io ) |
|
||||
| TOOLJET_HOST | the public URL of ToolJet client ( eg: https://app.tooljet.com ) |
|
||||
|
||||
#### Database configuration ( required )
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ This is used to set up for CSP headers and put trace info to be used with APM ve
|
|||
|
||||
| variable | description |
|
||||
| ------------------ | ----------------------------------------------------------- |
|
||||
| TOOLJET_SERVER_URL | the URL of ToolJet server ( eg: https://server.tooljet.io ) |
|
||||
| TOOLJET_SERVER_URL | the URL of ToolJet server ( eg: https://server.tooljet.com ) |
|
||||
|
||||
#### RELEASE VERSION ( optional)
|
||||
|
||||
|
|
@ -128,7 +128,7 @@ This is required when client is built separately.
|
|||
|
||||
| variable | description |
|
||||
| ------------------ | ----------------------------------------------------------- |
|
||||
| TOOLJET_SERVER_URL | the URL of ToolJet server ( eg: https://server.tooljet.io ) |
|
||||
| TOOLJET_SERVER_URL | the URL of ToolJet server ( eg: https://server.tooljet.com ) |
|
||||
|
||||
|
||||
#### Asset path ( optionally required )
|
||||
|
|
@ -138,4 +138,4 @@ This can be an absolute path, or relative to main HTML file.
|
|||
|
||||
| variable | description |
|
||||
| ------------------ | ----------------------------------------------------------- |
|
||||
| ASSET_PATH | the asset path for the website ( eg: https://app.tooljet.io/) |
|
||||
| ASSET_PATH | the asset path for the website ( eg: https://app.tooljet.com/) |
|
||||
|
|
|
|||
140
docs/docs/deployment/google-cloud-run.md
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
---
|
||||
sidebar_position: 7
|
||||
sidebar_label: Google Cloud Run
|
||||
---
|
||||
|
||||
# Deploying ToolJet on Google Cloud Run
|
||||
|
||||
:::info
|
||||
You should setup a PostgreSQL database manually to be used by ToolJet.
|
||||
:::
|
||||
|
||||
Follow the steps below to deploy ToolJet on Cloud run with `gcloud` CLI.
|
||||
|
||||
## Deploying ToolJet application
|
||||
|
||||
1. Cloud Run requires prebuilt image to be present within cloud registry. You can pull specific tooljet image from docker hub and then tag with your project to push it to cloud registry.
|
||||
|
||||
```bash
|
||||
gcloud auth configure-docker
|
||||
docker pull tooljet/tooljet-ce:latest
|
||||
docker tag tooljet/tooljet-ce:latest gcr.io/<replace-your-project-id>/tooljet/tooljet-ce:latest
|
||||
docker push gcr.io/<replace-your-project-id>/tooljet/tooljet-ce:latest
|
||||
```
|
||||
|
||||
2. Deploy new cloud run service
|
||||
|
||||
:::note
|
||||
This command takes the assumption that certain required environment has already been created in secrets. If you haven't created already then use the [secret manager](https://console.cloud.google.com/security/secret-manager).
|
||||
:::
|
||||
|
||||
```bash
|
||||
gcloud run deploy tooljet-ce --image gcr.io/<replace-your-project-id>/tooljet/tooljet-ce:latest \
|
||||
--allow-unauthenticated \
|
||||
--cpu 1 \
|
||||
--memory 1Gi \
|
||||
--min-instances 1 \
|
||||
--set-env-vars "TOOLJET_HOST=https://<replace-your-public-host>.com" \
|
||||
--set-secrets "PG_HOST=PG_HOST:latest" \
|
||||
--set-secrets "PG_DB=PG_DB:latest" \
|
||||
--set-secrets "PG_USER=PG_USER:latest" \
|
||||
--set-secrets "PG_PASS=PG_PASS:latest" \
|
||||
--set-secrets "PG_PORT=PG_PORT:latest" \
|
||||
--set-secrets "LOCKBOX_MASTER_KEY=LOCKBOX_MASTER_KEY:latest" \
|
||||
--set-secrets "SECRET_KEY_BASE=SECRET_KEY_BASE:latest" \
|
||||
--args "npm,run,start:prod"
|
||||
```
|
||||
|
||||
Update `TOOLJET_HOST` environment variable if you want to use the default url assigned with Cloud run after the initial deploy.
|
||||
|
||||
|
||||
:::tip
|
||||
If you are to use [Public IP](https://cloud.google.com/sql/docs/mysql/connect-run) for Cloud SQL, then database host connection needs to be done via unix socket. In that case you can set value for `PG_HOST` as `/cloudsql/<CLOUD_SQL_CONNECTION_NAME>`. Additionally you will also have to set these two flags with the above command:
|
||||
```
|
||||
--set-cloudsql-instances <replace-cloud-sql-connection-name> \
|
||||
--set-secrets "CLOUD_SQL_CONNECTION_NAME=CLOUD_SQL_CONNECTION_NAME:latest" \
|
||||
```
|
||||
:::
|
||||
|
||||
3. Create default user (Optional)
|
||||
|
||||
Signing up requires [SMTP configuration](https://docs.tooljet.com/docs/deployment/env-vars#smtp-configuration--optional-) to be done, but if you want to start off with default user you can run the command by modifying the `args` flag for a one time usage.
|
||||
|
||||
```bash
|
||||
gcloud run deploy <replace-service-name> \
|
||||
--image gcr.io/<replace-your-project-id>/tooljet/tooljet-ce:latest \
|
||||
--args "npm,run,--prefix,server,db:seed"
|
||||
```
|
||||
|
||||
The deployment will fail as it runs a seed script. Check logs to see that default user was created. Now run the following command to have the app deployed.
|
||||
|
||||
```bash
|
||||
gcloud run deploy <replace-service-name> \
|
||||
--image gcr.io/<replace-your-project-id>/tooljet/tooljet-ce:latest \
|
||||
--args "npm,run,start:prod"
|
||||
```
|
||||
|
||||
The default username of the admin is `dev@tooljet.io` and the password is `password`.
|
||||
|
||||
## Deploying only ToolJet server
|
||||
|
||||
1. Cloud Run requires prebuilt image to be present within cloud registry. You can pull specific tooljet image from docker hub and then tag with you project to push it to cloud registry.
|
||||
|
||||
```bash
|
||||
gcloud auth configure-docker
|
||||
docker pull tooljet/tooljet-server-ce:latest
|
||||
docker tag tooljet/tooljet-server-ce:latest gcr.io/<replace-your-project-id>/tooljet/tooljet-server-ce:latest
|
||||
docker push gcr.io/<replace-your-project-id>/tooljet/tooljet-server-ce:latest
|
||||
```
|
||||
|
||||
2. Deploy new cloud run service
|
||||
|
||||
:::note
|
||||
This command takes the assumption that certain required environment has already been created in secrets. If you haven't created already then use the [secret manager](https://console.cloud.google.com/security/secret-manager).
|
||||
:::
|
||||
|
||||
```bash
|
||||
gcloud run deploy tooljet-server-ce --image gcr.io/<replace-your-project-id>/tooljet/tooljet-server-ce:latest \
|
||||
--allow-unauthenticated \
|
||||
--cpu 1 \
|
||||
--memory 1Gi \
|
||||
--min-instances 1 \
|
||||
--set-env-vars "SERVE_CLIENT=false" \
|
||||
--set-env-vars "TOOLJET_HOST=https://<replace-your-public-host>.com" \
|
||||
--set-secrets "PG_HOST=PG_HOST:latest" \
|
||||
--set-secrets "PG_DB=PG_DB:latest" \
|
||||
--set-secrets "PG_USER=PG_USER:latest" \
|
||||
--set-secrets "PG_PASS=PG_PASS:latest" \
|
||||
--set-secrets "PG_PORT=PG_PORT:latest" \
|
||||
--set-secrets "LOCKBOX_MASTER_KEY=LOCKBOX_MASTER_KEY:latest" \
|
||||
--set-secrets "SECRET_KEY_BASE=SECRET_KEY_BASE:latest" \
|
||||
--args "npm,run,start:prod"
|
||||
```
|
||||
|
||||
:::tip
|
||||
If you are to use [Public IP](https://cloud.google.com/sql/docs/mysql/connect-run) for Cloud SQL, then database host connection needs to be done via unix socket. In that case you can set value for `PG_HOST` as `/cloudsql/<CLOUD_SQL_CONNECTION_NAME>`. Additionally you will also have to set these two flags with the above command:
|
||||
```
|
||||
--set-cloudsql-instances <replace-cloud-sql-connection-name> \
|
||||
--set-secrets "CLOUD_SQL_CONNECTION_NAME=CLOUD_SQL_CONNECTION_NAME:latest" \
|
||||
```
|
||||
:::
|
||||
|
||||
3. Create default user (Optional)
|
||||
|
||||
Signing up requires [SMTP configuration](https://docs.tooljet.com/docs/deployment/env-vars#smtp-configuration--optional-) to be done, but if you want to start off with default user you can run the command by modifying the `args` flag for a one time usage.
|
||||
|
||||
```bash
|
||||
gcloud run deploy <replace-service-name> \
|
||||
--image gcr.io/<replace-your-project-id>/tooljet/tooljet-server-ce:latest \
|
||||
--args "npm,run,db:seed"
|
||||
```
|
||||
|
||||
The deployment will fail as it runs a seed script. Check logs to see that default user was created. Now run the following command to have the app deployed.
|
||||
|
||||
```bash
|
||||
gcloud run deploy <replace-service-name> \
|
||||
--image gcr.io/<replace-your-project-id>/tooljet/tooljet-server-ce:latest \
|
||||
--args "npm,run,start:prod"
|
||||
```
|
||||
|
||||
The default username of the admin is `dev@tooljet.io` and the password is `password`.
|
||||
|
|
@ -31,7 +31,7 @@ Change the domain name to the domain/subdomain that you wish to use for ToolJet
|
|||
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).
|
||||
Make sure to edit the environment variables in the `deployment.yaml`. You can check out the available options [here](https://docs.tooljet.com/docs/deployment/env-vars).
|
||||
|
||||
4. Create k8s service
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ The references for datasources and widgets:
|
|||
- **[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.
|
||||
We have extensively documented the features of ToolJet, but in case you are stuck, please feel to mail us: hello@tooljet.com.
|
||||
If you are using ToolJet cloud, click on the chat icon at the bottom-left corner for instant help.
|
||||
If you have found a bug, please create a GitHub issue for the same.
|
||||
Also, feel free to join our highly active [slack community](https://join.slack.com/t/tooljet/shared_invite/zt-r2neyfcw-KD1COL6t2kgVTlTtAV5rtg).
|
||||
|
|
|
|||
|
|
@ -5,10 +5,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"/>
|
||||
|
|
|
|||
|
|
@ -26,4 +26,5 @@ Some of the actions that ToolJet Support are
|
|||
Show modal | Open any modal that you've added |
|
||||
Close modal | Close any modal that you've added if its already open |
|
||||
Copy to clipboard | Copy any available text that you see on the application to clipboard |
|
||||
Set localStorage | Set a key and corresponding value to localStorage |
|
||||
Set localStorage | Set a key and corresponding value to localStorage |
|
||||
Generate file | Construct file using data available in your application and let the user download it |
|
||||
|
|
@ -18,6 +18,6 @@ You will redirected to the visual app editor once the app has been created. The
|
|||
|
||||
The main components of an app:
|
||||
|
||||
- **[Widgets](https://docs.tooljet.io/docs/tutorial/adding-widget)** - UI components such as tables, buttons, dropdowns.
|
||||
- **[Data sources](https://docs.tooljet.io/docs/tutorial/adding-a-datasource)** - ToolJet can connect to databases, APIs and external services to fetch and modify data.
|
||||
- **[Queries](https://docs.tooljet.io/docs/tutorial/building-queries)** - Queries are used to access the connected datasources.
|
||||
- **[Widgets](https://docs.tooljet.com/docs/tutorial/adding-widget)** - UI components such as tables, buttons, dropdowns.
|
||||
- **[Data sources](https://docs.tooljet.com/docs/tutorial/adding-a-datasource)** - ToolJet can connect to databases, APIs and external services to fetch and modify data.
|
||||
- **[Queries](https://docs.tooljet.com/docs/tutorial/building-queries)** - Queries are used to access the connected datasources.
|
||||
|
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Button
|
||||
|
||||
Button widget can be used to take actions.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
sidebar_position: 22
|
||||
---
|
||||
|
||||
# Calendar
|
||||
Calendar widget comes with the following features:
|
||||
- Day, month and week level views
|
||||
|
|
@ -49,6 +45,7 @@ Assuming that you set the date format to `MM-DD-YYYY HH:mm:ss A Z`, setting the
|
|||
| allDay | Optional. Qualifies the event as an 'All day event', which will pin it to date headers on `day` and `week` level views |
|
||||
| tooltip | Tooltip which will be display when the user hovers over the event |
|
||||
| color | Background color of the event, any css supported color name or hex code can be used |
|
||||
| textColor | Color of the event title, any css supported color name or hex code can be used |
|
||||
| textOrientation | Optional. If it is set to `vertical`, the title of the event will be oriented vertically. |
|
||||
| resourceId | Applicable only if you're using resource scheduling. This is the id of the resource to which this event correspond to. |
|
||||
|
||||
|
|
@ -80,6 +77,8 @@ If we specify the `resourceId` of any of the events as `1`, then that event will
|
|||
Determines whether the calendar would display a `day`, a `week` or a `month`.
|
||||
Setting this property to anything other than these values will make the calendar default to `month` view.
|
||||
|
||||
The view that is currently selected will be exposed as the variable `currentView`.
|
||||
|
||||
#### Show toolbar
|
||||
|
||||
Determines whether the calendar toolbar should be displayed or not.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Chart
|
||||
|
||||
Chart widget takes the chart type, data and styles to draw charts using Plotly.js.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Checkbox
|
||||
|
||||
Checkbox widget can be used for allowing the users to make a binary choice, e.g,. unselected or selected.
|
||||
|
|
|
|||
150
docs/docs/widgets/code-editor.md
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
# Code Editor
|
||||
|
||||
Code Editor widget is a versatile text editor for editing code and supports several languages.
|
||||
|
||||
:::info
|
||||
<details>
|
||||
<summary>Supporting all commonly used languages.</summary>
|
||||
<ul>
|
||||
<li>APL</li>
|
||||
<li>ASN.1</li>
|
||||
<li>Asterisk dialplan</li>
|
||||
<li>Brainfuck</li>
|
||||
<li>C, C++, C#</li>
|
||||
<li>Ceylon</li>
|
||||
<li>Clojure</li>
|
||||
<li>Closure Stylesheets (GSS)</li>
|
||||
<li>CMake</li>
|
||||
<li>COBOL</li>
|
||||
<li>CoffeeScript</li>
|
||||
<li>Common Lisp</li>
|
||||
<li>Crystal</li>
|
||||
<li>CSS</li>
|
||||
<li>Cypher</li>
|
||||
<li>Cython</li>
|
||||
<li>D</li>
|
||||
<li>Dart</li>
|
||||
<li>Django (templating language)</li>
|
||||
<li>Dockerfile</li>
|
||||
<li>diff</li>
|
||||
<li>DTD</li>
|
||||
<li>Dylan</li>
|
||||
<li>EBNF</li>
|
||||
<li>ECL</li>
|
||||
<li>Eiffel</li>
|
||||
<li>Elixir</li>
|
||||
<li>Elm</li>
|
||||
<li>Erlang</li>
|
||||
<li>Factor</li>
|
||||
<li>FCL</li>
|
||||
<li>Forth</li>
|
||||
<li>Fortran</li>
|
||||
<li>F#</li>
|
||||
<li>Gas (AT&T-style assembly)</li>
|
||||
<li>Gherkin</li>
|
||||
<li>Go</li>
|
||||
<li>Groovy</li>
|
||||
<li>HAML</li>
|
||||
<li>Handlebars</li>
|
||||
<li>Haskell</li>
|
||||
<li>Haxe</li>
|
||||
<li>HTML embedded (JSP, ASP.NET)</li>
|
||||
<li>HTML mixed-mode</li>
|
||||
<li>HTTP</li>
|
||||
<li>IDL</li>
|
||||
<li>Java</li>
|
||||
<li>JavaScript (JSX)</li>
|
||||
<li>Jinja2</li>
|
||||
<li>Julia</li>
|
||||
<li>Kotlin</li>
|
||||
<li>LESS</li>
|
||||
<li>LiveScript</li>
|
||||
<li>Lua</li>
|
||||
<li>Markdown (GitHub-flavour)</li>
|
||||
<li>Mathematica</li>
|
||||
<li>mbox</li>
|
||||
<li>mIRC</li>
|
||||
<li>Modelica</li>
|
||||
<li>MscGen</li>
|
||||
<li>MUMPS</li>
|
||||
<li>Nginx</li>
|
||||
<li>NSIS</li>
|
||||
<li>N-Triples/N-Quads</li>
|
||||
<li>Objective C</li>
|
||||
<li>OCaml</li>
|
||||
<li>Octave (MATLAB)</li>
|
||||
<li>Oz</li>
|
||||
<li>Pascal</li>
|
||||
<li>PEG.js</li>
|
||||
<li>Perl</li>
|
||||
<li>PGP (ASCII armor)</li>
|
||||
<li>PHP</li>
|
||||
<li>Pig Latin</li>
|
||||
<li>PowerShell</li>
|
||||
<li>Properties files</li>
|
||||
<li>ProtoBuf</li>
|
||||
<li>Pug</li>
|
||||
<li>Puppet</li>
|
||||
<li>Python</li>
|
||||
<li>Q</li>
|
||||
<li>R</li>
|
||||
<li>RPM</li>
|
||||
<li>reStructuredText</li>
|
||||
<li>Ruby</li>
|
||||
<li>Rust</li>
|
||||
<li>SAS</li>
|
||||
<li>Sass</li>
|
||||
<li>Spreadsheet</li>
|
||||
<li>Scala</li>
|
||||
<li>Scheme</li>
|
||||
<li>SCSS</li>
|
||||
<li>Shell</li>
|
||||
<li>Sieve</li>
|
||||
<li>Slim</li>
|
||||
<li>Smalltalk</li>
|
||||
<li>Smarty</li>
|
||||
<li>Solr</li>
|
||||
<li>Soy</li>
|
||||
<li>Stylus</li>
|
||||
<li>SQL (several dialects)</li>
|
||||
<li>SPARQL</li>
|
||||
<li>Squirrel</li>
|
||||
<li>Swift</li>
|
||||
<li>sTeX, LaTeX</li>
|
||||
<li>Tcl</li>
|
||||
<li>Textile</li>
|
||||
<li>Tiddlywiki</li>
|
||||
<li>Tiki wiki</li>
|
||||
<li>TOML</li>
|
||||
<li>Tornado (templating language)</li>
|
||||
<li>troff (for manpages)</li>
|
||||
<li>TTCN</li>
|
||||
<li>TTCN Configuration</li>
|
||||
<li>Turtle</li>
|
||||
<li>Twig</li>
|
||||
<li>VB.NET</li>
|
||||
<li>VBScript</li>
|
||||
<li>Velocity</li>
|
||||
<li>Verilog/SystemVerilog</li>
|
||||
<li>VHDL</li>
|
||||
<li>Vue.js app</li>
|
||||
<li>Web IDL</li>
|
||||
<li>WebAssembly Text Format</li>
|
||||
<li>XML/HTML</li>
|
||||
<li>XQuery</li>
|
||||
<li>Yacas</li>
|
||||
<li>YAML</li>
|
||||
<li>YAML frontmatter</li>
|
||||
<li>Z80</li>
|
||||
</ul>
|
||||
</details>
|
||||
:::
|
||||
|
||||
<img class="screenshot-full" src="/img/widgets/code-editor/code-editor.png" alt="ToolJet - Widget Reference - Code Editor" height="420"/>
|
||||
|
||||
|
||||
| properties | description |
|
||||
| ----------- | ----------- |
|
||||
| Placeholder | It specifies a hint that describes the expected value.|
|
||||
| Mode | It is used to specify the language to be used for the code-editor.|
|
||||
| Show Line Number | This property is used to show or hide line numbers to the left of the editor.|
|
||||
|
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Date-range picker
|
||||
|
||||
The date-range picker widget allows users to select a range of dates.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# Datepicker
|
||||
|
||||
The Datepicker widget allows users to select a single value for date and time from a pre-determined set.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
# Divider
|
||||
|
||||
Divider widget is used to add separator between components.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
sidebar_position: 7
|
||||
---
|
||||
|
||||
# Dropdown
|
||||
|
||||
The Dropdown widget can be used to collect user input from a list of options.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
# Filepicker
|
||||
|
||||
Filepicker widget allows the user to drag and drop files or upload files by browsing the filesystem and selecting one or more files in a directory.
|
||||
|
|
|
|||
10
docs/docs/widgets/iframe.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Iframe
|
||||
|
||||
Iframe widget is used to embed another HTML page into the current one and display iframes in your app.
|
||||
|
||||
<img class="screenshot-full" src="/img/widgets/iframe/iframe.gif" alt="ToolJet - Widget Reference - Iframe " height="420"/>
|
||||
|
||||
#### Properties
|
||||
| properties | description |
|
||||
| ----------- | ----------- |
|
||||
| URL | Sets the **URL** of the page to embed. |
|
||||
|
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
sidebar_position: 8
|
||||
---
|
||||
|
||||
# Image
|
||||
|
||||
Image widget is used to display images in your app.
|
||||
|
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
sidebar_position: 9
|
||||
---
|
||||
|
||||
# Map
|
||||
|
||||
The map widget can be used to pick or select locations on the Google map with the location's coordinates.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
sidebar_position: 10
|
||||
---
|
||||
|
||||
# Modal
|
||||
|
||||
Modal widget renders in front of a backdrop, and it blocks interaction with the rest of the application until the modal is closed. It can be used to add dialog boxes to your app for lightboxes, user notifications, forms, etc.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
sidebar_position: 11
|
||||
---
|
||||
|
||||
# Multiselect
|
||||
|
||||
Multiselect widget can be used to collect multiple user inputs from a list of options.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
sidebar_position: 12
|
||||
---
|
||||
|
||||
# Number Input
|
||||
|
||||
Number Input widget lets users enter and change numbers.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
sidebar_position: 13
|
||||
---
|
||||
|
||||
# QR Scanner
|
||||
Scan QR codes using device camera and hold the data they carry.
|
||||
<img class="screenshot-full" src="/img/widgets/qr-scanner/qr-scanner.jpeg" alt="ToolJet - QR Scanner" height="420"/>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
sidebar_position: 14
|
||||
---
|
||||
|
||||
# Radio Button
|
||||
|
||||
Radio button widget can be used to select one option from a group of options.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
sidebar_position: 15
|
||||
---
|
||||
|
||||
# Rich Text Editor
|
||||
|
||||
Rich Text Editor can be used to enter and edit the text in HTML format.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
sidebar_position: 16
|
||||
---
|
||||
|
||||
# Star rating
|
||||
|
||||
Star rating widget can be used to display as well as input ratings. The widget supports half stars, and the number of stars can be set too.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
sidebar_position: 17
|
||||
---
|
||||
|
||||
# Table
|
||||
|
||||
Tables can be used for both displaying and editing data.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
sidebar_position: 18
|
||||
---
|
||||
|
||||
# Text Input
|
||||
|
||||
Text Input widget let users enter and edit text.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
sidebar_position: 19
|
||||
---
|
||||
|
||||
# Text
|
||||
|
||||
Text widget can be used to display text.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
sidebar_position: 20
|
||||
---
|
||||
|
||||
# Textarea
|
||||
|
||||
Textarea widgets let users enter and edit just text like [Text Input](/docs/widgets/text-input) widget.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
---
|
||||
sidebar_position: 21
|
||||
---
|
||||
|
||||
# Toggle Switch
|
||||
|
||||
The toggle switch widget allows the user to change a setting between two states.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/** @type {import('@docusaurus/types').DocusaurusConfig} */
|
||||
module.exports = {
|
||||
title: 'ToolJet - Documentation',
|
||||
tagline: 'Build and deploy internal tools.',
|
||||
url: 'https://docs.tooljet.io',
|
||||
tagline: 'Low-code framework to Build internal tools and business apps.',
|
||||
url: 'https://docs.tooljet.com',
|
||||
baseUrl: '/',
|
||||
onBrokenLinks: 'ignore',
|
||||
onBrokenMarkdownLinks: 'warn',
|
||||
|
|
@ -10,6 +10,19 @@ module.exports = {
|
|||
organizationName: 'ToolJet', // Usually your GitHub org/user name.
|
||||
projectName: 'ToolJet', // Usually your repo name.
|
||||
themeConfig: {
|
||||
announcementBar: {
|
||||
id: 'support_us',
|
||||
content:
|
||||
'⭐️ If you like ToolJet, give it a star on GitHub <a target="_blank" rel="noopener noreferrer" href="https://github.com/ToolJet/ToolJet">GitHub</a> and follow us on <a target="_blank" rel="noopener noreferrer" href="https://twitter.com/ToolJet">Twitter</a>',
|
||||
backgroundColor: '#4D72DA',
|
||||
textColor: '#ffffff',
|
||||
isCloseable: true,
|
||||
},
|
||||
gtag: {
|
||||
trackingID: process.env.GA_MID,
|
||||
// Optional fields.
|
||||
anonymizeIP: true, // Should IPs be anonymized?
|
||||
},
|
||||
colorMode: {
|
||||
switchConfig: {
|
||||
darkIcon: '\00a0 ',
|
||||
|
|
|
|||
18273
docs/package-lock.json
generated
|
|
@ -14,8 +14,9 @@
|
|||
"write-heading-ids": "docusaurus write-heading-ids"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "2.0.0-alpha.73",
|
||||
"@docusaurus/preset-classic": "2.0.0-alpha.73",
|
||||
"@docusaurus/core": "^2.0.0-beta.9",
|
||||
"@docusaurus/plugin-google-gtag": "^2.0.0-alpha.73",
|
||||
"@docusaurus/preset-classic": "^2.0.0-beta.9",
|
||||
"@mdx-js/react": "^1.6.21",
|
||||
"clsx": "^1.1.1",
|
||||
"react": "^17.0.1",
|
||||
|
|
@ -33,4 +34,4 @@
|
|||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
BIN
docs/static/img/datasource-reference/aws-s3-connect.png
vendored
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
docs/static/img/datasource-reference/aws-s3-query.png
vendored
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
docs/static/img/datasource-reference/gcs-connect.png
vendored
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
docs/static/img/datasource-reference/gcs-query.png
vendored
Normal file
|
After Width: | Height: | Size: 107 KiB |
2
docs/static/img/logo.svg
vendored
|
|
@ -2,7 +2,7 @@
|
|||
<svg width="215px" height="190px" viewBox="0 0 215 190" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>logo</title>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="logo" fill="#3C92DC" fill-rule="nonzero">
|
||||
<g id="logo" fill="#4D72DA" fill-rule="nonzero">
|
||||
<path d="M214.605,188.381 C214.605,189.21 213.926,189.889 213.097,189.889 C213.082,189.889 213.082,189.889 213.068,189.889 C212.735,189.889 212.388,189.785 212.116,189.558 C195.325,175.876 160.494,166.296 121.197,164.532 C120.411,164.502 119.778,163.868 119.749,163.098 C116.369,87.596 110.274,65.933 107.515,65.043 C104.844,65.933 98.749,87.596 95.37,163.098 C95.34,163.868 94.706,164.501 93.922,164.532 C54.625,166.297 19.792,175.877 3.002,189.558 C2.443,190.01 1.629,189.996 1.086,189.528 C0.528,189.06 0.378,188.275 0.739,187.642 L106.247,0.824 C106.775,-0.127 108.344,-0.127 108.872,0.824 L214.243,187.4 C214.47,187.672 214.605,188.003 214.605,188.381 Z" id="Path"></path>
|
||||
<path d="M115.715,166.383 C112.789,187.097 109.954,189.889 107.573,189.889 C105.191,189.889 102.358,187.097 99.433,166.383 C99.38,166.013 99.486,165.642 99.737,165.351 C99.975,165.06 100.333,164.888 100.703,164.888 C102.848,164.823 105.097,164.796 107.585,164.796 C110.06,164.796 112.31,164.822 114.44,164.888 C114.812,164.888 115.168,165.06 115.407,165.351 C115.662,165.642 115.768,166.013 115.715,166.383 Z" id="Path"></path>
|
||||
</g>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
BIN
docs/static/img/widgets/code-editor/code-editor.png
vendored
Normal file
|
After Width: | Height: | Size: 199 KiB |
BIN
docs/static/img/widgets/iframe/iframe.gif
vendored
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
6389
docs/yarn.lock
6
frontend/assets/images/icons/widgets/codeeditor.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-code" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<polyline points="7 8 3 12 7 16"></polyline>
|
||||
<polyline points="17 8 21 12 17 16"></polyline>
|
||||
<line x1="14" y1="4" x2="10" y2="20"></line>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 439 B |
8
frontend/assets/images/icons/widgets/iframe.svg
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-browser" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<rect x="4" y="4" width="16" height="16" rx="1" />
|
||||
<line x1="4" y1="8" x2="20" y2="8" />
|
||||
<line x1="8" y1="4" x2="8" y2="8" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 422 B |
|
|
@ -1,9 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-forms" width="24" height="24" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M12 3a3 3 0 0 0 -3 3v12a3 3 0 0 0 3 3"></path>
|
||||
<path d="M6 3a3 3 0 0 1 3 3v12a3 3 0 0 1 -3 3"></path>
|
||||
<path d="M13 7h7a1 1 0 0 1 1 1v8a1 1 0 0 1 -1 1h-7"></path>
|
||||
<path d="M5 7h-1a1 1 0 0 0 -1 1v8a1 1 0 0 0 1 1h1"></path>
|
||||
<path d="M17 12h.01"></path>
|
||||
<path d="M13 12h.01"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 598 B |
11
frontend/assets/images/icons/widgets/passwordinput.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-forms" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M12 3a3 3 0 0 0 -3 3v12a3 3 0 0 0 3 3" />
|
||||
<path d="M6 3a3 3 0 0 1 3 3v12a3 3 0 0 1 -3 3" />
|
||||
<path d="M13 7h7a1 1 0 0 1 1 1v8a1 1 0 0 1 -1 1h-7" />
|
||||
<path d="M5 7h-1a1 1 0 0 0 -1 1v8a1 1 0 0 0 1 1h1" />
|
||||
<path d="M17 12h.01" />
|
||||
<path d="M13 12h.01" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 558 B |
6
frontend/assets/images/icons/widgets/tabs.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path fill="#444" d="M14 4v-2h-14v12h16v-10h-2zM10 3h3v1h-3v-1zM6 3h3v1h-3v-1zM15 13h-14v-10h4v2h10v8z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 403 B |
43
frontend/assets/images/sso-buttons/google.svg
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="46px" height="46px" viewBox="0 0 46 46" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: Sketch 3.3.3 (12081) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>btn_google_light_normal_ios</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="filter-1">
|
||||
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
|
||||
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.168 0" in="shadowBlurOuter1" type="matrix" result="shadowMatrixOuter1"></feColorMatrix>
|
||||
<feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter2"></feOffset>
|
||||
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter2" result="shadowBlurOuter2"></feGaussianBlur>
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.084 0" in="shadowBlurOuter2" type="matrix" result="shadowMatrixOuter2"></feColorMatrix>
|
||||
<feMerge>
|
||||
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
|
||||
<feMergeNode in="shadowMatrixOuter2"></feMergeNode>
|
||||
<feMergeNode in="SourceGraphic"></feMergeNode>
|
||||
</feMerge>
|
||||
</filter>
|
||||
<rect id="path-2" x="0" y="0" width="40" height="40" rx="2"></rect>
|
||||
</defs>
|
||||
<g id="Google-Button" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="9-PATCH" sketch:type="MSArtboardGroup" transform="translate(-608.000000, -160.000000)"></g>
|
||||
<g id="btn_google_light_normal" sketch:type="MSArtboardGroup" transform="translate(-1.000000, -1.000000)">
|
||||
<g id="button" sketch:type="MSLayerGroup" transform="translate(4.000000, 4.000000)" filter="url(#filter-1)">
|
||||
<g id="button-bg">
|
||||
<use fill="#FFFFFF" fill-rule="evenodd" sketch:type="MSShapeGroup" xlink:href="#path-2"></use>
|
||||
<use fill="none" xlink:href="#path-2"></use>
|
||||
<use fill="none" xlink:href="#path-2"></use>
|
||||
<use fill="none" xlink:href="#path-2"></use>
|
||||
</g>
|
||||
</g>
|
||||
<g id="logo_googleg_48dp" sketch:type="MSLayerGroup" transform="translate(15.000000, 15.000000)">
|
||||
<path d="M17.64,9.20454545 C17.64,8.56636364 17.5827273,7.95272727 17.4763636,7.36363636 L9,7.36363636 L9,10.845 L13.8436364,10.845 C13.635,11.97 13.0009091,12.9231818 12.0477273,13.5613636 L12.0477273,15.8195455 L14.9563636,15.8195455 C16.6581818,14.2527273 17.64,11.9454545 17.64,9.20454545 L17.64,9.20454545 Z" id="Shape" fill="#4285F4" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M9,18 C11.43,18 13.4672727,17.1940909 14.9563636,15.8195455 L12.0477273,13.5613636 C11.2418182,14.1013636 10.2109091,14.4204545 9,14.4204545 C6.65590909,14.4204545 4.67181818,12.8372727 3.96409091,10.71 L0.957272727,10.71 L0.957272727,13.0418182 C2.43818182,15.9831818 5.48181818,18 9,18 L9,18 Z" id="Shape" fill="#34A853" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M3.96409091,10.71 C3.78409091,10.17 3.68181818,9.59318182 3.68181818,9 C3.68181818,8.40681818 3.78409091,7.83 3.96409091,7.29 L3.96409091,4.95818182 L0.957272727,4.95818182 C0.347727273,6.17318182 0,7.54772727 0,9 C0,10.4522727 0.347727273,11.8268182 0.957272727,13.0418182 L3.96409091,10.71 L3.96409091,10.71 Z" id="Shape" fill="#FBBC05" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M9,3.57954545 C10.3213636,3.57954545 11.5077273,4.03363636 12.4404545,4.92545455 L15.0218182,2.34409091 C13.4631818,0.891818182 11.4259091,0 9,0 C5.48181818,0 2.43818182,2.01681818 0.957272727,4.95818182 L3.96409091,7.29 C4.67181818,5.16272727 6.65590909,3.57954545 9,3.57954545 L9,3.57954545 Z" id="Shape" fill="#EA4335" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M0,0 L18,0 L18,18 L0,18 L0,0 Z" id="Shape" sketch:type="MSShapeGroup"></path>
|
||||
</g>
|
||||
<g id="handles_square" sketch:type="MSLayerGroup"></g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
35
frontend/ee/components/LoginPage/GoogleSSOLoginButton.jsx
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import React from 'react';
|
||||
import GoogleLogin from 'react-google-login';
|
||||
import { authenticationService } from '@/_services';
|
||||
|
||||
export default function GoogleSSOLoginButton(props) {
|
||||
const googleSSOSuccessHandler = (googleUser) => {
|
||||
const idToken = googleUser.getAuthResponse().id_token;
|
||||
authenticationService.signInViaOAuth(idToken).then(props.authSuccessHandler).catch(props.authFailureHandler);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-2">
|
||||
<GoogleLogin
|
||||
clientId={window.public_config.SSO_GOOGLE_OAUTH2_CLIENT_ID}
|
||||
buttonText="Login"
|
||||
onSuccess={googleSSOSuccessHandler}
|
||||
onFailure={props.authFailureHandler}
|
||||
cookiePolicy={'single_host_origin'}
|
||||
render={(renderProps) => (
|
||||
<div>
|
||||
<button {...renderProps} className="btn border-0 rounded-2">
|
||||
<img
|
||||
onClick={renderProps.onClick}
|
||||
disabled={renderProps.disabled}
|
||||
src="/assets/images/sso-buttons/google.svg"
|
||||
className="h-4"
|
||||
/>
|
||||
<span className="px-1">Sign in with Google</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
25
frontend/package-lock.json
generated
|
|
@ -5,7 +5,6 @@
|
|||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "frontend",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.4.3",
|
||||
|
|
@ -52,6 +51,7 @@
|
|||
"react-dom": "^16.14.0",
|
||||
"react-dropzone": "^11.4.2",
|
||||
"react-easy-sort": "^0.2.1",
|
||||
"react-google-login": "^5.2.2",
|
||||
"react-hot-toast": "^2.1.1",
|
||||
"react-json-view": "^1.21.3",
|
||||
"react-lazyload": "^3.2.0",
|
||||
|
|
@ -17942,6 +17942,19 @@
|
|||
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz",
|
||||
"integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew=="
|
||||
},
|
||||
"node_modules/react-google-login": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/react-google-login/-/react-google-login-5.2.2.tgz",
|
||||
"integrity": "sha512-JUngfvaSMcOuV0lFff7+SzJ2qviuNMQdqlsDJkUM145xkGPVIfqWXq9Ui+2Dr6jdJWH5KYdynz9+4CzKjI5u6g==",
|
||||
"dependencies": {
|
||||
"@types/react": "*",
|
||||
"prop-types": "^15.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16 || ^17",
|
||||
"react-dom": "^16 || ^17"
|
||||
}
|
||||
},
|
||||
"node_modules/react-hot-toast": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.1.1.tgz",
|
||||
|
|
@ -21737,6 +21750,7 @@
|
|||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz",
|
||||
"integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==",
|
||||
"deprecated": "This SVGO version is no longer supported. Upgrade to v2.x.x.",
|
||||
"dependencies": {
|
||||
"chalk": "^2.4.1",
|
||||
"coa": "^2.0.2",
|
||||
|
|
@ -38523,6 +38537,15 @@
|
|||
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz",
|
||||
"integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew=="
|
||||
},
|
||||
"react-google-login": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/react-google-login/-/react-google-login-5.2.2.tgz",
|
||||
"integrity": "sha512-JUngfvaSMcOuV0lFff7+SzJ2qviuNMQdqlsDJkUM145xkGPVIfqWXq9Ui+2Dr6jdJWH5KYdynz9+4CzKjI5u6g==",
|
||||
"requires": {
|
||||
"@types/react": "*",
|
||||
"prop-types": "^15.6.0"
|
||||
}
|
||||
},
|
||||
"react-hot-toast": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.1.1.tgz",
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@
|
|||
"react-dom": "^16.14.0",
|
||||
"react-dropzone": "^11.4.2",
|
||||
"react-easy-sort": "^0.2.1",
|
||||
"react-google-login": "^5.2.2",
|
||||
"react-hot-toast": "^2.1.1",
|
||||
"react-json-view": "^1.21.3",
|
||||
"react-lazyload": "^3.2.0",
|
||||
|
|
|
|||
|
|
@ -45,4 +45,13 @@ export const ActionTypes = [
|
|||
{ name: 'value', type: 'code', default: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Generate file',
|
||||
id: 'generate-file',
|
||||
options: [
|
||||
{ name: 'fileType', type: 'text', default: '' },
|
||||
{ name: 'fileName', type: 'text', default: '' },
|
||||
{ name: 'data', type: 'code', default: '{{[]}}' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { TextInput } from './Components/TextInput';
|
|||
import { NumberInput } from './Components/NumberInput';
|
||||
import { TextArea } from './Components/TextArea';
|
||||
import { Container } from './Components/Container';
|
||||
import { Tabs } from './Components/Tabs';
|
||||
import { RichTextEditor } from './Components/RichTextEditor';
|
||||
import { DropDown } from './Components/DropDown';
|
||||
import { Checkbox } from './Components/Checkbox';
|
||||
|
|
@ -24,6 +25,8 @@ import { Divider } from './Components/Divider';
|
|||
import { FilePicker } from './Components/FilePicker';
|
||||
import { PasswordInput } from './Components/PasswordInput';
|
||||
import { Calendar } from './Components/Calendar';
|
||||
import { IFrame } from './Components/IFrame';
|
||||
import { CodeEditor } from './Components/CodeEditor';
|
||||
import { renderTooltip } from '../_helpers/appUtils';
|
||||
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
|
||||
import '@/_styles/custom.scss';
|
||||
|
|
@ -39,6 +42,7 @@ const AllComponents = {
|
|||
Table,
|
||||
TextArea,
|
||||
Container,
|
||||
Tabs,
|
||||
RichTextEditor,
|
||||
DropDown,
|
||||
Checkbox,
|
||||
|
|
@ -56,6 +60,8 @@ const AllComponents = {
|
|||
FilePicker,
|
||||
PasswordInput,
|
||||
Calendar,
|
||||
IFrame,
|
||||
CodeEditor,
|
||||
};
|
||||
|
||||
export const Box = function Box({
|
||||
|
|
@ -76,6 +82,7 @@ export const Box = function Box({
|
|||
containerProps,
|
||||
darkMode,
|
||||
removeComponent,
|
||||
canvasWidth,
|
||||
mode,
|
||||
}) {
|
||||
const backgroundColor = yellow ? 'yellow' : '';
|
||||
|
|
@ -132,6 +139,7 @@ export const Box = function Box({
|
|||
containerProps={containerProps}
|
||||
darkMode={darkMode}
|
||||
removeComponent={removeComponent}
|
||||
canvasWidth={canvasWidth}
|
||||
properties={resolvedProperties}
|
||||
exposedVariables={exposedVariables}
|
||||
styles={resolvedStyles}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useEffect, useState, memo } from 'react';
|
||||
|
||||
export const BoxDragPreview = memo(function BoxDragPreview({ item, currentLayout }) {
|
||||
export const BoxDragPreview = memo(function BoxDragPreview({ item, currentLayout, canvasWidth }) {
|
||||
const [tickTock, setTickTock] = useState(false);
|
||||
|
||||
useEffect(
|
||||
|
|
@ -22,7 +22,7 @@ export const BoxDragPreview = memo(function BoxDragPreview({ item, currentLayout
|
|||
return (
|
||||
<div
|
||||
className="resizer-active draggable-box"
|
||||
style={{ height, width, border: 'solid 1px rgb(70, 165, 253)', padding: '2px' }}
|
||||
style={{ height, width: (width * canvasWidth) / 43, border: 'solid 1px rgb(70, 165, 253)', padding: '2px' }}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
|
|
|
|||
|
|
@ -115,9 +115,9 @@ export function CodeHinter({
|
|||
</animated.div>
|
||||
);
|
||||
};
|
||||
|
||||
enablePreview = enablePreview ?? true;
|
||||
return (
|
||||
<>
|
||||
<div style={{ width: '100%' }}>
|
||||
<div
|
||||
className={`code-hinter ${className || 'codehinter-default-input'}`}
|
||||
key={suggestions.length}
|
||||
|
|
@ -140,6 +140,6 @@ export function CodeHinter({
|
|||
/>
|
||||
</div>
|
||||
{enablePreview && getPreview()}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import Button from '@/_ui/Button';
|
|||
import useShortcuts from '@/_hooks/use-shortcuts';
|
||||
import usePopover from '@/_hooks/use-popover';
|
||||
|
||||
function CommentFooter({ editComment = '', editCommentId, handleSubmit }) {
|
||||
function CommentFooter({ users, editComment = '', editCommentId, handleSubmit }) {
|
||||
const [comment, setComment] = React.useState(editComment);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [open, trigger, content, setOpen] = usePopover(false);
|
||||
|
|
@ -38,7 +38,12 @@ function CommentFooter({ editComment = '', editCommentId, handleSubmit }) {
|
|||
<div className="card-footer">
|
||||
<div className="row align-items-center">
|
||||
<div className="col-8">
|
||||
<TextareaMentions value={comment} setValue={setComment} placeholder="Type your comment here" />
|
||||
<TextareaMentions
|
||||
users={users}
|
||||
value={comment}
|
||||
setValue={setComment}
|
||||
placeholder="Type your comment here"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-1 cursor-pointer">
|
||||
<svg {...trigger} width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
|
|
|
|||
|
|
@ -11,7 +11,18 @@ import usePopover from '@/_hooks/use-popover';
|
|||
import { commentsService } from '@/_services';
|
||||
import useRouter from '@/_hooks/use-router';
|
||||
|
||||
const Comment = ({ socket, x, y, threadId, user = {}, isResolved, fetchThreads, appVersionsId }) => {
|
||||
const Comment = ({
|
||||
socket,
|
||||
x,
|
||||
y,
|
||||
threadId,
|
||||
user = {},
|
||||
isResolved,
|
||||
fetchThreads,
|
||||
appVersionsId,
|
||||
canvasWidth,
|
||||
users,
|
||||
}) => {
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [editComment, setEditComment] = React.useState('');
|
||||
const [editCommentId, setEditCommentId] = React.useState('');
|
||||
|
|
@ -106,7 +117,7 @@ const Comment = ({ socket, x, y, threadId, user = {}, isResolved, fetchThreads,
|
|||
id={`thread-${threadId}`}
|
||||
className={cx('comments cursor-move', { open: open })}
|
||||
style={{
|
||||
transform: `translate(${x}px, ${y}px)`,
|
||||
transform: `translate(${(x * canvasWidth) / 100}px, ${y}px)`,
|
||||
...commentFadeStyle,
|
||||
}}
|
||||
onDragStart={() => setOpen(false)}
|
||||
|
|
@ -156,6 +167,7 @@ const Comment = ({ socket, x, y, threadId, user = {}, isResolved, fetchThreads,
|
|||
thread={thread}
|
||||
/>
|
||||
<CommentFooter
|
||||
users={users}
|
||||
editComment={editComment}
|
||||
editCommentId={editCommentId}
|
||||
handleSubmit={editCommentId ? handleEdit : handleSubmit}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,29 @@
|
|||
import '@/_styles/editor/comments.scss';
|
||||
|
||||
import React from 'react';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { isEmpty, capitalize } from 'lodash';
|
||||
|
||||
import Comment from './Comment';
|
||||
import { commentsService } from '@/_services';
|
||||
import { commentsService, organizationService } from '@/_services';
|
||||
|
||||
import useRouter from '@/_hooks/use-router';
|
||||
|
||||
const Comments = ({ newThread = {}, appVersionsId, socket }) => {
|
||||
const Comments = ({ newThread = {}, appVersionsId, socket, canvasWidth }) => {
|
||||
const [threads, setThreads] = React.useState([]);
|
||||
const router = useRouter();
|
||||
|
||||
const [users, setUsers] = React.useState([]);
|
||||
|
||||
React.useEffect(() => {
|
||||
organizationService.getUsers(null).then((data) => {
|
||||
const _users = data.users.map((u) => ({
|
||||
id: u.id,
|
||||
display: `${capitalize(u.first_name)} ${capitalize(u.last_name)}`,
|
||||
}));
|
||||
setUsers(_users);
|
||||
});
|
||||
}, []);
|
||||
|
||||
async function fetchData() {
|
||||
const { data } = await commentsService.getThreads(router.query.id, appVersionsId);
|
||||
setThreads(data);
|
||||
|
|
@ -43,6 +55,8 @@ const Comments = ({ newThread = {}, appVersionsId, socket }) => {
|
|||
fetchThreads={fetchData}
|
||||
socket={socket}
|
||||
threadId={id}
|
||||
canvasWidth={canvasWidth}
|
||||
users={users}
|
||||
{...thread}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ export const Button = function Button({ width, height, component, currentState,
|
|||
const computedStyles = {
|
||||
backgroundColor,
|
||||
color,
|
||||
width: '100%',
|
||||
borderRadius: `${parsedBorderRadius}px`,
|
||||
width,
|
||||
height,
|
||||
display: parsedWidgetVisibility ? '' : 'none',
|
||||
'--tblr-btn-color-darker': tinycolor(backgroundColor).darken(8).toString(),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Calendar as ReactCalendar, momentLocalizer } from 'react-big-calendar';
|
||||
import moment from 'moment';
|
||||
import 'react-big-calendar/lib/css/react-big-calendar.css';
|
||||
import { CalendarEventPopover } from './CalendarPopover';
|
||||
|
||||
const localizer = momentLocalizer(moment);
|
||||
|
||||
|
|
@ -15,18 +16,31 @@ const parseDate = (date, dateFormat) => moment(date, dateFormat).toDate();
|
|||
|
||||
const allowedCalendarViews = ['month', 'week', 'day'];
|
||||
|
||||
export const Calendar = function ({ height, width, properties, styles, fireEvent, darkMode }) {
|
||||
const style = { height, width };
|
||||
export const Calendar = function ({
|
||||
id,
|
||||
height,
|
||||
properties,
|
||||
styles,
|
||||
fireEvent,
|
||||
darkMode,
|
||||
containerProps,
|
||||
removeComponent,
|
||||
setExposedVariable,
|
||||
}) {
|
||||
const style = { height };
|
||||
const resourcesParam = properties.resources?.length === 0 ? {} : { resources: properties.resources };
|
||||
|
||||
const events = properties.events ? properties.events.map((event) => prepareEvent(event, properties.dateFormat)) : [];
|
||||
const defaultDate = parseDate(properties.defaultDate, properties.dateFormat);
|
||||
|
||||
const [eventPopoverOptions, setEventPopoverOptions] = useState({ show: false });
|
||||
|
||||
const eventPropGetter = (event) => {
|
||||
const backgroundColor = event.color;
|
||||
const textStyle =
|
||||
event.textOrientation === 'vertical' ? { writingMode: 'vertical-rl', textOrientation: 'mixed' } : {};
|
||||
const style = { backgroundColor, ...textStyle, padding: 3, paddingLeft: 5, paddingRight: 5 };
|
||||
const color = event.textColor ?? 'white';
|
||||
const style = { backgroundColor, ...textStyle, padding: 3, paddingLeft: 5, paddingRight: 5, color };
|
||||
|
||||
return { style };
|
||||
};
|
||||
|
|
@ -48,12 +62,26 @@ export const Calendar = function ({ height, width, properties, styles, fireEvent
|
|||
fireEvent('onCalendarSlotSelect', { selectedSlots });
|
||||
};
|
||||
|
||||
function popoverClosed() {
|
||||
setEventPopoverOptions({
|
||||
...eventPopoverOptions,
|
||||
show: false,
|
||||
});
|
||||
}
|
||||
|
||||
const defaultView = allowedCalendarViews.includes(properties.defaultView)
|
||||
? properties.defaultView
|
||||
: allowedCalendarViews[0];
|
||||
|
||||
const components = {
|
||||
timeGutterHeader: () => <div style={{ height: '100%', display: 'flex', alignItems: 'flex-end' }}>All day</div>,
|
||||
week: {
|
||||
header: (props) => <div>{moment(props.date).format(styles.displayDayNamesInWeekView ? 'ddd' : 'DD MMM')}</div>,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div id={id}>
|
||||
<ReactCalendar
|
||||
className={`calendar-widget
|
||||
${darkMode ? 'dark-mode' : ''}
|
||||
|
|
@ -68,16 +96,39 @@ export const Calendar = function ({ height, width, properties, styles, fireEvent
|
|||
style={style}
|
||||
views={allowedCalendarViews}
|
||||
defaultView={defaultView}
|
||||
onView={(view) => setExposedVariable('currentView', view)}
|
||||
{...resourcesParam}
|
||||
resourceIdAccessor="resourceId"
|
||||
resourceTitleAccessor="title"
|
||||
onSelectEvent={(calendarEvent) => fireEvent('onCalendarEventSelect', { calendarEvent })}
|
||||
onSelectEvent={(calendarEvent, e) => {
|
||||
fireEvent('onCalendarEventSelect', { calendarEvent });
|
||||
if (properties.showPopOverOnEventClick)
|
||||
setEventPopoverOptions({
|
||||
...eventPopoverOptions,
|
||||
show: true,
|
||||
offset: {
|
||||
left: e.target.getBoundingClientRect().x,
|
||||
top: e.target.getBoundingClientRect().y,
|
||||
width: e.target.getBoundingClientRect().width,
|
||||
height: e.target.getBoundingClientRect().height,
|
||||
},
|
||||
});
|
||||
}}
|
||||
selectable={true}
|
||||
onSelectSlot={slotSelectHandler}
|
||||
toolbar={properties.displayToolbar}
|
||||
eventPropGetter={eventPropGetter}
|
||||
tooltipAccessor="tooltip"
|
||||
popup={true}
|
||||
components={components}
|
||||
/>
|
||||
<CalendarEventPopover
|
||||
calenderWidgetId={id}
|
||||
show={eventPopoverOptions.show}
|
||||
offset={eventPopoverOptions.offset}
|
||||
containerProps={containerProps}
|
||||
removeComponent={removeComponent}
|
||||
popoverClosed={popoverClosed}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
98
frontend/src/Editor/Components/CalendarPopover.jsx
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { SubCustomDragLayer } from '../SubCustomDragLayer';
|
||||
import { SubContainer } from '../SubContainer';
|
||||
|
||||
export const CalendarEventPopover = function ({
|
||||
show,
|
||||
offset,
|
||||
calenderWidgetId,
|
||||
containerProps,
|
||||
removeComponent,
|
||||
popoverClosed,
|
||||
}) {
|
||||
const parentRef = useRef(null);
|
||||
const [showPopover, setShow] = useState(show);
|
||||
const [top, setTop] = useState(0);
|
||||
const [left, setLeft] = useState(0);
|
||||
|
||||
const minHeight = 400;
|
||||
let calendarBounds;
|
||||
let canvasBounds;
|
||||
|
||||
const calendarElement = document.getElementById(calenderWidgetId);
|
||||
|
||||
useEffect(() => {
|
||||
setShow(show);
|
||||
}, [show]);
|
||||
|
||||
useEffect(() => {
|
||||
if (offset?.top && showPopover) {
|
||||
const _left = offset.left - calendarBounds.x + offset.width;
|
||||
const _top = ((offset.top - calendarBounds.y) * 100) / calendarBounds.height;
|
||||
setTop(_top);
|
||||
setLeft(_left);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [offset?.top, showPopover]);
|
||||
|
||||
if (calendarElement && showPopover) {
|
||||
calendarBounds = calendarElement.getBoundingClientRect();
|
||||
const canvasElement = document.getElementsByClassName('canvas-container')[0];
|
||||
canvasBounds = canvasElement.getBoundingClientRect();
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{showPopover && (
|
||||
<div
|
||||
style={{
|
||||
// backgroundColor: 'rgba(0, 0, 0, 0.6)', // This can be used for testing the overlay
|
||||
top: -(calendarBounds.y + top),
|
||||
left: -calendarBounds.x,
|
||||
zIndex: 10,
|
||||
position: 'fixed',
|
||||
height: canvasBounds.height + top,
|
||||
width: canvasBounds.width,
|
||||
}}
|
||||
onClick={() => popoverClosed()}
|
||||
></div>
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
zIndex: 100,
|
||||
width: '300px',
|
||||
maxWidth: '300px',
|
||||
minHeight,
|
||||
top: `${top}%`,
|
||||
left,
|
||||
display: showPopover ? 'block' : 'none',
|
||||
}}
|
||||
role="tooltip"
|
||||
x-placement="left"
|
||||
className="popover bs-popover-left shadow-lg"
|
||||
ref={parentRef}
|
||||
id={`${calenderWidgetId}-popover`}
|
||||
>
|
||||
{parentRef.current && showPopover && (
|
||||
<div className="popover-body" style={{ padding: 'unset', width: '100%', height: '100%', zIndex: 11 }}>
|
||||
<>
|
||||
<SubContainer
|
||||
containerCanvasWidth={300}
|
||||
parent={`${calenderWidgetId}-popover`}
|
||||
{...containerProps}
|
||||
parentRef={parentRef}
|
||||
removeComponent={removeComponent}
|
||||
/>
|
||||
<SubCustomDragLayer
|
||||
parent={calenderWidgetId}
|
||||
parentRef={parentRef}
|
||||
currentLayout={containerProps.currentLayout}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -32,7 +32,7 @@ export const Chart = function Chart({ id, width, height, component, onComponentC
|
|||
}, [currentState]);
|
||||
|
||||
const computedStyles = {
|
||||
width,
|
||||
width: width - 4,
|
||||
height,
|
||||
display: parsedWidgetVisibility ? '' : 'none',
|
||||
background: darkMode ? '#1f2936' : 'white',
|
||||
|
|
@ -56,7 +56,7 @@ export const Chart = function Chart({ id, width, height, component, onComponentC
|
|||
const fontColor = darkMode ? '#c3c3c3' : null;
|
||||
|
||||
const layout = {
|
||||
width,
|
||||
width: width - 4,
|
||||
height,
|
||||
plot_bgcolor: darkMode ? '#1f2936' : null,
|
||||
paper_bgcolor: darkMode ? '#1f2936' : null,
|
||||
|
|
@ -137,7 +137,7 @@ export const Chart = function Chart({ id, width, height, component, onComponentC
|
|||
}}
|
||||
>
|
||||
{loadingState === true ? (
|
||||
<div style={{ width: '100%' }} className="p-2">
|
||||
<div style={{ width }} className="p-2">
|
||||
<center>
|
||||
<div className="spinner-border mt-5" role="status"></div>
|
||||
</center>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
|
|||
|
||||
export const Checkbox = function Checkbox({
|
||||
id,
|
||||
width,
|
||||
height,
|
||||
component,
|
||||
onComponentClick,
|
||||
|
|
@ -46,7 +45,7 @@ export const Checkbox = function Checkbox({
|
|||
<div
|
||||
data-disabled={parsedDisabledState}
|
||||
className="row py-1"
|
||||
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
style={{ height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component, event);
|
||||
|
|
|
|||
89
frontend/src/Editor/Components/CodeEditor.jsx
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { resolveWidgetFieldValue } from '@/_helpers/utils';
|
||||
import CodeMirror from '@uiw/react-codemirror';
|
||||
import 'codemirror/addon/comment/comment';
|
||||
import 'codemirror/addon/hint/show-hint';
|
||||
import 'codemirror/addon/display/placeholder';
|
||||
import 'codemirror/addon/search/match-highlighter';
|
||||
import 'codemirror/addon/hint/show-hint.css';
|
||||
import 'codemirror/theme/base16-light.css';
|
||||
import 'codemirror/theme/duotone-light.css';
|
||||
import 'codemirror/theme/monokai.css';
|
||||
import { onBeforeChange, handleChange } from '../CodeBuilder/utils';
|
||||
|
||||
export const CodeEditor = ({ width, height, component, currentState, onComponentOptionChanged, darkMode }) => {
|
||||
const enableLineNumber = component.definition.properties?.enableLineNumber?.value ?? true;
|
||||
const languageMode = component.definition.properties.mode.value;
|
||||
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 parsedWidgetVisibility =
|
||||
typeof widgetVisibility !== 'boolean' ? resolveWidgetFieldValue(widgetVisibility, currentState) : widgetVisibility;
|
||||
|
||||
const parsedEnableLineNumber =
|
||||
typeof enableLineNumber !== 'boolean' ? resolveWidgetFieldValue(enableLineNumber, currentState) : enableLineNumber;
|
||||
|
||||
const value = currentState?.components[component?.name]?.value;
|
||||
|
||||
const [editorValue, setEditorValue] = useState(value);
|
||||
const [realState, setRealState] = useState(currentState);
|
||||
|
||||
function codeChanged(code) {
|
||||
setEditorValue(code);
|
||||
onComponentOptionChanged(component, 'value', code);
|
||||
}
|
||||
|
||||
const styles = {
|
||||
width: width,
|
||||
height: height,
|
||||
display: !parsedWidgetVisibility ? 'none' : 'block',
|
||||
};
|
||||
const options = {
|
||||
lineNumbers: parsedEnableLineNumber,
|
||||
lineWrapping: true,
|
||||
singleLine: true,
|
||||
mode: languageMode,
|
||||
tabSize: 2,
|
||||
theme: darkMode ? 'monokai' : 'duotone-light',
|
||||
readOnly: false,
|
||||
highlightSelectionMatches: true,
|
||||
placeholder,
|
||||
};
|
||||
|
||||
function valueChanged(editor, onChange, ignoreBraces = false) {
|
||||
handleChange(editor, onChange, [], ignoreBraces);
|
||||
setEditorValue(editor.getValue());
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setRealState(currentState);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentState.components]);
|
||||
|
||||
return (
|
||||
<div data-disabled={parsedDisabledState} style={styles}>
|
||||
<div
|
||||
className={`code-hinter codehinter-default-input code-editor-widget`}
|
||||
style={{ height: height || 'auto', minHeight: height - 1, maxHeight: '320px', overflow: 'auto' }}
|
||||
>
|
||||
<CodeMirror
|
||||
value={editorValue}
|
||||
realState={realState}
|
||||
scrollbarStyle={null}
|
||||
height={height - 1}
|
||||
onBlur={(editor) => {
|
||||
const value = editor.getValue();
|
||||
codeChanged(value);
|
||||
}}
|
||||
onChange={(editor) => valueChanged(editor, codeChanged)}
|
||||
onBeforeChange={(editor, change) => onBeforeChange(editor, change)}
|
||||
options={options}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -3,15 +3,7 @@ import { SubCustomDragLayer } from '../SubCustomDragLayer';
|
|||
import { SubContainer } from '../SubContainer';
|
||||
import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
|
||||
|
||||
export const Container = function Container({
|
||||
id,
|
||||
component,
|
||||
height,
|
||||
containerProps,
|
||||
width,
|
||||
currentState,
|
||||
removeComponent,
|
||||
}) {
|
||||
export const Container = function Container({ id, component, width, height, containerProps, currentState, removeComponent }) {
|
||||
const backgroundColor = component.definition.styles.backgroundColor.value;
|
||||
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
|
||||
const disabledState = component.definition.styles?.disabledState?.value ?? false;
|
||||
|
|
@ -29,7 +21,6 @@ export const Container = function Container({
|
|||
|
||||
const computedStyles = {
|
||||
backgroundColor,
|
||||
width,
|
||||
height,
|
||||
display: parsedWidgetVisibility ? 'flex' : 'none',
|
||||
};
|
||||
|
|
@ -40,12 +31,13 @@ export const Container = function Container({
|
|||
<div
|
||||
data-disabled={parsedDisabledState}
|
||||
className="jet-container"
|
||||
id={id}
|
||||
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} />
|
||||
<SubContainer containerCanvasWidth={width} parent={id} {...containerProps} parentRef={parentRef} removeComponent={removeComponent} />
|
||||
<SubCustomDragLayer containerCanvasWidth={width} parent={id} parentRef={parentRef} currentLayout={containerProps.currentLayout} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { resolveReferences, resolveWidgetFieldValue, validateWidget } from '@/_h
|
|||
|
||||
export const Datepicker = function Datepicker({
|
||||
id,
|
||||
width,
|
||||
height,
|
||||
component,
|
||||
onComponentClick,
|
||||
|
|
@ -84,7 +83,7 @@ export const Datepicker = function Datepicker({
|
|||
<div
|
||||
data-disabled={parsedDisabledState}
|
||||
className="datepicker-widget"
|
||||
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
style={{ height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component, event);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
|
|||
|
||||
export const DaterangePicker = function DaterangePicker({
|
||||
id,
|
||||
width,
|
||||
height,
|
||||
component,
|
||||
onComponentClick,
|
||||
|
|
@ -59,7 +58,7 @@ export const DaterangePicker = function DaterangePicker({
|
|||
return (
|
||||
<div
|
||||
className="daterange-picker-widget p-0"
|
||||
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
style={{ height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component, event);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import SelectSearch, { fuzzySearch } from 'react-select-search';
|
|||
|
||||
export const DropDown = function DropDown({
|
||||
id,
|
||||
width,
|
||||
height,
|
||||
component,
|
||||
onComponentClick,
|
||||
|
|
@ -61,7 +60,9 @@ export const DropDown = function DropDown({
|
|||
|
||||
const currentValueProperty = component.definition.properties.value;
|
||||
const value = currentValueProperty ? currentValueProperty.value : '';
|
||||
const [currentValue, setCurrentValue] = useState('');
|
||||
const [currentValue, setCurrentValue] = useState(() =>
|
||||
resolveReferences(currentValueProperty.value, currentState, '')
|
||||
);
|
||||
|
||||
let newValue = value;
|
||||
if (currentValueProperty && currentState) {
|
||||
|
|
@ -91,10 +92,19 @@ export const DropDown = function DropDown({
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentValue]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectOptions.some((e) => e.value === newValue)) {
|
||||
setCurrentValue(newValue);
|
||||
} else {
|
||||
setCurrentValue(undefined);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [values]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="dropdown-widget row g-0"
|
||||
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
style={{ height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component, event);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export const FilePicker = ({ width, height, component, currentState, onComponent
|
|||
typeof enableDropzone !== 'boolean' ? resolveWidgetFieldValue(enableDropzone, currentState) : true;
|
||||
const parsedEnablePicker =
|
||||
typeof enablePicker !== 'boolean' ? resolveWidgetFieldValue(enablePicker, currentState) : true;
|
||||
|
||||
const parsedMaxFileCount =
|
||||
typeof maxFileCount !== 'number' ? resolveWidgetFieldValue(maxFileCount, currentState) : maxFileCount;
|
||||
const parsedEnableMultiple =
|
||||
|
|
@ -50,7 +51,6 @@ export const FilePicker = ({ width, height, component, currentState, onComponent
|
|||
outline: 'none',
|
||||
transition: 'border .24s ease-in-out',
|
||||
display: parsedWidgetVisibility ? 'flex' : 'none',
|
||||
width,
|
||||
height,
|
||||
backgroundColor: !parsedDisabledState && bgThemeColor,
|
||||
};
|
||||
|
|
@ -95,6 +95,47 @@ export const FilePicker = ({ width, height, component, currentState, onComponent
|
|||
const [showSelectdFiles, setShowSelectedFiles] = React.useState(false);
|
||||
const [selectedFiles, setSelectedFiles] = React.useState([]);
|
||||
|
||||
/**
|
||||
* *getFileData()
|
||||
* @param {*} file
|
||||
* @param {*} method: readAsDataURL, readAsText
|
||||
*/
|
||||
const getFileData = (file = {}, method = 'readAsText') => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (result) => {
|
||||
resolve([result, reader]);
|
||||
};
|
||||
reader[method](file);
|
||||
reader.onerror = (error) => {
|
||||
reject(error);
|
||||
if (error.name == 'NotReadableError') {
|
||||
toast.error(error.message, { hideProgressBar: true, autoClose: 3000 });
|
||||
}
|
||||
};
|
||||
}).then((result) => {
|
||||
if (method === 'readAsDataURL') {
|
||||
return result[0].srcElement.result.split(',')[1];
|
||||
}
|
||||
return result[0].srcElement.result;
|
||||
});
|
||||
};
|
||||
|
||||
const fileReader = async (file) => {
|
||||
// * readAsText
|
||||
const readFileAsText = await getFileData(file);
|
||||
|
||||
// * readAsDataURL
|
||||
const readFileAsDataURL = await getFileData(file, 'readAsDataURL');
|
||||
|
||||
return {
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
content: readFileAsText,
|
||||
dataURL: readFileAsDataURL,
|
||||
};
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (acceptedFiles.length === 0) {
|
||||
onComponentOptionChanged(component, 'file', []);
|
||||
|
|
@ -103,23 +144,9 @@ export const FilePicker = ({ width, height, component, currentState, onComponent
|
|||
if (acceptedFiles.length !== 0) {
|
||||
const fileData = parsedEnableMultiple ? [...selectedFiles] : [];
|
||||
acceptedFiles.map((acceptedFile) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let reader = new FileReader();
|
||||
reader.onload = (result) => {
|
||||
//* Resolve both the FileReader result and its original file.
|
||||
resolve([result, acceptedFile]);
|
||||
};
|
||||
//* Reads contents of the file as a text string.
|
||||
reader.readAsText(acceptedFile);
|
||||
}).then((zippedResults) => {
|
||||
//? Run the callback after all files have been read.
|
||||
const fileSelected = {
|
||||
name: zippedResults[1].name,
|
||||
content: zippedResults[0].srcElement.result,
|
||||
type: zippedResults[1].type,
|
||||
};
|
||||
|
||||
fileData.push(fileSelected);
|
||||
const acceptedFileData = fileReader(acceptedFile);
|
||||
acceptedFileData.then((data) => {
|
||||
fileData.push(data);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
40
frontend/src/Editor/Components/IFrame.jsx
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import React from 'react';
|
||||
import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
|
||||
|
||||
export const IFrame = function IFrame({ 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;
|
||||
|
||||
let parsedWidgetVisibility = widgetVisibility;
|
||||
|
||||
try {
|
||||
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
data-disabled={parsedDisabledState}
|
||||
style={{ display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component, event);
|
||||
}}
|
||||
>
|
||||
<iframe
|
||||
width={width}
|
||||
height={height}
|
||||
src={source}
|
||||
title="IFrame Widget"
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -2,7 +2,7 @@ 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, 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;
|
||||
|
|
@ -22,7 +22,7 @@ export const Image = function Image({ id, width, height, component, onComponentC
|
|||
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', height }}></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -34,8 +34,8 @@ export const Image = function Image({ id, width, height, component, onComponentC
|
|||
onComponentClick(id, component, event);
|
||||
}}
|
||||
>
|
||||
<LazyLoad width={width} height={height} placeholder={<Placeholder />} debounce={500}>
|
||||
<img style={{ objectFit: 'contain' }} src={data} width={width} height={height} />
|
||||
<LazyLoad height={height} placeholder={<Placeholder />} debounce={500}>
|
||||
<img style={{ objectFit: 'contain' }} src={data} height={height} />
|
||||
</LazyLoad>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export const Map = function Map({
|
|||
onComponentOptionChanged,
|
||||
onComponentOptionsChanged,
|
||||
onEvent,
|
||||
canvasWidth
|
||||
}) {
|
||||
const center = component.definition.properties.initialLocation.value;
|
||||
const defaultMarkerValue = component.definition.properties.defaultMarkers.value;
|
||||
|
|
@ -50,7 +51,7 @@ export const Map = function Map({
|
|||
const [markers, setMarkers] = useState(resolveReferences(defaultMarkers, currentState));
|
||||
|
||||
const containerStyle = {
|
||||
width,
|
||||
width: '100%',
|
||||
height,
|
||||
};
|
||||
|
||||
|
|
@ -111,7 +112,7 @@ export const Map = function Map({
|
|||
return (
|
||||
<div
|
||||
data-disabled={parsedDisabledState}
|
||||
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
style={{ height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component, event);
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export const Modal = function Modal({ id, component, height, containerProps, cur
|
|||
</div>
|
||||
</BootstrapModal.Header>
|
||||
|
||||
<BootstrapModal.Body style={{ height }} ref={parentRef}>
|
||||
<BootstrapModal.Body style={{ height }} ref={parentRef} id={id}>
|
||||
<SubContainer parent={id} {...containerProps} parentRef={parentRef} />
|
||||
<SubCustomDragLayer
|
||||
snapToGrid={true}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export const Multiselect = function Multiselect({
|
|||
return (
|
||||
<div
|
||||
className="multiselect-widget row g-0"
|
||||
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
style={{ height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component, event);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
|
|||
|
||||
export const NumberInput = function NumberInput({
|
||||
id,
|
||||
width,
|
||||
height,
|
||||
component,
|
||||
onComponentClick,
|
||||
|
|
@ -54,7 +53,7 @@ export const NumberInput = function NumberInput({
|
|||
type="number"
|
||||
className="form-control rounded-0"
|
||||
placeholder={placeholder}
|
||||
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
style={{ height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
value={number}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
|
|||
|
||||
export const RadioButton = function RadioButton({
|
||||
id,
|
||||
width,
|
||||
height,
|
||||
component,
|
||||
onComponentClick,
|
||||
|
|
@ -72,9 +71,7 @@ export const RadioButton = function RadioButton({
|
|||
|
||||
function onSelect(selection) {
|
||||
onComponentOptionChanged(component, 'value', selection);
|
||||
if (selection) {
|
||||
onEvent('onSelectionChange', { component });
|
||||
}
|
||||
onEvent('onSelectionChange', { component });
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -86,7 +83,7 @@ export const RadioButton = function RadioButton({
|
|||
<div
|
||||
data-disabled={parsedDisabledState}
|
||||
className="row py-1"
|
||||
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
style={{ height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component, event);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export const RichTextEditor = function RichTextEditor({
|
|||
return (
|
||||
<div
|
||||
data-disabled={parsedDisabledState}
|
||||
style={{ width: `${width}px`, height: `${height}px`, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
style={{ height: `${height}px`, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component, event);
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ export const StarRating = function StarRating({
|
|||
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: labelColor }}>
|
||||
<span className="label form-check-label col-auto" style={{ color: labelColor }}>
|
||||
{label}
|
||||
</span>
|
||||
<div className="col px-1 py-0 mt-0">
|
||||
|
|
|
|||
|
|
@ -324,7 +324,7 @@ export function Table({
|
|||
case 'string':
|
||||
case undefined:
|
||||
case 'default': {
|
||||
const textColor = resolveReferences(column.textColor, currentState, { cellValue });
|
||||
const textColor = resolveReferences(column.textColor, currentState, '', { cellValue });
|
||||
|
||||
const cellStyles = {
|
||||
color: textColor ?? '',
|
||||
|
|
@ -561,65 +561,65 @@ export function Table({
|
|||
const leftActionsCellData =
|
||||
leftActions().length > 0
|
||||
? [
|
||||
{
|
||||
id: 'leftActions',
|
||||
Header: 'Actions',
|
||||
accessor: 'edit',
|
||||
width: columnSizes.leftActions || defaultColumn.width,
|
||||
Cell: (cell) => {
|
||||
return leftActions().map((action) => (
|
||||
<button
|
||||
key={action.name}
|
||||
className="btn btn-sm m-1 btn-light"
|
||||
style={{ background: action.backgroundColor, color: action.textColor }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onEvent('onTableActionButtonClicked', {
|
||||
component,
|
||||
data: cell.row.original,
|
||||
rowId: cell.row.id,
|
||||
action,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{action.buttonText}
|
||||
</button>
|
||||
));
|
||||
},
|
||||
{
|
||||
id: 'leftActions',
|
||||
Header: 'Actions',
|
||||
accessor: 'edit',
|
||||
width: columnSizes.leftActions || defaultColumn.width,
|
||||
Cell: (cell) => {
|
||||
return leftActions().map((action) => (
|
||||
<button
|
||||
key={action.name}
|
||||
className="btn btn-sm m-1 btn-light"
|
||||
style={{ background: action.backgroundColor, color: action.textColor }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onEvent('onTableActionButtonClicked', {
|
||||
component,
|
||||
data: cell.row.original,
|
||||
rowId: cell.row.id,
|
||||
action,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{action.buttonText}
|
||||
</button>
|
||||
));
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const rightActionsCellData =
|
||||
rightActions().length > 0
|
||||
? [
|
||||
{
|
||||
id: 'rightActions',
|
||||
Header: 'Actions',
|
||||
accessor: 'edit',
|
||||
width: columnSizes.rightActions || defaultColumn.width,
|
||||
Cell: (cell) => {
|
||||
return rightActions().map((action) => (
|
||||
<button
|
||||
key={action.name}
|
||||
className="btn btn-sm m-1 btn-light"
|
||||
style={{ background: action.backgroundColor, color: action.textColor }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onEvent('onTableActionButtonClicked', {
|
||||
component,
|
||||
data: cell.row.original,
|
||||
rowId: cell.row.id,
|
||||
action,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{action.buttonText}
|
||||
</button>
|
||||
));
|
||||
},
|
||||
{
|
||||
id: 'rightActions',
|
||||
Header: 'Actions',
|
||||
accessor: 'edit',
|
||||
width: columnSizes.rightActions || defaultColumn.width,
|
||||
Cell: (cell) => {
|
||||
return rightActions().map((action) => (
|
||||
<button
|
||||
key={action.name}
|
||||
className="btn btn-sm m-1 btn-light"
|
||||
style={{ background: action.backgroundColor, color: action.textColor }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onEvent('onTableActionButtonClicked', {
|
||||
component,
|
||||
data: cell.row.original,
|
||||
rowId: cell.row.id,
|
||||
action,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{action.buttonText}
|
||||
</button>
|
||||
));
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const IndeterminateCheckbox = React.forwardRef(({ indeterminate, ...rest }, ref) => {
|
||||
|
|
@ -666,7 +666,7 @@ export function Table({
|
|||
const data = useMemo(() => tableData, [tableData.length, componentState.changeSet]);
|
||||
|
||||
const computedStyles = {
|
||||
width: `${width}px`,
|
||||
// width: `${width}px`,
|
||||
};
|
||||
|
||||
const {
|
||||
|
|
@ -773,7 +773,7 @@ export function Table({
|
|||
<div
|
||||
data-disabled={parsedDisabledState}
|
||||
className="card jet-table"
|
||||
style={{ width: `${width}px`, height: `${height}px`, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
style={{ width: `100%`, height: `${height}px`, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onComponentClick(id, component, event);
|
||||
|
|
@ -835,9 +835,8 @@ export function Table({
|
|||
return (
|
||||
<tr
|
||||
key={index}
|
||||
className={`table-row ${
|
||||
highlightSelectedRow && row.id === componentState.selectedRowId ? 'selected' : ''
|
||||
}`}
|
||||
className={`table-row ${highlightSelectedRow && row.id === componentState.selectedRowId ? 'selected' : ''
|
||||
}`}
|
||||
{...row.getRowProps()}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
|
@ -899,62 +898,62 @@ export function Table({
|
|||
Object.keys(componentState.changeSet || {}).length > 0 ||
|
||||
showFilterButton ||
|
||||
showDownloadButton) && (
|
||||
<div className="card-footer d-flex align-items-center jet-table-footer">
|
||||
<div className="table-footer row">
|
||||
<div className="col">
|
||||
{(clientSidePagination || serverSidePagination) && (
|
||||
<Pagination
|
||||
lastActivePageIndex={pageIndex}
|
||||
serverSide={serverSidePagination}
|
||||
autoGotoPage={gotoPage}
|
||||
autoCanNextPage={canNextPage}
|
||||
autoPageCount={pageCount}
|
||||
autoPageOptions={pageOptions}
|
||||
onPageIndexChanged={onPageIndexChanged}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{showBulkUpdateActions && Object.keys(componentState.changeSet || {}).length > 0 && (
|
||||
<div className="card-footer d-flex align-items-center jet-table-footer">
|
||||
<div className="table-footer row">
|
||||
<div className="col">
|
||||
<button
|
||||
className={`btn btn-primary btn-sm ${componentState.isSavingChanges ? 'btn-loading' : ''}`}
|
||||
onClick={() =>
|
||||
onEvent('onBulkUpdate', { component }).then(() => {
|
||||
handleChangesSaved();
|
||||
})
|
||||
}
|
||||
>
|
||||
Save Changes
|
||||
</button>
|
||||
<button className="btn btn-light btn-sm mx-2" onClick={() => handleChangesDiscarded()}>
|
||||
Discard changes
|
||||
</button>
|
||||
{(clientSidePagination || serverSidePagination) && (
|
||||
<Pagination
|
||||
lastActivePageIndex={pageIndex}
|
||||
serverSide={serverSidePagination}
|
||||
autoGotoPage={gotoPage}
|
||||
autoCanNextPage={canNextPage}
|
||||
autoPageCount={pageCount}
|
||||
autoPageOptions={pageOptions}
|
||||
onPageIndexChanged={onPageIndexChanged}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="col-auto">
|
||||
{showFilterButton && (
|
||||
<span data-tip="Filter data" className="btn btn-light btn-sm p-1 mx-2" onClick={() => showFilters()}>
|
||||
<img src="/assets/images/icons/filter.svg" width="13" height="13" />
|
||||
{filters.length > 0 && (
|
||||
<a className="badge bg-azure" style={{ width: '4px', height: '4px', marginTop: '5px' }}></a>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
{showDownloadButton && (
|
||||
<span
|
||||
data-tip="Download as CSV"
|
||||
className="btn btn-light btn-sm p-1"
|
||||
onClick={() => exportData('csv', true)}
|
||||
>
|
||||
<img src="/assets/images/icons/download.svg" width="13" height="13" />
|
||||
</span>
|
||||
{showBulkUpdateActions && Object.keys(componentState.changeSet || {}).length > 0 && (
|
||||
<div className="col">
|
||||
<button
|
||||
className={`btn btn-primary btn-sm ${componentState.isSavingChanges ? 'btn-loading' : ''}`}
|
||||
onClick={() =>
|
||||
onEvent('onBulkUpdate', { component }).then(() => {
|
||||
handleChangesSaved();
|
||||
})
|
||||
}
|
||||
>
|
||||
Save Changes
|
||||
</button>
|
||||
<button className="btn btn-light btn-sm mx-2" onClick={() => handleChangesDiscarded()}>
|
||||
Discard changes
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="col-auto">
|
||||
{showFilterButton && (
|
||||
<span data-tip="Filter data" className="btn btn-light btn-sm p-1 mx-2" onClick={() => showFilters()}>
|
||||
<img src="/assets/images/icons/filter.svg" width="13" height="13" />
|
||||
{filters.length > 0 && (
|
||||
<a className="badge bg-azure" style={{ width: '4px', height: '4px', marginTop: '5px' }}></a>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
{showDownloadButton && (
|
||||
<span
|
||||
data-tip="Download as CSV"
|
||||
className="btn btn-light btn-sm p-1"
|
||||
onClick={() => exportData('csv', true)}
|
||||
>
|
||||
<img src="/assets/images/icons/download.svg" width="13" height="13" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
{isFiltersVisible && (
|
||||
<div className="table-filters card">
|
||||
<div className="card-header row">
|
||||
|
|
|
|||
92
frontend/src/Editor/Components/Tabs.jsx
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import React, { useRef, useState } from 'react';
|
||||
import { SubCustomDragLayer } from '../SubCustomDragLayer';
|
||||
import { SubContainer } from '../SubContainer';
|
||||
import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
|
||||
|
||||
export const Tabs = function Tabs({ id, component, width, height, containerProps, currentState, removeComponent }) {
|
||||
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
|
||||
const disabledState = component.definition.styles?.disabledState?.value ?? false;
|
||||
const defaultTab = component.definition.properties.defaultTab.value;
|
||||
|
||||
// config for tabs. Includes title
|
||||
const tabs = component.definition.properties?.tabs?.value ?? [];
|
||||
let parsedTabs = tabs;
|
||||
parsedTabs = resolveWidgetFieldValue(parsedTabs, currentState);
|
||||
|
||||
// set index as id if id is not provided
|
||||
parsedTabs = parsedTabs.map((parsedTab, index) => ({ ...parsedTab, id: parsedTab.id ? parsedTab.id : index }));
|
||||
|
||||
// Highlight color - for active tab text and border
|
||||
const highlightColor = component.definition.styles?.highlightColor?.value ?? '';
|
||||
let parsedHighlightColor = highlightColor;
|
||||
parsedHighlightColor = resolveWidgetFieldValue(highlightColor, currentState);
|
||||
|
||||
// Default tab
|
||||
let parsedDefaultTab = defaultTab;
|
||||
parsedDefaultTab = resolveWidgetFieldValue(parsedDefaultTab, currentState, 1);
|
||||
|
||||
const parsedDisabledState =
|
||||
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
||||
|
||||
let parsedWidgetVisibility = widgetVisibility;
|
||||
|
||||
try {
|
||||
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
const computedStyles = {
|
||||
height,
|
||||
display: parsedWidgetVisibility ? 'flex' : 'none',
|
||||
};
|
||||
|
||||
const parentRef = useRef(null);
|
||||
|
||||
const [currentTab, setCurrentTab] = useState(parsedDefaultTab);
|
||||
|
||||
return (
|
||||
<div
|
||||
data-disabled={parsedDisabledState}
|
||||
className="jet-tabs card"
|
||||
onClick={() => {
|
||||
containerProps.onComponentClick(id, component);
|
||||
}}
|
||||
style={computedStyles}
|
||||
>
|
||||
<ul className="nav nav-tabs" data-bs-toggle="tabs">
|
||||
{parsedTabs.map((tab) => (
|
||||
<li className="nav-item" onClick={() => setCurrentTab(tab.id)} key={tab.id}>
|
||||
<a
|
||||
className={`nav-link ${currentTab === tab.id ? 'active' : ''}`}
|
||||
style={
|
||||
currentTab === tab.id
|
||||
? { color: parsedHighlightColor, borderBottom: `1px solid ${parsedHighlightColor}` }
|
||||
: {}
|
||||
}
|
||||
>
|
||||
{tab.title}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="tab-content" ref={parentRef} id={`${id}-${currentTab}`}>
|
||||
<div className="tab-pane active show">
|
||||
<SubContainer
|
||||
parent={`${id}-${currentTab}`}
|
||||
{...containerProps}
|
||||
parentRef={parentRef}
|
||||
removeComponent={removeComponent}
|
||||
containerCanvasWidth={width}
|
||||
/>
|
||||
<SubCustomDragLayer
|
||||
parent={id}
|
||||
parentRef={parentRef}
|
||||
currentLayout={containerProps.currentLayout}
|
||||
containerCanvasWidth={width}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
|
|||
import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
export const Text = function Text({ id, width, height, component, onComponentClick, currentState }) {
|
||||
export const Text = function Text({ id, 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;
|
||||
|
|
@ -45,7 +45,6 @@ export const Text = function Text({ id, width, height, component, onComponentCli
|
|||
|
||||
const computedStyles = {
|
||||
color,
|
||||
width,
|
||||
height,
|
||||
display: parsedWidgetVisibility ? 'flex' : 'none',
|
||||
alignItems: 'center',
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export const TextArea = function TextArea({ width, height, properties, exposedVa
|
|||
type="text"
|
||||
className="form-control"
|
||||
placeholder={properties.placeholder}
|
||||
style={{ width, height, resize:'none', display: styles.visibility ? '' : 'none' }}
|
||||
style={{ height, resize: 'none', display: styles.visibility ? '' : 'none' }}
|
||||
value={exposedVariables.value}
|
||||
></textarea>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
|
|||
|
||||
export const TextInput = function TextInput({
|
||||
id,
|
||||
width,
|
||||
height,
|
||||
component,
|
||||
onComponentClick,
|
||||
|
|
@ -65,7 +64,7 @@ export const TextInput = function TextInput({
|
|||
type="text"
|
||||
className={`form-control ${!isValid ? 'is-invalid' : ''} validation-without-icon`}
|
||||
placeholder={placeholder}
|
||||
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
style={{ height, display: parsedWidgetVisibility ? '' : 'none' }}
|
||||
value={text}
|
||||
/>
|
||||
<div className="invalid-feedback">{validationError}</div>
|
||||
|
|
|
|||