mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-23 00:48:25 +00:00
merge base
This commit is contained in:
commit
3e7f524403
100 changed files with 2411 additions and 1046 deletions
92
.github/workflows/docker-release.yml
vendored
92
.github/workflows/docker-release.yml
vendored
|
|
@ -232,3 +232,95 @@ jobs:
|
|||
# fi
|
||||
|
||||
# curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$message\"}" ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
|
||||
|
||||
try-tooljet-image-build:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-tooljet-image-for-ee-edtion
|
||||
if: ${{ needs.build-tooljet-image-for-ee-edtion.result == 'success' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code to develop
|
||||
if: "!contains(github.event.release.tag_name, 'ee-lts')"
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: refs/heads/main
|
||||
|
||||
- name: Checkout code to lts-3.0
|
||||
if: contains(github.event.release.tag_name, '-ee-lts')
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: refs/heads/lts-3.0
|
||||
|
||||
# Create Docker Buildx builder with platform configuration
|
||||
- name: Set up Docker Buildx
|
||||
run: |
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
curl -SL https://github.com/docker/buildx/releases/download/v0.11.0/buildx-v0.11.0.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx
|
||||
chmod a+x ~/.docker/cli-plugins/docker-buildx
|
||||
docker buildx create --name mybuilder --platform linux/arm64,linux/amd64,linux/amd64/v2,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/arm/v7,linux/arm/v6
|
||||
docker buildx use mybuilder
|
||||
|
||||
- name: Set DOCKER_CLI_EXPERIMENTAL
|
||||
run: echo "DOCKER_CLI_EXPERIMENTAL=enabled" >> $GITHUB_ENV
|
||||
|
||||
- name: use mybuilder buildx
|
||||
run: docker buildx use mybuilder
|
||||
|
||||
- name: Docker Login
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Check if Docker image is present
|
||||
id: check-image-presence
|
||||
run: |
|
||||
response=$(curl -s "https://hub.docker.com/v2/repositories/tooljet/tooljet/tags/${{ github.event.release.tag_name }}")
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "Failed to fetch JSON response. Stopping workflow execution."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $response == *"tag '${{ github.event.release.tag_name }}' not found"* ]]; then
|
||||
echo "Docker image tag '${{ github.event.release.tag_name }}' not present."
|
||||
exit 1
|
||||
else
|
||||
echo "Docker image tag '${{ github.event.release.tag_name }}' is present."
|
||||
fi
|
||||
|
||||
- name: Build and Push Docker image for non-EE-LTS
|
||||
if: "!contains(github.event.release.tag_name, '-ee-lts')"
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: docker/ee/ee-try-tooljet.Dockerfile
|
||||
push: true
|
||||
tags: tooljet/try:${{ github.event.release.tag_name }},tooljet/try:ee-latest
|
||||
platforms: linux/amd64
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and Push Docker image for EE-LTS-3.0
|
||||
if: contains(github.event.release.tag_name, '-ee-lts')
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: docker/ee/ee-try-tooljet-lts.Dockerfile
|
||||
push: true
|
||||
tags: tooljet/try:${{ github.event.release.tag_name }},tooljet/try:ee-lts-latest
|
||||
platforms: linux/amd64
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Send Slack Notification
|
||||
run: |
|
||||
if [[ "${{ job.status }}" == "success" ]]; then
|
||||
message="Try-ToolJet image published:\\n\`tooljet/try:${{ github.event.release.tag_name }}\`"
|
||||
else
|
||||
message="Job '${{ env.JOB_NAME }}' failed! tooljet/try:${{ github.event.release.tag_name }}"
|
||||
fi
|
||||
|
||||
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$message\"}" ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
|
|
|
|||
44
.github/workflows/render-suspend-labeler.yml
vendored
44
.github/workflows/render-suspend-labeler.yml
vendored
|
|
@ -8,16 +8,16 @@ permissions:
|
|||
issues: write
|
||||
|
||||
jobs:
|
||||
label-stale-deploys:
|
||||
label-stale-ce-deploys:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: akshaysasidrn/stale-label-fetch@v1.1
|
||||
id: stale-label
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-label: 'active-review-app'
|
||||
stale-label: 'active-ce-review-app'
|
||||
stale-time: '86400'
|
||||
type: 'pull_request'
|
||||
- name: Get stale numbers
|
||||
|
|
@ -40,6 +40,42 @@ jobs:
|
|||
issue_number: prNumber,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ['suspend-review-app']
|
||||
labels: ['suspend-ce-review-app']
|
||||
})
|
||||
}
|
||||
|
||||
label-stale-ee-deploys:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: akshaysasidrn/stale-label-fetch@v1.1
|
||||
id: stale-label
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-label: 'active-ee-review-app'
|
||||
stale-time: '86400'
|
||||
type: 'pull_request'
|
||||
- name: Get stale numbers
|
||||
run: echo "Matched PR numbers - ${{ steps.stale-label.outputs.stale-numbers }}"
|
||||
- name: Add suspend label
|
||||
uses: actions/github-script@v6
|
||||
env:
|
||||
STALE_NUMBERS: ${{ steps.stale-label.outputs.stale-numbers }}
|
||||
with:
|
||||
github-token: ${{ secrets.TJ_BOT_PAT }}
|
||||
script: |
|
||||
if (!process.env.STALE_NUMBERS) return
|
||||
|
||||
const prNumbers = process.env.STALE_NUMBERS.split(",")
|
||||
|
||||
console.log(`Adding suspend labels for: ${prNumbers}`)
|
||||
|
||||
for (const prNumber of prNumbers) {
|
||||
github.rest.issues.addLabels({
|
||||
issue_number: prNumber,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ['suspend-ee-review-app']
|
||||
})
|
||||
}
|
||||
|
|
|
|||
2
.github/workflows/vulnerability-ci.yml
vendored
2
.github/workflows/vulnerability-ci.yml
vendored
|
|
@ -11,7 +11,7 @@ on:
|
|||
# Schedule the workflow to run every two weeks once
|
||||
|
||||
schedule:
|
||||
- cron: '30 5 */14 * *'
|
||||
- cron: '30 5 * * 1'
|
||||
|
||||
jobs:
|
||||
PeriodicVulnerability-CheckOn-frontend-code:
|
||||
|
|
|
|||
27
docker/ce-entrypoint.sh
Executable file
27
docker/ce-entrypoint.sh
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [ -d "./server/dist" ]; then
|
||||
SETUP_CMD='npm run db:setup:prod'
|
||||
else
|
||||
SETUP_CMD='npm run db:setup'
|
||||
fi
|
||||
|
||||
if [ -f "./.env" ]; then
|
||||
declare $(grep -v '^#' ./.env | xargs)
|
||||
fi
|
||||
|
||||
if [ -z "$DATABASE_URL" ]; then
|
||||
./server/scripts/wait-for-it.sh $PG_HOST:${PG_PORT:-5432} --strict --timeout=300 -- $SETUP_CMD
|
||||
else
|
||||
PG_HOST=$(echo "$DATABASE_URL" | awk -F'[/:@?]' '{print $6}')
|
||||
PG_PORT=$(echo "$DATABASE_URL" | awk -F'[/:@?]' '{print $7}')
|
||||
|
||||
if [ -z "$DATABASE_PORT" ]; then
|
||||
DATABASE_PORT="5432"
|
||||
fi
|
||||
|
||||
./server/scripts/wait-for-it.sh "$PG_HOST:$PG_PORT" --strict --timeout=300 -- $SETUP_CMD
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
|
|
@ -88,12 +88,13 @@ COPY --from=builder /app/frontend/build ./app/frontend/build
|
|||
# copy server build
|
||||
COPY --from=builder /app/server/package.json ./app/server/package.json
|
||||
COPY --from=builder /app/server/.version ./app/server/.version
|
||||
COPY --from=builder /app/server/entrypoint.sh ./app/server/entrypoint.sh
|
||||
COPY --from=builder /app/server/node_modules ./app/server/node_modules
|
||||
COPY --from=builder /app/server/templates ./app/server/templates
|
||||
COPY --from=builder /app/server/scripts ./app/server/scripts
|
||||
COPY --from=builder /app/server/dist ./app/server/dist
|
||||
|
||||
COPY ./docker/ce-entrypoint.sh ./app/server/entrypoint.sh
|
||||
|
||||
# Define non-sudo user
|
||||
RUN useradd --create-home --home-dir /home/appuser appuser \
|
||||
&& chown -R appuser:0 /app \
|
||||
|
|
@ -111,5 +112,4 @@ WORKDIR /app
|
|||
# Dependencies for scripts outside nestjs
|
||||
RUN npm install dotenv@10.0.0 joi@17.4.1
|
||||
|
||||
|
||||
ENTRYPOINT ["./server/entrypoint.sh"]
|
||||
|
|
|
|||
|
|
@ -145,12 +145,13 @@ COPY --from=builder /app/frontend/build ./app/frontend/build
|
|||
COPY --from=builder /app/server/package.json ./app/server/package.json
|
||||
COPY --from=builder /app/server/.version ./app/server/.version
|
||||
COPY --from=builder /app/server/ee/keys ./app/server/ee/keys
|
||||
COPY --from=builder /app/server/entrypoint.sh ./app/server/entrypoint.sh
|
||||
COPY --from=builder /app/server/node_modules ./app/server/node_modules
|
||||
COPY --from=builder /app/server/templates ./app/server/templates
|
||||
COPY --from=builder /app/server/scripts ./app/server/scripts
|
||||
COPY --from=builder /app/server/dist ./app/server/dist
|
||||
|
||||
COPY ./docker/ee/ee-entrypoint.sh ./app/server/ee-entrypoint.sh
|
||||
|
||||
# Define non-sudo user
|
||||
RUN useradd --create-home --home-dir /home/appuser appuser \
|
||||
&& chown -R appuser:0 /app \
|
||||
|
|
@ -214,4 +215,4 @@ RUN npm install dotenv@10.0.0 joi@17.4.1
|
|||
|
||||
RUN npm cache clean --force
|
||||
|
||||
ENTRYPOINT ["./server/entrypoint.sh"]
|
||||
ENTRYPOINT ["./server/ee-entrypoint.sh"]
|
||||
|
|
|
|||
15
docker/ee/ee-try-entrypoint-lts.sh
Executable file
15
docker/ee/ee-try-entrypoint-lts.sh
Executable file
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Start Redis
|
||||
# service redis-server start
|
||||
# redis-server /etc/redis/redis.conf
|
||||
|
||||
# Start Postgres
|
||||
service postgresql start
|
||||
|
||||
# Export the PORT variable to be used by the application
|
||||
export PORT=${PORT:-80}
|
||||
|
||||
# Start Supervisor
|
||||
exec supervisord -c /etc/supervisor/conf.d/supervisord.conf
|
||||
2
server/try-entrypoint.sh → docker/ee/ee-try-entrypoint.sh
Normal file → Executable file
2
server/try-entrypoint.sh → docker/ee/ee-try-entrypoint.sh
Normal file → Executable file
|
|
@ -22,7 +22,7 @@ echo "Starting Temporal Server..."
|
|||
export PORT=${PORT:-80}
|
||||
|
||||
# Start Supervisor
|
||||
/usr/bin/supervisord -n &
|
||||
exec supervisord -c /etc/supervisor/conf.d/supervisord.conf &
|
||||
|
||||
# Wait for Temporal Server to be ready
|
||||
echo "Waiting for Temporal Server to be ready..."
|
||||
|
|
@ -1,21 +1,31 @@
|
|||
FROM tooljet/tooljet-ce:latest
|
||||
FROM tooljet/tooljet:ee-lts-latest
|
||||
|
||||
# copy postgrest executable
|
||||
COPY --from=postgrest/postgrest:v10.1.1.20221215 /bin/postgrest /bin
|
||||
# Copy PostgREST executable
|
||||
COPY --from=postgrest/postgrest:v12.2.0 /bin/postgrest /bin
|
||||
|
||||
# Install Postgres
|
||||
# Install PostgreSQL
|
||||
USER root
|
||||
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
|
||||
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ bullseye-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list
|
||||
RUN echo "deb http://deb.debian.org/debian"
|
||||
RUN apt update && apt -y install postgresql-13 postgresql-client-13 supervisor
|
||||
|
||||
USER postgres
|
||||
RUN service postgresql start && \
|
||||
psql -c "create role tooljet with login superuser password 'postgres';"
|
||||
USER root
|
||||
|
||||
# Install Redis
|
||||
RUN apt update && apt -y install redis
|
||||
|
||||
# Create appuser home & ensure permission for supervisord and services
|
||||
RUN mkdir -p /var/log/supervisor /var/run/postgresql /var/lib/postgresql /var/lib/redis && \
|
||||
chown -R appuser:appuser /etc/supervisor /var/log/supervisor /var/lib/redis && \
|
||||
chown -R postgres:postgres /var/run/postgresql /var/lib/postgresql
|
||||
|
||||
# Configure Supervisor to manage PostgREST, ToolJet, and Redis
|
||||
RUN echo "[supervisord] \n" \
|
||||
"nodaemon=true \n" \
|
||||
"user=root \n" \
|
||||
"\n" \
|
||||
"[program:postgrest] \n" \
|
||||
"command=/bin/postgrest \n" \
|
||||
|
|
@ -23,12 +33,23 @@ RUN echo "[supervisord] \n" \
|
|||
"autorestart=true \n" \
|
||||
"\n" \
|
||||
"[program:tooljet] \n" \
|
||||
"user=appuser \n" \
|
||||
"command=/bin/bash -c '/app/server/scripts/init-db-boot.sh' \n" \
|
||||
"autostart=true \n" \
|
||||
"autorestart=true \n" \
|
||||
"stderr_logfile=/dev/stdout \n" \
|
||||
"stderr_logfile_maxbytes=0 \n" \
|
||||
"stdout_logfile=/dev/stdout \n" \
|
||||
"stdout_logfile_maxbytes=0 \n" \
|
||||
"\n" \
|
||||
"[program:redis] \n" \
|
||||
"user=appuser \n" \
|
||||
"command=/usr/bin/redis-server \n" \
|
||||
"autostart=true \n" \
|
||||
"autorestart=true \n" \
|
||||
"stderr_logfile=/dev/stdout \n" \
|
||||
"stderr_logfile_maxbytes=0 \n" \
|
||||
"stdout_logfile=/dev/stdout \n" \
|
||||
"stdout_logfile_maxbytes=0 \n" | sed 's/ //' > /etc/supervisor/conf.d/supervisord.conf
|
||||
|
||||
# ENV defaults
|
||||
|
|
@ -49,10 +70,17 @@ ENV TOOLJET_HOST=http://localhost \
|
|||
PGRST_HOST=http://localhost:3000 \
|
||||
PGRST_DB_URI=postgres://tooljet:postgres@localhost/tooljet_db \
|
||||
PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj \
|
||||
PGRST_DB_PRE_CONFIG=postgrest.pre_config \
|
||||
ORM_LOGGING=true \
|
||||
DEPLOYMENT_PLATFORM=docker:local \
|
||||
HOME=/home/appuser \
|
||||
REDIS_HOST=localhost \
|
||||
REDIS_PORT=6379 \
|
||||
REDIS_USER=default \
|
||||
REDIS_PASS= \
|
||||
TERM=xterm
|
||||
|
||||
# Prepare DB and start application
|
||||
ENTRYPOINT service postgresql start 1> /dev/null && /usr/bin/supervisord
|
||||
# Set the entrypoint
|
||||
COPY ./docker/ee/ee-try-entrypoint-lts.sh /ee-try-entrypoint-lts.sh
|
||||
RUN chmod +x /ee-try-entrypoint-lts
|
||||
ENTRYPOINT ["/ee-try-entrypoint-lts.sh"]
|
||||
117
docker/ee/ee-try-tooljet.Dockerfile
Normal file
117
docker/ee/ee-try-tooljet.Dockerfile
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
FROM tooljet/tooljet:ee-latest
|
||||
|
||||
# Copy postgrest executable
|
||||
COPY --from=postgrest/postgrest:v12.2.0 /bin/postgrest /bin
|
||||
|
||||
# Install Postgres
|
||||
USER root
|
||||
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
|
||||
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ bullseye-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list
|
||||
RUN echo "deb http://deb.debian.org/debian"
|
||||
RUN apt update && apt -y install postgresql-13 postgresql-client-13 supervisor
|
||||
USER postgres
|
||||
RUN service postgresql start && \
|
||||
psql -c "create role tooljet with login superuser password 'postgres';"
|
||||
USER root
|
||||
|
||||
|
||||
RUN apt update && apt -y install redis
|
||||
|
||||
# Create appuser home & ensure permission for supervisord and services
|
||||
RUN mkdir -p /var/log/supervisor /var/run/postgresql /var/lib/postgresql /var/lib/redis && \
|
||||
chown -R appuser:appuser /etc/supervisor /var/log/supervisor /var/lib/redis && \
|
||||
chown -R postgres:postgres /var/run/postgresql /var/lib/postgresql
|
||||
|
||||
# Install Temporal Server Binaries
|
||||
RUN curl -OL https://github.com/temporalio/temporal/releases/download/v1.24.2/temporal_1.24.2_linux_amd64.tar.gz && \
|
||||
tar -xzf temporal_1.24.2_linux_amd64.tar.gz && \
|
||||
mv temporal-server /usr/bin/temporal-server && \
|
||||
chmod +x /usr/bin/temporal-server && \
|
||||
rm temporal_1.24.2_linux_amd64.tar.gz
|
||||
|
||||
# Install Temporal UI Server Binaries
|
||||
RUN curl -OL https://github.com/temporalio/ui-server/releases/download/v2.28.0/ui-server_2.28.0_linux_amd64.tar.gz && \
|
||||
tar -xzf ui-server_2.28.0_linux_amd64.tar.gz && \
|
||||
mv ui-server /usr/bin/temporal-ui-server && \
|
||||
chmod +x /usr/bin/temporal-ui-server && \
|
||||
rm ui-server_2.28.0_linux_amd64.tar.gz
|
||||
|
||||
# Copy Temporal configuration files
|
||||
COPY ./docker/ee/temporal-server.yaml /etc/temporal/temporal-server.yaml
|
||||
COPY ./docker/ee/temporal-ui-server.yaml /etc/temporal/temporal-ui-server.yaml
|
||||
|
||||
# Install grpcurl
|
||||
RUN apt update && apt install -y curl \
|
||||
&& curl -sSL https://github.com/fullstorydev/grpcurl/releases/download/v1.8.0/grpcurl_1.8.0_linux_x86_64.tar.gz | tar -xzv -C /usr/local/bin grpcurl
|
||||
|
||||
# Configure Supervisor to manage PostgREST, ToolJet, and Redis
|
||||
RUN echo "[supervisord] \n" \
|
||||
"nodaemon=true \n" \
|
||||
"user=root \n" \
|
||||
"\n" \
|
||||
"[program:postgrest] \n" \
|
||||
"command=/bin/postgrest \n" \
|
||||
"autostart=true \n" \
|
||||
"autorestart=true \n" \
|
||||
"\n" \
|
||||
"[program:tooljet] \n" \
|
||||
"user=appuser \n" \
|
||||
"command=/bin/bash -c '/app/server/scripts/init-db-boot.sh' \n" \
|
||||
"autostart=true \n" \
|
||||
"autorestart=true \n" \
|
||||
"stderr_logfile=/dev/stdout \n" \
|
||||
"stderr_logfile_maxbytes=0 \n" \
|
||||
"stdout_logfile=/dev/stdout \n" \
|
||||
"stdout_logfile_maxbytes=0 \n" \
|
||||
"\n" \
|
||||
"[program:redis] \n" \
|
||||
"user=appuser \n" \
|
||||
"command=/usr/bin/redis-server \n" \
|
||||
"autostart=true \n" \
|
||||
"autorestart=true \n" \
|
||||
"stderr_logfile=/dev/stdout \n" \
|
||||
"stderr_logfile_maxbytes=0 \n" \
|
||||
"stdout_logfile=/dev/stdout \n" \
|
||||
"stdout_logfile_maxbytes=0 \n" | sed 's/ //' > /etc/supervisor/conf.d/supervisord.conf
|
||||
|
||||
|
||||
# ENV defaults
|
||||
ENV TOOLJET_HOST=http://localhost \
|
||||
TOOLJET_SERVER_URL=http://localhost \
|
||||
PORT=80 \
|
||||
NODE_ENV=production \
|
||||
LOCKBOX_MASTER_KEY=replace_with_lockbox_master_key \
|
||||
SECRET_KEY_BASE=replace_with_secret_key_base \
|
||||
PG_DB=tooljet_production \
|
||||
PG_USER=tooljet \
|
||||
PG_PASS=postgres \
|
||||
PG_HOST=localhost \
|
||||
ENABLE_TOOLJET_DB=true \
|
||||
TOOLJET_DB_HOST=localhost \
|
||||
TOOLJET_DB_USER=tooljet \
|
||||
TOOLJET_DB_PASS=postgres \
|
||||
TOOLJET_DB=tooljet_db \
|
||||
PGRST_HOST=http://localhost:3000 \
|
||||
PGRST_DB_URI=postgres://tooljet:postgres@localhost/tooljet_db \
|
||||
PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj \
|
||||
PGRST_DB_PRE_CONFIG=postgrest.pre_config \
|
||||
ORM_LOGGING=true \
|
||||
DEPLOYMENT_PLATFORM=docker:local \
|
||||
HOME=/home/appuser \
|
||||
REDIS_HOST=localhost \
|
||||
REDIS_PORT=6379 \
|
||||
REDIS_USER=default \
|
||||
REDIS_PASS= \
|
||||
ENABLE_MARKETPLACE_FEATURE=true \
|
||||
TERM=xterm \
|
||||
ENABLE_WORKFLOW_SCHEDULING=true \
|
||||
TEMPORAL_SERVER_ADDRESS=localhost:7233 \
|
||||
TEMPORAL_TASK_QUEUE_NAME_FOR_WORKFLOWS=tooljet-workflows \
|
||||
TOOLJET_WORKFLOWS_TEMPORAL_NAMESPACE=default \
|
||||
TEMPORAL_ADDRESS=localhost:7233 \
|
||||
TEMPORAL_CORS_ORIGINS=http://localhost:8080
|
||||
|
||||
# Set the entrypoint
|
||||
COPY ./docker/ee/ee-try-entrypoint.sh /ee-try-entrypoint.sh
|
||||
RUN chmod +x /ee-try-entrypoint.sh
|
||||
ENTRYPOINT ["/ee-try-entrypoint.sh"]
|
||||
75
docker/ee/temporal-server.yaml
Normal file
75
docker/ee/temporal-server.yaml
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
log:
|
||||
stdout: true
|
||||
level: info
|
||||
|
||||
persistence:
|
||||
defaultStore: sqlite-default
|
||||
visibilityStore: sqlite-visibility
|
||||
numHistoryShards: 4
|
||||
datastores:
|
||||
sqlite-default:
|
||||
sql:
|
||||
pluginName: "sqlite"
|
||||
databaseName: "/etc/temporal/default.db"
|
||||
connectAddr: "localhost"
|
||||
connectProtocol: "tcp"
|
||||
connectAttributes:
|
||||
cache: "private"
|
||||
setup: true
|
||||
|
||||
sqlite-visibility:
|
||||
sql:
|
||||
pluginName: "sqlite"
|
||||
databaseName: "/etc/temporal/visibility.db"
|
||||
connectAddr: "localhost"
|
||||
connectProtocol: "tcp"
|
||||
connectAttributes:
|
||||
cache: "private"
|
||||
setup: true
|
||||
|
||||
global:
|
||||
membership:
|
||||
maxJoinDuration: 30s
|
||||
broadcastAddress: "127.0.0.1"
|
||||
pprof:
|
||||
port: 7936
|
||||
|
||||
services:
|
||||
frontend:
|
||||
rpc:
|
||||
grpcPort: 7233
|
||||
membershipPort: 6933
|
||||
bindOnLocalHost: true
|
||||
httpPort: 7243
|
||||
|
||||
matching:
|
||||
rpc:
|
||||
grpcPort: 7235
|
||||
membershipPort: 6935
|
||||
bindOnLocalHost: true
|
||||
|
||||
history:
|
||||
rpc:
|
||||
grpcPort: 7234
|
||||
membershipPort: 6934
|
||||
bindOnLocalHost: true
|
||||
|
||||
worker:
|
||||
rpc:
|
||||
membershipPort: 6939
|
||||
|
||||
clusterMetadata:
|
||||
enableGlobalNamespace: false
|
||||
failoverVersionIncrement: 10
|
||||
masterClusterName: "active"
|
||||
currentClusterName: "active"
|
||||
clusterInformation:
|
||||
active:
|
||||
enabled: true
|
||||
initialFailoverVersion: 1
|
||||
rpcName: "frontend"
|
||||
rpcAddress: "localhost:7236"
|
||||
httpAddress: "localhost:7243"
|
||||
|
||||
dcRedirectionPolicy:
|
||||
policy: "noop"
|
||||
8
docker/ee/temporal-ui-server.yaml
Normal file
8
docker/ee/temporal-ui-server.yaml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
temporalGrpcAddress: 127.0.0.1:7233 # Use the correct Temporal server address
|
||||
host: 0.0.0.0
|
||||
port: 8080
|
||||
enableUi: true
|
||||
cors:
|
||||
allowOrigins:
|
||||
- http://localhost:8080
|
||||
defaultNamespace: default
|
||||
|
|
@ -22,6 +22,8 @@ import {
|
|||
handleActivateTargets,
|
||||
handleDeactivateTargets,
|
||||
handleActivateNonDraggingComponents,
|
||||
computeScrollDelta,
|
||||
computeScrollDeltaOnDrag,
|
||||
} from './gridUtils';
|
||||
import { dragContextBuilder, getAdjustedDropPosition } from './helpers/dragEnd';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
|
|
@ -56,6 +58,7 @@ export default function Grid({ gridWidth, currentLayout }) {
|
|||
const canvasWidth = NO_OF_GRIDS * gridWidth;
|
||||
const getHoveredComponentForGrid = useStore((state) => state.getHoveredComponentForGrid, shallow);
|
||||
const getResolvedComponent = useStore((state) => state.getResolvedComponent, shallow);
|
||||
const updateContainerAutoHeight = useStore((state) => state.updateContainerAutoHeight, shallow);
|
||||
const [canvasBounds, setCanvasBounds] = useState(CANVAS_BOUNDS);
|
||||
const draggingComponentId = useStore((state) => state.draggingComponentId, shallow);
|
||||
const resizingComponentId = useGridStore((state) => state.resizingComponentId, shallow);
|
||||
|
|
@ -345,6 +348,7 @@ export default function Grid({ gridWidth, currentLayout }) {
|
|||
const handleDragEnd = useCallback(
|
||||
(boxPositions) => {
|
||||
let newParent = null;
|
||||
let oldParent = null;
|
||||
const updatedLayouts = boxPositions.reduce((layouts, { id, x, y, parent }) => {
|
||||
const currentWidget = boxList.find((box) => box.id === id);
|
||||
const containerWidth = parent ? useGridStore.getState().subContainerWidths[parent] : gridWidth;
|
||||
|
|
@ -389,7 +393,7 @@ export default function Grid({ gridWidth, currentLayout }) {
|
|||
}
|
||||
}
|
||||
newParent = parent ? parent : null;
|
||||
|
||||
oldParent = currentWidget.component?.parent;
|
||||
layouts[id] = {
|
||||
width: _width,
|
||||
height: _height,
|
||||
|
|
@ -400,6 +404,11 @@ export default function Grid({ gridWidth, currentLayout }) {
|
|||
return layouts;
|
||||
}, {});
|
||||
setComponentLayout(updatedLayouts, newParent, undefined, { updateParent: true });
|
||||
|
||||
// const currentWidget = boxList.find((box) => box.id === id);
|
||||
updateContainerAutoHeight(newParent);
|
||||
updateContainerAutoHeight(oldParent);
|
||||
|
||||
toggleCanvasUpdater();
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
|
@ -579,6 +588,11 @@ export default function Grid({ gridWidth, currentLayout }) {
|
|||
keepRatio={false}
|
||||
individualGroupableProps={individualGroupableProps}
|
||||
onResize={(e) => {
|
||||
if(resizingComponentId !== e.target.id) {
|
||||
useGridStore.getState().actions.setResizingComponentId(e.target.id);
|
||||
showGridLines();
|
||||
}
|
||||
|
||||
const currentWidget = boxList.find(({ id }) => id === e.target.id);
|
||||
let _gridWidth = useGridStore.getState().subContainerWidths[currentWidget.component?.parent] || gridWidth;
|
||||
if (currentWidget.component?.parent) {
|
||||
|
|
@ -639,9 +653,7 @@ export default function Grid({ gridWidth, currentLayout }) {
|
|||
return false;
|
||||
}
|
||||
handleActivateNonDraggingComponents();
|
||||
useGridStore.getState().actions.setResizingComponentId(e.target.id);
|
||||
e.setMin([gridWidth, GRID_HEIGHT]);
|
||||
showGridLines();
|
||||
}}
|
||||
onResizeEnd={(e) => {
|
||||
try {
|
||||
|
|
@ -867,20 +879,19 @@ export default function Grid({ gridWidth, currentLayout }) {
|
|||
|
||||
const targetSlotId = target?.slotId;
|
||||
const targetGridWidth = useGridStore.getState().subContainerWidths[targetSlotId] || gridWidth;
|
||||
|
||||
// const restrictedWidgets = RESTRICTED_WIDGETS_CONFIG?.[source.widgetType] || [];
|
||||
// const draggedWidgetType = dragged.widgetType;
|
||||
const isParentChangeAllowed = dragContext.isDroppable;
|
||||
|
||||
// Compute new position
|
||||
let { left, top } = getAdjustedDropPosition(e, target, isParentChangeAllowed, targetGridWidth, dragged);
|
||||
|
||||
const isModalToCanvas = source.isModal && target.slotId === 'real-canvas';
|
||||
let scrollDelta = computeScrollDelta({ source });
|
||||
|
||||
if (isParentChangeAllowed && !isModalToCanvas) {
|
||||
const parent = target.slotId === 'real-canvas' ? null : target.slotId;
|
||||
// Special case for Modal; If source widget is modal, prevent drops to canvas
|
||||
handleDragEnd([{ id: e.target.id, x: left, y: top, parent }]);
|
||||
const parent = target.slotId === 'real-canvas' ? null : target.slotId;
|
||||
|
||||
handleDragEnd([{ id: e.target.id, x: left, y: top + scrollDelta, parent }]);
|
||||
} else {
|
||||
const sourcegridWidth = useGridStore.getState().subContainerWidths[source.slotId] || gridWidth;
|
||||
|
||||
|
|
@ -889,9 +900,8 @@ export default function Grid({ gridWidth, currentLayout }) {
|
|||
!isModalToCanvas ??
|
||||
toast.error(`${dragged.widgetType} is not compatible as a child component of ${target.widgetType}`);
|
||||
}
|
||||
|
||||
// Apply transform for smooth transition
|
||||
e.target.style.transform = `translate(${left}px, ${top}px)`;
|
||||
e.target.style.transform = `translate(${left}px, ${top + scrollDelta}px)`;
|
||||
|
||||
// Force reordering of conatiner if the parent has not changed
|
||||
const newParentId = target.slotId === 'real-canvas' ? 'canvas' : target.slotId;
|
||||
|
|
@ -945,7 +955,7 @@ export default function Grid({ gridWidth, currentLayout }) {
|
|||
const isParentModal = isParentNewModal || isParentLegacyModal || isParentModalSlot;
|
||||
|
||||
if (isParentModal) {
|
||||
const modalContainer = e.target.closest('.tj-modal-widget-content');
|
||||
const modalContainer = e.target.closest('.tj-modal--container');
|
||||
const mainCanvas = document.getElementById('real-canvas');
|
||||
|
||||
const mainRect = mainCanvas.getBoundingClientRect();
|
||||
|
|
@ -959,12 +969,6 @@ export default function Grid({ gridWidth, currentLayout }) {
|
|||
setCanvasBounds({ ...relativePosition });
|
||||
}
|
||||
|
||||
e.target.style.transform = `translate(${left}px, ${top}px)`;
|
||||
e.target.setAttribute(
|
||||
'widget-pos2',
|
||||
`translate: ${e.translate[0]} | Round: ${Math.round(e.translate[0] / gridWidth) * gridWidth} | ${gridWidth}`
|
||||
);
|
||||
|
||||
// This block is to show grid lines on the canvas when the dragged element is over a new canvas
|
||||
if (document.elementFromPoint(e.clientX, e.clientY)) {
|
||||
const targetElems = document.elementsFromPoint(e.clientX, e.clientY);
|
||||
|
|
@ -992,6 +996,17 @@ export default function Grid({ gridWidth, currentLayout }) {
|
|||
handleActivateTargets(newParentId);
|
||||
}
|
||||
}
|
||||
|
||||
// Build the drag context from the event
|
||||
const source = { slotId: oldParentId };
|
||||
let scrollDelta = computeScrollDeltaOnDrag({ source });
|
||||
|
||||
e.target.style.transform = `translate(${left}px, ${top - scrollDelta}px)`;
|
||||
e.target.setAttribute(
|
||||
'widget-pos2',
|
||||
`translate: ${e.translate[0]} | Round: ${Math.round(e.translate[0] / gridWidth) * gridWidth} | ${gridWidth}`
|
||||
);
|
||||
|
||||
// Postion ghost element exactly as same at dragged element
|
||||
if (document.getElementById(`moveable-drag-ghost`)) {
|
||||
document.getElementById(`moveable-drag-ghost`).style.transform = `translate(${left}px, ${top}px)`;
|
||||
|
|
@ -1081,6 +1096,7 @@ export default function Grid({ gridWidth, currentLayout }) {
|
|||
}
|
||||
}}
|
||||
snapGridAll={true}
|
||||
scrollable={true}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -415,6 +415,20 @@ export function hideGridLines() {
|
|||
document.getElementById('real-canvas')?.classList.add('hide-grid');
|
||||
}
|
||||
|
||||
export function showGridLinesOnSlot(slotId) {
|
||||
var canvasElm = document.getElementById(`canvas-${slotId}`);
|
||||
|
||||
canvasElm.classList.remove('hide-grid');
|
||||
canvasElm.classList.add('show-grid');
|
||||
}
|
||||
|
||||
export function hideGridLinesOnSlot(slotId) {
|
||||
var canvasElm = document.getElementById(`canvas-${slotId}`);
|
||||
|
||||
canvasElm.classList.remove('show-grid');
|
||||
canvasElm.classList.add('hide-grid');
|
||||
}
|
||||
|
||||
// Track previously active elements for efficient cleanup
|
||||
let previousActiveWidgets = null;
|
||||
let previousActiveCanvas = null;
|
||||
|
|
@ -488,3 +502,18 @@ export const handleDeactivateTargets = () => {
|
|||
component.classList.remove('non-dragging-component');
|
||||
});
|
||||
};
|
||||
export const computeScrollDelta = ({ source }) => {
|
||||
// Only need to calculate scroll delta when moving from a sub-container
|
||||
if (source.slotId !== 'real-canvas') {
|
||||
const subContainerWrap = document
|
||||
.querySelector(`#canvas-${source.slotId}`)
|
||||
?.closest('.sub-container-overflow-wrap');
|
||||
|
||||
return subContainerWrap?.scrollTop || 0;
|
||||
}
|
||||
|
||||
// Default case: No scroll adjustment needed
|
||||
return 0;
|
||||
};
|
||||
|
||||
export const computeScrollDeltaOnDrag = computeScrollDelta;
|
||||
|
|
|
|||
|
|
@ -175,7 +175,6 @@ export class DragContext {
|
|||
|
||||
const restrictedWidgets = [...restrictedWidgetsOnTarget, ...restrictedWidgetsOnTargetSlot];
|
||||
return !restrictedWidgets.includes(dragged.widgetType);
|
||||
ß;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ const SHOULD_ADD_BOX_SHADOW_AND_VISIBILITY = [
|
|||
'Divider',
|
||||
'VerticalDivider',
|
||||
'Link',
|
||||
'Form',
|
||||
];
|
||||
|
||||
const RenderWidget = ({
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import useStore from '@/AppBuilder/_stores/store';
|
|||
import { shallow } from 'zustand/shallow';
|
||||
import { search, searchKeymap, searchPanelOpen } from '@codemirror/search';
|
||||
import { handleSearchPanel, SearchBtn } from './SearchBox';
|
||||
import { useQueryPanelKeyHooks } from './useQueryPanelKeyHooks';
|
||||
import { isInsideParent } from './utils';
|
||||
|
||||
const langSupport = Object.freeze({
|
||||
|
|
@ -73,6 +74,8 @@ const MultiLineCodeEditor = (props) => {
|
|||
|
||||
const [editorView, setEditorView] = React.useState(null);
|
||||
|
||||
const { queryPanelKeybindings } = useQueryPanelKeyHooks(onChange, currentValueRef, 'multiline');
|
||||
|
||||
const handleOnBlur = () => {
|
||||
if (!delayOnChange) return onChange(currentValueRef.current);
|
||||
setTimeout(() => {
|
||||
|
|
@ -94,6 +97,7 @@ const MultiLineCodeEditor = (props) => {
|
|||
highlightActiveLine: false,
|
||||
autocompletion: hideSuggestion ?? true,
|
||||
highlightActiveLineGutter: false,
|
||||
defaultKeymap: false,
|
||||
completionKeymap: true,
|
||||
searchKeymap: false,
|
||||
};
|
||||
|
|
@ -203,7 +207,12 @@ const MultiLineCodeEditor = (props) => {
|
|||
};
|
||||
}
|
||||
|
||||
const customKeyMaps = [...defaultKeymap, ...completionKeymap, ...searchKeymap];
|
||||
const customKeyMaps = [
|
||||
...defaultKeymap.filter((keyBinding) => keyBinding.key !== 'Mod-Enter'), // Remove default keybinding for Mod-Enter
|
||||
...completionKeymap,
|
||||
...searchKeymap,
|
||||
];
|
||||
|
||||
const customTabKeymap = keymap.of([
|
||||
{
|
||||
key: 'Tab',
|
||||
|
|
@ -224,6 +233,7 @@ const MultiLineCodeEditor = (props) => {
|
|||
return true;
|
||||
},
|
||||
},
|
||||
...queryPanelKeybindings,
|
||||
]);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import CodeHinter from './CodeHinter';
|
|||
import { removeNestedDoubleCurlyBraces } from '@/_helpers/utils';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { useQueryPanelKeyHooks } from './useQueryPanelKeyHooks';
|
||||
|
||||
const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...restProps }) => {
|
||||
const { initialValue, onChange, enablePreview = true, portalProps } = restProps;
|
||||
|
|
@ -201,6 +202,8 @@ const EditorInput = ({
|
|||
const getServerSideGlobalSuggestions = useStore((state) => state.getServerSideGlobalSuggestions, shallow);
|
||||
|
||||
const getSuggestions = useStore((state) => state.getSuggestions, shallow);
|
||||
const { queryPanelKeybindings } = useQueryPanelKeyHooks(onBlurUpdate, currentValue, 'singleline');
|
||||
|
||||
const isInsideQueryManager = useMemo(
|
||||
() => isInsideParent(wrapperRef?.current, 'query-manager'),
|
||||
[wrapperRef.current]
|
||||
|
|
@ -271,7 +274,10 @@ const EditorInput = ({
|
|||
maxRenderedOptions: 10,
|
||||
});
|
||||
|
||||
const customKeyMaps = [...defaultKeymap, ...completionKeymap];
|
||||
const customKeyMaps = [
|
||||
...defaultKeymap.filter((keyBinding) => keyBinding.key !== 'Mod-Enter'), // Remove default keybinding for Mod-Enter
|
||||
...completionKeymap,
|
||||
];
|
||||
const customTabKeymap = keymap.of([
|
||||
{
|
||||
key: 'Tab',
|
||||
|
|
@ -293,6 +299,7 @@ const EditorInput = ({
|
|||
}
|
||||
},
|
||||
},
|
||||
...queryPanelKeybindings,
|
||||
]);
|
||||
|
||||
const handleOnChange = React.useCallback((val) => {
|
||||
|
|
@ -442,7 +449,8 @@ const EditorInput = ({
|
|||
bracketMatching: true,
|
||||
foldGutter: false,
|
||||
highlightActiveLine: false,
|
||||
autocompletion: showSuggestions,
|
||||
autocompletion: true,
|
||||
defaultKeymap: false,
|
||||
completionKeymap: true,
|
||||
searchKeymap: false,
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -220,6 +220,9 @@
|
|||
.query-hinter{
|
||||
flex-grow: 1;
|
||||
}
|
||||
.cm-editor {
|
||||
min-height: 150px !important;
|
||||
}
|
||||
}
|
||||
.code-editor-query-panel{
|
||||
&.show-line-numbers{
|
||||
|
|
@ -398,6 +401,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.rest-api-body-codehinter {
|
||||
.cm-editor {
|
||||
min-height: 150px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.border-danger {
|
||||
.cm-editor {
|
||||
border: 1px solid red !important;
|
||||
|
|
|
|||
58
frontend/src/AppBuilder/CodeEditor/useQueryPanelKeyHooks.js
Normal file
58
frontend/src/AppBuilder/CodeEditor/useQueryPanelKeyHooks.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
export const useQueryPanelKeyHooks = (onChange, value, type) => {
|
||||
const queryPanelHeight = useStore((state) => state.queryPanel.queryPanelHeight);
|
||||
const runQueryOnShortcut = useStore((state) => state.queryPanel.runQueryOnShortcut);
|
||||
const previewQueryOnShortcut = useStore((state) => state.queryPanel.previewQueryOnShortcut);
|
||||
const moduleId = useModuleId();
|
||||
const location = useLocation();
|
||||
const { pathname } = location;
|
||||
|
||||
const [queryPanelKeybindings, setQueryPanelKeybindings] = useState([]);
|
||||
|
||||
const handleRunQuery = useCallback(
|
||||
(view) => {
|
||||
const isEditor = pathname.includes('/apps/');
|
||||
if (queryPanelHeight !== 0 && isEditor) {
|
||||
onChange(type === 'multiline' ? value.current : value);
|
||||
runQueryOnShortcut();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[queryPanelHeight, onChange, runQueryOnShortcut, value]
|
||||
);
|
||||
|
||||
const handlePreviewQuery = useCallback(
|
||||
(view) => {
|
||||
const isEditor = pathname.includes('/apps/');
|
||||
if (queryPanelHeight !== 0 && isEditor) {
|
||||
onChange(type === 'multiline' ? value.current : value);
|
||||
previewQueryOnShortcut(moduleId);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[queryPanelHeight, moduleId, onChange, previewQueryOnShortcut, value]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setQueryPanelKeybindings([
|
||||
{
|
||||
key: 'Mod-Enter',
|
||||
preventDefault: true,
|
||||
run: handleRunQuery,
|
||||
},
|
||||
{
|
||||
key: 'Mod-Shift-Enter',
|
||||
preventDefault: true,
|
||||
run: handlePreviewQuery,
|
||||
},
|
||||
]);
|
||||
}, [handleRunQuery, handlePreviewQuery]);
|
||||
|
||||
return {
|
||||
queryPanelKeybindings,
|
||||
};
|
||||
};
|
||||
|
|
@ -381,7 +381,6 @@ export const BaseQueryManagerBody = ({ darkMode, activeTab, renderCopilot = () =
|
|||
{activeTab === 1 && renderQueryElement()}
|
||||
{activeTab === 2 && renderTransformation()}
|
||||
{activeTab === 3 && renderQueryOptions()}
|
||||
<div className="pb-5" />
|
||||
<Preview darkMode={darkMode} calculatePreviewHeight={calculatePreviewHeight} />
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
import React, { useState, forwardRef, useRef, useEffect, useCallback } from 'react';
|
||||
import RenameIcon from '../Icons/RenameIcon';
|
||||
import Eye1 from '@/_ui/Icon/solidIcons/Eye1';
|
||||
import Play from '@/_ui/Icon/solidIcons/Play';
|
||||
import cx from 'classnames';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { DATA_SOURCE_TYPE } from '@/_helpers/constants';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { ToolTip } from '@/_components';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import { decodeEntities } from '@/_helpers/utils';
|
||||
import { canDeleteDataSource, canReadDataSource, canUpdateDataSource } from '@/_helpers';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
import { Button as ButtonComponent } from '@/components/ui/Button/Button';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
export const QueryManagerHeader = forwardRef(({ darkMode, setActiveTab, activeTab }, ref) => {
|
||||
|
|
@ -27,6 +26,7 @@ export const QueryManagerHeader = forwardRef(({ darkMode, setActiveTab, activeTa
|
|||
const setShowCreateQuery = useStore((state) => state.queryPanel.setShowCreateQuery);
|
||||
const queryName = selectedQuery?.name ?? '';
|
||||
const shouldFreeze = useStore((state) => state.getShouldFreeze());
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedQuery?.name) {
|
||||
setShowCreateQuery(false);
|
||||
|
|
@ -244,34 +244,26 @@ const RunButton = ({ buttonLoadingState }) => {
|
|||
const isLoading = useStore(
|
||||
(state) => state.resolvedStore.modules.canvas.exposedValues.queries[selectedQuery?.id]?.isLoading ?? false
|
||||
);
|
||||
const isMac = typeof navigator !== 'undefined' && navigator?.userAgent?.toLowerCase().includes('mac');
|
||||
|
||||
const shortcutDisplay = isMac ? 'Run query ⌘↩' : 'Run query Ctrl+Enter';
|
||||
return (
|
||||
<span
|
||||
{...(isInDraft && {
|
||||
'data-tooltip-id': 'query-header-btn-run',
|
||||
'data-tooltip-content': 'Connect a data source to run',
|
||||
})}
|
||||
>
|
||||
<button
|
||||
onClick={() => runQuery(selectedQuery?.id, selectedQuery?.name, undefined, 'edit', {}, true)}
|
||||
className={`border-0 default-secondary-button ${buttonLoadingState(isLoading)}`}
|
||||
data-cy="query-run-button"
|
||||
disabled={isInDraft}
|
||||
{...(isInDraft && {
|
||||
'data-tooltip-id': 'query-header-btn-run',
|
||||
'data-tooltip-content': 'Publish the query to run',
|
||||
})}
|
||||
>
|
||||
<span
|
||||
className={cx({
|
||||
invisible: isLoading,
|
||||
})}
|
||||
<span>
|
||||
<ToolTip message={shortcutDisplay} placement="bottom" trigger={['hover']} show={true} tooltipClassName="">
|
||||
<ButtonComponent
|
||||
size="medium"
|
||||
variant="secondary"
|
||||
onClick={() => runQuery(selectedQuery?.id, selectedQuery?.name, undefined, 'edit', {}, true)}
|
||||
leadingIcon="play01"
|
||||
disabled={isInDraft}
|
||||
isLoading={isLoading}
|
||||
className={isMac ? '!tw-w-[88px]' : '!tw-w-[120px]'}
|
||||
data-cy="query-run-button"
|
||||
>
|
||||
<Play width={14} fill="var(--indigo9)" viewBox="0 0 14 14" />
|
||||
</span>
|
||||
<span className="query-manager-btn-name">{isLoading ? ' ' : 'Run'}</span>
|
||||
</button>
|
||||
{isInDraft && <Tooltip id="query-header-btn-run" className="tooltip" />}
|
||||
Run
|
||||
<span className="query-manager-btn-shortcut">{isMac ? '⌘↩' : 'Ctrl+Enter'}</span>
|
||||
</ButtonComponent>
|
||||
</ToolTip>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
|
@ -287,20 +279,22 @@ const PreviewButton = ({ buttonLoadingState, onClick }) => {
|
|||
: true;
|
||||
const isPreviewQueryLoading = useStore((state) => state.queryPanel.isPreviewQueryLoading);
|
||||
const { t } = useTranslation();
|
||||
const isMac = typeof navigator !== 'undefined' && navigator?.userAgent?.toLowerCase().includes('mac');
|
||||
|
||||
const shortcutDisplay = `Preview query ${isMac ? '⌘⇧↩' : 'Ctrl+Shift+Enter'}`;
|
||||
return (
|
||||
<button
|
||||
disabled={!hasPermissions}
|
||||
onClick={onClick}
|
||||
className={cx(`default-tertiary-button ${buttonLoadingState(isPreviewQueryLoading)} `, {
|
||||
disabled: !hasPermissions,
|
||||
})}
|
||||
data-cy={'query-preview-button'}
|
||||
>
|
||||
<span className="query-preview-svg d-flex align-items-center query-icon-wrapper">
|
||||
<Eye1 width={14} fill="var(--slate9)" />
|
||||
</span>
|
||||
<span>{t('editor.queryManager.preview', 'Preview')}</span>
|
||||
</button>
|
||||
<ToolTip message={shortcutDisplay} placement="bottom" trigger={['hover']} show={true} tooltipClassName="">
|
||||
<ButtonComponent
|
||||
size="medium"
|
||||
variant="outline"
|
||||
onClick={onClick}
|
||||
// className="!tw-w-[100px]"
|
||||
disabled={!hasPermissions}
|
||||
isLoading={isPreviewQueryLoading}
|
||||
data-cy={'query-preview-button'}
|
||||
>
|
||||
Preview
|
||||
</ButtonComponent>
|
||||
</ToolTip>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ export const Transformation = ({ changeOption, options, darkMode, queryId, rende
|
|||
<br />
|
||||
<div className={`d-flex copilot-codehinter-wrap ${!enableTransformation && 'read-only-codehinter'}`}>
|
||||
<div className="col flex-grow-1">
|
||||
<div style={{ borderRadius: '6px', marginBottom: '20px', background: darkMode ? '#272822' : '#F8F9FA' }}>
|
||||
<div style={{ borderRadius: '6px', background: darkMode ? '#272822' : '#F8F9FA' }}>
|
||||
<div className="py-3 px-3 d-flex justify-content-between copilot-section-header">
|
||||
<Tab.Container
|
||||
activeKey={lang}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ export default ({
|
|||
);
|
||||
})}
|
||||
{bodyToggle && (
|
||||
<div>
|
||||
<div className="rest-api-body-codehinter">
|
||||
<CodeHinter
|
||||
type="extendedSingleLine"
|
||||
initialValue={(rawBody || jsonBody) ?? ''} // If raw_body is not set, set initial value to legacy json_body if present
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const Runjs = (props) => {
|
|||
}, [props.options]);
|
||||
|
||||
return (
|
||||
<Card className="runjs-editor mb-3">
|
||||
<Card className="runjs-editor mb-3 !tw-mb-0">
|
||||
<CodeHinter
|
||||
renderCopilot={props.renderCopilot}
|
||||
type="multiline"
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export class Runpy extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<div className="runps-editor mb-3">
|
||||
<div className="runps-editor mb-3 !tw-mb-0">
|
||||
<CodeHinter
|
||||
renderCopilot={this.props.renderCopilot}
|
||||
type="multiline"
|
||||
|
|
|
|||
26
frontend/src/AppBuilder/QueryPanel/QueryKeyHooks.jsx
Normal file
26
frontend/src/AppBuilder/QueryPanel/QueryKeyHooks.jsx
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import React from 'react';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
|
||||
const QueryKeyHooks = ({ children, isExpanded }) => {
|
||||
const runQueryOnShortcut = useStore((state) => state.queryPanel.runQueryOnShortcut);
|
||||
const previewQueryOnShortcut = useStore((state) => state.queryPanel.previewQueryOnShortcut);
|
||||
const moduleId = useModuleId();
|
||||
|
||||
useHotkeys(
|
||||
['mod+enter', 'mod+shift+enter'],
|
||||
(event, handler) => {
|
||||
if (handler.mod && handler.keys[0] === 'enter') {
|
||||
if (handler.shift) {
|
||||
previewQueryOnShortcut(moduleId);
|
||||
} else runQueryOnShortcut();
|
||||
}
|
||||
},
|
||||
{ enabled: isExpanded, enableOnFormTags: ['input'] }
|
||||
);
|
||||
|
||||
return <div className="row main-row">{children}</div>;
|
||||
};
|
||||
|
||||
export default QueryKeyHooks;
|
||||
|
|
@ -11,6 +11,7 @@ import useStore from '@/AppBuilder/_stores/store';
|
|||
import SectionCollapse from '@/_ui/Icon/solidIcons/SectionCollapse';
|
||||
import SectionExpand from '@/_ui/Icon/solidIcons/SectionExpand';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import QueryKeyHooks from './QueryKeyHooks';
|
||||
|
||||
const MemoizedQueryDataPane = memo(QueryDataPane);
|
||||
const MemoizedQueryManager = memo(QueryManager);
|
||||
|
|
@ -193,14 +194,14 @@ export const QueryPanel = ({ darkMode }) => {
|
|||
}}
|
||||
>
|
||||
{isExpanded && (
|
||||
<div className="row main-row">
|
||||
<QueryKeyHooks isExpanded={isExpanded}>
|
||||
<MemoizedQueryDataPane darkMode={darkMode} />
|
||||
<div className="query-definition-pane-wrapper">
|
||||
<div className="query-definition-pane">
|
||||
<MemoizedQueryManager darkMode={darkMode} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</QueryKeyHooks>
|
||||
)}
|
||||
</div>
|
||||
<Tooltip id="tooltip-for-query-panel-footer-btn" className="tooltip" />
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ export const Form = ({
|
|||
|
||||
const { id } = component;
|
||||
const newOptions = [{ name: 'None', value: 'none' }];
|
||||
|
||||
Object.entries(allComponents).forEach(([componentId, _component]) => {
|
||||
const validParent =
|
||||
_component.component.parent === id ||
|
||||
|
|
@ -52,6 +53,19 @@ export const Form = ({
|
|||
|
||||
tempComponentMeta.properties.buttonToSubmit.options = newOptions;
|
||||
|
||||
// Hide header footer if custom schema is turned on
|
||||
|
||||
if (component.component.definition.properties.advanced.value === '{{true}}') {
|
||||
component.component.properties.showHeader = {
|
||||
...component.component.properties.headerHeight,
|
||||
isHidden: true,
|
||||
};
|
||||
component.component.properties.showFooter = {
|
||||
...component.component.properties.headerHeight,
|
||||
isHidden: true,
|
||||
};
|
||||
}
|
||||
|
||||
const accordionItems = baseComponentProperties(
|
||||
properties,
|
||||
events,
|
||||
|
|
@ -110,24 +124,6 @@ export const baseComponentProperties = (
|
|||
});
|
||||
}
|
||||
|
||||
items.push({
|
||||
title: 'Additional actions',
|
||||
isOpen: true,
|
||||
children: additionalActions?.map((property) =>
|
||||
renderElement(
|
||||
component,
|
||||
componentMeta,
|
||||
paramUpdated,
|
||||
dataQueries,
|
||||
property,
|
||||
'properties',
|
||||
currentState,
|
||||
allComponents,
|
||||
darkMode
|
||||
)
|
||||
),
|
||||
});
|
||||
|
||||
if (events.length > 0) {
|
||||
items.push({
|
||||
title: `${i18next.t('widget.common.events', 'Events')}`,
|
||||
|
|
@ -149,6 +145,24 @@ export const baseComponentProperties = (
|
|||
});
|
||||
}
|
||||
|
||||
items.push({
|
||||
title: 'Additional actions',
|
||||
isOpen: true,
|
||||
children: additionalActions?.map((property) =>
|
||||
renderElement(
|
||||
component,
|
||||
componentMeta,
|
||||
paramUpdated,
|
||||
dataQueries,
|
||||
property,
|
||||
'properties',
|
||||
currentState,
|
||||
allComponents,
|
||||
darkMode
|
||||
)
|
||||
),
|
||||
});
|
||||
|
||||
if (validations.length > 0) {
|
||||
items.push({
|
||||
title: `${i18next.t('widget.common.validation', 'Validation')}`,
|
||||
|
|
@ -168,25 +182,6 @@ export const baseComponentProperties = (
|
|||
});
|
||||
}
|
||||
|
||||
items.push({
|
||||
title: `${i18next.t('widget.common.general', 'General')}`,
|
||||
isOpen: true,
|
||||
children: (
|
||||
<>
|
||||
{renderElement(
|
||||
component,
|
||||
componentMeta,
|
||||
layoutPropertyChanged,
|
||||
dataQueries,
|
||||
'tooltip',
|
||||
'general',
|
||||
currentState,
|
||||
allComponents
|
||||
)}
|
||||
</>
|
||||
),
|
||||
});
|
||||
|
||||
items.push({
|
||||
title: `${i18next.t('widget.common.devices', 'Devices')}`,
|
||||
isOpen: true,
|
||||
|
|
|
|||
|
|
@ -539,6 +539,17 @@ export function Select({ componentMeta, darkMode, ...restProps }) {
|
|||
currentState,
|
||||
allComponents
|
||||
)}
|
||||
{isMultiSelect &&
|
||||
renderElement(
|
||||
component,
|
||||
componentMeta,
|
||||
paramUpdated,
|
||||
dataQueries,
|
||||
'showAllSelectedLabel',
|
||||
'properties',
|
||||
currentState,
|
||||
allComponents
|
||||
)}
|
||||
{isSortingEnabled &&
|
||||
renderElement(
|
||||
component,
|
||||
|
|
|
|||
|
|
@ -131,6 +131,19 @@ export const buttonGroupConfig = {
|
|||
defaultValue: 'left',
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
selected: [1],
|
||||
|
|
@ -162,6 +175,7 @@ export const buttonGroupConfig = {
|
|||
borderRadius: { value: '{{4}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
selectedTextColor: { value: '#FFFFFF' },
|
||||
padding: { value: 'default' },
|
||||
selectedBackgroundColor: { value: 'var(--primary-brand)' },
|
||||
alignment: { value: 'left' },
|
||||
},
|
||||
|
|
|
|||
|
|
@ -126,6 +126,20 @@ export const checkboxConfig = {
|
|||
],
|
||||
accordian: 'label',
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
accordian: 'switch',
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
value: false,
|
||||
|
|
@ -189,6 +203,7 @@ export const checkboxConfig = {
|
|||
handleColor: { value: '#FFFFFF' },
|
||||
alignment: { value: 'right' },
|
||||
boxShadow: { value: '0px 0px 0px 0px #00000090' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
validation: {
|
||||
mandatory: { value: '{{false}}' },
|
||||
|
|
|
|||
|
|
@ -28,6 +28,19 @@ export const colorPickerConfig = {
|
|||
},
|
||||
styles: {
|
||||
visibility: { type: 'toggle', displayName: 'Visibility' },
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
selectedColorHex: '#000000',
|
||||
|
|
@ -47,6 +60,7 @@ export const colorPickerConfig = {
|
|||
events: [],
|
||||
styles: {
|
||||
visibility: { value: '{{true}}' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ export const containerConfig = {
|
|||
displayName: 'Container',
|
||||
description: 'Group components',
|
||||
defaultSize: {
|
||||
width: 5,
|
||||
width: 10,
|
||||
height: 200,
|
||||
},
|
||||
component: 'Container',
|
||||
|
|
@ -44,13 +44,19 @@ export const containerConfig = {
|
|||
displayName: 'Show header',
|
||||
validation: {
|
||||
schema: { type: 'boolean' },
|
||||
defaultValue: false,
|
||||
defaultValue: true,
|
||||
},
|
||||
},
|
||||
headerHeight: {
|
||||
type: 'numberInput',
|
||||
displayName: 'Header height',
|
||||
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
|
||||
},
|
||||
},
|
||||
defaultChildren: [
|
||||
{
|
||||
componentName: 'Text',
|
||||
slotName: 'header',
|
||||
layout: {
|
||||
top: 20,
|
||||
left: 1,
|
||||
|
|
@ -98,15 +104,6 @@ export const containerConfig = {
|
|||
},
|
||||
accordian: 'container',
|
||||
},
|
||||
headerHeight: {
|
||||
type: 'numberInput',
|
||||
displayName: 'Height',
|
||||
validation: {
|
||||
schema: { type: 'number' },
|
||||
defaultValue: 80,
|
||||
},
|
||||
accordian: 'header',
|
||||
},
|
||||
borderRadius: {
|
||||
type: 'numberInput',
|
||||
displayName: 'Border',
|
||||
|
|
@ -154,10 +151,11 @@ export const containerConfig = {
|
|||
showOnMobile: { value: '{{false}}' },
|
||||
},
|
||||
properties: {
|
||||
showHeader: { value: `{{false}}` },
|
||||
showHeader: { value: `{{true}}` },
|
||||
loadingState: { value: `{{false}}` },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
headerHeight: { value: `{{80}}` },
|
||||
},
|
||||
events: [],
|
||||
styles: {
|
||||
|
|
|
|||
|
|
@ -75,6 +75,18 @@ export const dropdownV2Config = {
|
|||
accordian: 'Options',
|
||||
isFxNotRequired: true,
|
||||
},
|
||||
showClearBtn: {
|
||||
type: 'toggle',
|
||||
displayName: 'Show clear selection button',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
section: 'additionalActions',
|
||||
},
|
||||
showSearchInput: {
|
||||
type: 'toggle',
|
||||
displayName: 'Show search in options',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
section: 'additionalActions',
|
||||
},
|
||||
loadingState: {
|
||||
type: 'toggle',
|
||||
displayName: 'Loading state',
|
||||
|
|
@ -314,6 +326,8 @@ export const dropdownV2Config = {
|
|||
optionsLoadingState: { value: '{{false}}' },
|
||||
sort: { value: 'asc' },
|
||||
placeholder: { value: 'Select an option' },
|
||||
showClearBtn: { value: '{{true}}' },
|
||||
showSearchInput: { value: '{{true}}' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
loadingState: { value: '{{false}}' },
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ export const formConfig = {
|
|||
description: 'Wrapper for multiple components',
|
||||
defaultSize: {
|
||||
width: 13,
|
||||
height: 480,
|
||||
height: 450,
|
||||
},
|
||||
defaultChildren: [
|
||||
{
|
||||
|
|
@ -19,8 +19,8 @@ export const formConfig = {
|
|||
accessorKey: 'text',
|
||||
styles: ['fontWeight', 'textSize', 'textColor'],
|
||||
defaultValue: {
|
||||
text: 'Form title',
|
||||
textSize: 20,
|
||||
text: 'Form',
|
||||
textSize: 16,
|
||||
textColor: '#000',
|
||||
},
|
||||
},
|
||||
|
|
@ -34,203 +34,68 @@ export const formConfig = {
|
|||
},
|
||||
properties: ['text'],
|
||||
defaultValue: {
|
||||
text: 'Button2',
|
||||
text: 'Submit',
|
||||
padding: 'none',
|
||||
},
|
||||
},
|
||||
{
|
||||
componentName: 'Text',
|
||||
layout: {
|
||||
top: 40,
|
||||
left: 10,
|
||||
height: 30,
|
||||
width: 17,
|
||||
},
|
||||
properties: ['text'],
|
||||
styles: [
|
||||
'textSize',
|
||||
'fontWeight',
|
||||
'fontStyle',
|
||||
'textColor',
|
||||
'isScrollRequired',
|
||||
'lineHeight',
|
||||
'textIndent',
|
||||
'textAlign',
|
||||
'verticalAlignment',
|
||||
'decoration',
|
||||
'transformation',
|
||||
'letterSpacing',
|
||||
'wordSpacing',
|
||||
'fontVariant',
|
||||
'backgroundColor',
|
||||
'borderColor',
|
||||
'borderRadius',
|
||||
'boxShadow',
|
||||
'padding',
|
||||
],
|
||||
defaultValue: {
|
||||
text: 'User Details',
|
||||
fontWeight: 'bold',
|
||||
textSize: 18,
|
||||
textColor: '#000',
|
||||
backgroundColor: '#fff00000',
|
||||
textAlign: 'left',
|
||||
decoration: 'none',
|
||||
transformation: 'none',
|
||||
fontStyle: 'normal',
|
||||
lineHeight: 1.5,
|
||||
textIndent: '0',
|
||||
letterSpacing: '0',
|
||||
wordSpacing: '0',
|
||||
fontVariant: 'normal',
|
||||
verticalAlignment: 'top',
|
||||
padding: 'default',
|
||||
boxShadow: '0px 0px 0px 0px #00000090',
|
||||
borderRadius: '0',
|
||||
isScrollRequired: 'enabled',
|
||||
},
|
||||
},
|
||||
{
|
||||
componentName: 'Text',
|
||||
layout: {
|
||||
top: 90,
|
||||
left: 10,
|
||||
height: 30,
|
||||
},
|
||||
properties: ['text'],
|
||||
styles: [
|
||||
'textSize',
|
||||
'fontWeight',
|
||||
'fontStyle',
|
||||
'textColor',
|
||||
'isScrollRequired',
|
||||
'lineHeight',
|
||||
'textIndent',
|
||||
'textAlign',
|
||||
'verticalAlignment',
|
||||
'decoration',
|
||||
'transformation',
|
||||
'letterSpacing',
|
||||
'wordSpacing',
|
||||
'fontVariant',
|
||||
'backgroundColor',
|
||||
'borderColor',
|
||||
'borderRadius',
|
||||
'boxShadow',
|
||||
'padding',
|
||||
],
|
||||
defaultValue: {
|
||||
text: 'Name',
|
||||
fontWeight: 'normal',
|
||||
textSize: 14,
|
||||
textColor: '#000',
|
||||
backgroundColor: '#fff00000',
|
||||
textAlign: 'left',
|
||||
decoration: 'none',
|
||||
transformation: 'none',
|
||||
fontStyle: 'normal',
|
||||
lineHeight: 1.5,
|
||||
textIndent: '0',
|
||||
letterSpacing: '0',
|
||||
wordSpacing: '0',
|
||||
fontVariant: 'normal',
|
||||
verticalAlignment: 'top',
|
||||
padding: 'default',
|
||||
boxShadow: '0px 0px 0px 0px #00000090',
|
||||
borderRadius: '0',
|
||||
isScrollRequired: 'enabled',
|
||||
},
|
||||
},
|
||||
{
|
||||
componentName: 'Text',
|
||||
layout: {
|
||||
top: 160,
|
||||
left: 10,
|
||||
height: 30,
|
||||
},
|
||||
properties: ['text'],
|
||||
styles: [
|
||||
'textSize',
|
||||
'fontWeight',
|
||||
'fontStyle',
|
||||
'textColor',
|
||||
'isScrollRequired',
|
||||
'lineHeight',
|
||||
'textIndent',
|
||||
'textAlign',
|
||||
'verticalAlignment',
|
||||
'decoration',
|
||||
'transformation',
|
||||
'letterSpacing',
|
||||
'wordSpacing',
|
||||
'fontVariant',
|
||||
'backgroundColor',
|
||||
'borderColor',
|
||||
'borderRadius',
|
||||
'boxShadow',
|
||||
'padding',
|
||||
],
|
||||
defaultValue: {
|
||||
text: 'Age',
|
||||
fontWeight: 'normal',
|
||||
textSize: 14,
|
||||
textColor: '#000',
|
||||
backgroundColor: '#fff00000',
|
||||
textAlign: 'left',
|
||||
decoration: 'none',
|
||||
transformation: 'none',
|
||||
fontStyle: 'normal',
|
||||
lineHeight: 1.5,
|
||||
textIndent: '0',
|
||||
letterSpacing: '0',
|
||||
wordSpacing: '0',
|
||||
fontVariant: 'normal',
|
||||
verticalAlignment: 'top',
|
||||
padding: 'default',
|
||||
boxShadow: '0px 0px 0px 0px #00000090',
|
||||
borderRadius: '0',
|
||||
isScrollRequired: 'enabled',
|
||||
},
|
||||
},
|
||||
{
|
||||
componentName: 'TextInput',
|
||||
layout: {
|
||||
top: 120,
|
||||
left: 10,
|
||||
height: 30,
|
||||
width: 25,
|
||||
top: 20,
|
||||
left: 5,
|
||||
height: 40,
|
||||
width: 31,
|
||||
},
|
||||
properties: ['placeholder', 'label'],
|
||||
styles: ['alignment', 'width', 'auto', 'padding', 'direction'],
|
||||
defaultValue: {
|
||||
placeholder: 'Enter your name',
|
||||
label: '',
|
||||
label: 'Name',
|
||||
width: '{{60}}',
|
||||
direction: 'left',
|
||||
alignment: 'side',
|
||||
auto: '{{false}}',
|
||||
padding: 'default',
|
||||
},
|
||||
},
|
||||
{
|
||||
componentName: 'NumberInput',
|
||||
layout: {
|
||||
top: 190,
|
||||
left: 10,
|
||||
height: 30,
|
||||
width: 25,
|
||||
top: 80,
|
||||
left: 5,
|
||||
height: 40,
|
||||
width: 31,
|
||||
},
|
||||
properties: ['value', 'label'],
|
||||
properties: ['placeholder', 'label'],
|
||||
styles: ['alignment', 'width', 'auto', 'padding', 'direction'],
|
||||
defaultValue: {
|
||||
value: 24,
|
||||
label: '',
|
||||
placeholder: 'Age',
|
||||
label: 'Age',
|
||||
width: '{{60}}',
|
||||
direction: 'left',
|
||||
alignment: 'side',
|
||||
auto: '{{false}}',
|
||||
padding: 'default',
|
||||
},
|
||||
},
|
||||
{
|
||||
componentName: 'Button',
|
||||
componentName: 'TextInput',
|
||||
layout: {
|
||||
top: 240,
|
||||
left: 10,
|
||||
height: 30,
|
||||
width: 10,
|
||||
top: 140,
|
||||
left: 5,
|
||||
height: 40,
|
||||
width: 31,
|
||||
},
|
||||
properties: ['text'],
|
||||
properties: ['placeholder', 'label'],
|
||||
styles: ['alignment', 'width', 'auto', 'padding', 'direction'],
|
||||
defaultValue: {
|
||||
text: 'Submit',
|
||||
placeholder: 'Tomy',
|
||||
label: 'Pet name',
|
||||
width: '{{60}}',
|
||||
alignment: 'side',
|
||||
direction: 'left',
|
||||
auto: '{{false}}',
|
||||
padding: 'default',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
@ -276,6 +141,24 @@ export const formConfig = {
|
|||
},
|
||||
showHeader: { type: 'toggle', displayName: 'Header' },
|
||||
showFooter: { type: 'toggle', displayName: 'Footer' },
|
||||
headerHeight: {
|
||||
type: 'numberInput',
|
||||
displayName: 'Header height',
|
||||
isHidden: true,
|
||||
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
|
||||
},
|
||||
canvasHeight: {
|
||||
type: 'numberInput',
|
||||
displayName: 'Canvas height',
|
||||
isHidden: true,
|
||||
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
|
||||
},
|
||||
footerHeight: {
|
||||
type: 'numberInput',
|
||||
displayName: 'Footer height',
|
||||
isHidden: true,
|
||||
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
|
||||
},
|
||||
visibility: {
|
||||
type: 'toggle',
|
||||
displayName: 'Visibility',
|
||||
|
|
@ -294,6 +177,13 @@ export const formConfig = {
|
|||
defaultValue: false,
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
type: 'code',
|
||||
displayName: 'Tooltip',
|
||||
validation: { schema: { type: 'string' } },
|
||||
section: 'additionalActions',
|
||||
placeholder: 'Enter tooltip text',
|
||||
},
|
||||
},
|
||||
events: {
|
||||
onSubmit: { displayName: 'On submit' },
|
||||
|
|
@ -316,22 +206,6 @@ export const formConfig = {
|
|||
defaultValue: '#ffffffff',
|
||||
},
|
||||
},
|
||||
headerHeight: {
|
||||
type: 'code',
|
||||
displayName: 'Header height',
|
||||
validation: {
|
||||
schema: { type: 'string' },
|
||||
defaultValue: '80px',
|
||||
},
|
||||
},
|
||||
footerHeight: {
|
||||
type: 'code',
|
||||
displayName: 'Footer height',
|
||||
validation: {
|
||||
schema: { type: 'string' },
|
||||
defaultValue: '80px',
|
||||
},
|
||||
},
|
||||
backgroundColor: {
|
||||
type: 'colorSwatches',
|
||||
displayName: 'Background color',
|
||||
|
|
@ -403,18 +277,18 @@ export const formConfig = {
|
|||
value:
|
||||
"{{ {title: 'User registration form', properties: {firstname: {type: 'textinput',value: 'Maria',label:'First name', validation:{maxLength:6}, styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},lastname:{type: 'textinput',value: 'Doe', label:'Last name', styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},age:{type:'number', label:'Age'},}, submitButton: {value: 'Submit', styles: {backgroundColor: '#3a433b',borderColor:'#595959'}}} }}",
|
||||
},
|
||||
showHeader: { value: '{{false}}' },
|
||||
showFooter: { value: '{{false}}' },
|
||||
showHeader: { value: '{{true}}' },
|
||||
showFooter: { value: '{{true}}' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
headerHeight: { value: 60 },
|
||||
footerHeight: { value: 60 },
|
||||
},
|
||||
events: [],
|
||||
styles: {
|
||||
backgroundColor: { value: '#fff' },
|
||||
borderRadius: { value: '0' },
|
||||
borderColor: { value: '#fff' },
|
||||
headerHeight: { value: '60px' },
|
||||
footerHeight: { value: '60px' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -78,6 +78,29 @@ export const iconConfig = {
|
|||
},
|
||||
accordian: 'Icon',
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
accordian: 'Icon',
|
||||
},
|
||||
boxShadow: {
|
||||
type: 'boxShadow',
|
||||
displayName: 'Box shadow',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: '0px 0px 0px 0px #00000040',
|
||||
},
|
||||
accordian: 'Icon',
|
||||
},
|
||||
},
|
||||
exposedVariables: {},
|
||||
actions: [
|
||||
|
|
@ -116,6 +139,8 @@ export const iconConfig = {
|
|||
styles: {
|
||||
iconColor: { value: '#000' },
|
||||
iconAlign: { value: 'center' },
|
||||
padding: { value: 'default' },
|
||||
boxShadow: { value: '0px 0px 0px 0px #00000040' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -121,6 +121,12 @@ export const multiselectV2Config = {
|
|||
},
|
||||
accordian: 'Options',
|
||||
},
|
||||
showAllSelectedLabel: {
|
||||
type: 'toggle',
|
||||
displayName: 'Show "All items are selected"',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
accordian: 'Options',
|
||||
},
|
||||
optionsLoadingState: {
|
||||
type: 'toggle',
|
||||
displayName: 'Options loading state',
|
||||
|
|
@ -142,6 +148,18 @@ export const multiselectV2Config = {
|
|||
accordian: 'Options',
|
||||
isFxNotRequired: true,
|
||||
},
|
||||
showClearBtn: {
|
||||
type: 'toggle',
|
||||
displayName: 'Show clear selection button',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
section: 'additionalActions',
|
||||
},
|
||||
showSearchInput: {
|
||||
type: 'toggle',
|
||||
displayName: 'Show search in options',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
section: 'additionalActions',
|
||||
},
|
||||
loadingState: {
|
||||
type: 'toggle',
|
||||
displayName: 'Loading state',
|
||||
|
|
@ -327,6 +345,9 @@ export const multiselectV2Config = {
|
|||
optionsLoadingState: { value: '{{false}}' },
|
||||
sort: { value: 'asc' },
|
||||
placeholder: { value: 'Select the options' },
|
||||
showAllSelectedLabel: { value: '{{true}}' },
|
||||
showClearBtn: { value: '{{true}}' },
|
||||
showSearchInput: { value: '{{true}}' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
loadingState: { value: '{{false}}' },
|
||||
|
|
|
|||
|
|
@ -84,6 +84,19 @@ export const rangeSliderConfig = {
|
|||
defaultValue: true,
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
value: null,
|
||||
|
|
@ -111,6 +124,7 @@ export const rangeSliderConfig = {
|
|||
handleColor: { value: '' },
|
||||
trackColor: { value: 'var(--primary-brand)' },
|
||||
visibility: { value: '{{true}}' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -89,6 +89,19 @@ export const starratingConfig = {
|
|||
defaultValue: false,
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
value: 0,
|
||||
|
|
@ -112,6 +125,7 @@ export const starratingConfig = {
|
|||
labelColor: { value: '' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -38,6 +38,19 @@ export const tagsConfig = {
|
|||
defaultValue: true,
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
alignment: {
|
||||
type: 'alignButtons',
|
||||
displayName: 'Alignment',
|
||||
|
|
@ -62,6 +75,7 @@ export const tagsConfig = {
|
|||
events: [],
|
||||
styles: {
|
||||
visibility: { value: '{{true}}' },
|
||||
padding: { value: 'default' },
|
||||
alignment: { value: 'left' },
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -126,6 +126,20 @@ export const toggleSwitchV2Config = {
|
|||
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } },
|
||||
accordian: 'switch',
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
accordian: 'switch',
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
value: false,
|
||||
|
|
@ -187,6 +201,7 @@ export const toggleSwitchV2Config = {
|
|||
handleColor: { value: '#FFFFFF' },
|
||||
alignment: { value: 'right' },
|
||||
boxShadow: { value: '0px 0px 0px 0px #00000090' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -33,7 +33,8 @@ export const Container = ({
|
|||
shallow
|
||||
);
|
||||
|
||||
const { borderRadius, borderColor, boxShadow, headerHeight = 80 } = styles;
|
||||
const { borderRadius, borderColor, boxShadow } = styles;
|
||||
const { headerHeight = 80 } = properties;
|
||||
const contentBgColor = useMemo(() => {
|
||||
return {
|
||||
backgroundColor:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { Container as SubContainer } from '@/AppBuilder/AppCanvas/Container';
|
||||
import { showGridLinesOnSlot, hideGridLinesOnSlot } from '@/AppBuilder/AppCanvas/Grid/gridUtils';
|
||||
import { useResizable } from '@/AppBuilder/_hooks/useMoveable';
|
||||
|
||||
export const HorizontalSlot = React.memo(
|
||||
({
|
||||
id,
|
||||
height = 0,
|
||||
width,
|
||||
darkMode,
|
||||
isDisabled,
|
||||
isActive,
|
||||
slotName = 'header', // 'header' or 'footer'
|
||||
slotStyle = {},
|
||||
onResize,
|
||||
isEditing,
|
||||
maxHeight,
|
||||
}) => {
|
||||
const parsedHeight = parseInt(height, 10);
|
||||
|
||||
const { getRootProps, getHandleProps, getResizeState } = useResizable({
|
||||
initialHeight: parsedHeight,
|
||||
initialWidth: '100%', // Now respects parent's width
|
||||
minHeight: 10,
|
||||
maxHeight: maxHeight || 400,
|
||||
maxWidth: '100%',
|
||||
stepHeight: 10, // Height will change in steps of 10px
|
||||
onResize: () => {},
|
||||
onDragEnd: (values) => {
|
||||
onResize(values);
|
||||
},
|
||||
isReverseVerticalDrag: slotName === 'footer', // Reverse dragging for Footer
|
||||
});
|
||||
|
||||
const { height: resizedHeight, isDragging } = getResizeState();
|
||||
|
||||
useEffect(() => {
|
||||
if (isDragging) {
|
||||
showGridLinesOnSlot(id);
|
||||
} else {
|
||||
hideGridLinesOnSlot(id);
|
||||
}
|
||||
}, [isDragging, id]);
|
||||
|
||||
const canvasHeight = parseInt(resizedHeight, 10) / 10;
|
||||
|
||||
const resizeStyle = {
|
||||
backgroundColor: darkMode ? '#1F2837' : '#fff',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`jet-form-${slotName} wj-form-${slotName}`} style={slotStyle}>
|
||||
<div
|
||||
className={`resizable-slot only-${slotName} ${isActive ? 'active' : ''} ${isEditing && 'is-editing'} ${
|
||||
isDragging ? 'dragging' : ''
|
||||
}`}
|
||||
{...getRootProps()}
|
||||
>
|
||||
<SubContainer
|
||||
id={id}
|
||||
canvasHeight={canvasHeight}
|
||||
canvasWidth={width}
|
||||
allowContainerSelect={false}
|
||||
darkMode={darkMode}
|
||||
styles={{
|
||||
margin: 0,
|
||||
backgroundColor: 'transparent',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
componentType="Form"
|
||||
/>
|
||||
{isEditing && <div className="resize-handle" {...getHandleProps()} style={resizeStyle} />}
|
||||
</div>
|
||||
|
||||
{isDisabled && (
|
||||
<div
|
||||
id={`${id}-disabled`}
|
||||
className="tj-form-disabled-overlay"
|
||||
style={{ height: resizedHeight || '100%' }}
|
||||
onClick={() => {}}
|
||||
onDrop={(e) => e.stopPropagation()}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
@ -2,7 +2,7 @@ import React, { useRef, useState, useEffect } from 'react';
|
|||
import { Container as SubContainer } from '@/AppBuilder/AppCanvas/Container';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import _, { debounce, omit } from 'lodash';
|
||||
import { generateUIComponents } from './FormUtils';
|
||||
import { generateUIComponents, getBodyHeight } from './FormUtils';
|
||||
import { useMounted } from '@/_hooks/use-mount';
|
||||
import { onComponentClick, removeFunctionObjects } from '@/_helpers/appUtils';
|
||||
import { deepClone } from '@/_helpers/utilities/utils.helpers';
|
||||
|
|
@ -14,12 +14,10 @@ import {
|
|||
CONTAINER_FORM_CANVAS_PADDING,
|
||||
SUBCONTAINER_CANVAS_BORDER_WIDTH,
|
||||
} from '@/AppBuilder/AppCanvas/appCanvasConstants';
|
||||
import './form.scss';
|
||||
import { HorizontalSlot } from './Components/HorizontalSlot';
|
||||
import { useActiveSlot } from '@/AppBuilder/_hooks/useActiveSlot';
|
||||
|
||||
const getCanvasHeight = (height) => {
|
||||
const parsedHeight = height.includes('px') ? parseInt(height, 10) : height;
|
||||
return Math.ceil(parsedHeight);
|
||||
};
|
||||
import './form.scss';
|
||||
|
||||
export const Form = function Form(props) {
|
||||
const {
|
||||
|
|
@ -35,26 +33,19 @@ export const Form = function Form(props) {
|
|||
properties,
|
||||
resetComponent = () => {},
|
||||
dataCy,
|
||||
onComponentClick,
|
||||
} = props;
|
||||
const childComponents = useStore((state) => state.getChildComponents(id), shallow);
|
||||
const {
|
||||
borderRadius,
|
||||
borderColor,
|
||||
boxShadow,
|
||||
headerHeight,
|
||||
footerHeight,
|
||||
footerBackgroundColor,
|
||||
headerBackgroundColor,
|
||||
} = styles;
|
||||
const { borderRadius, borderColor, boxShadow, footerBackgroundColor, headerBackgroundColor } = styles;
|
||||
const {
|
||||
buttonToSubmit,
|
||||
loadingState,
|
||||
advanced,
|
||||
JSONSchema,
|
||||
showHeader = false,
|
||||
showFooter = false,
|
||||
visibility,
|
||||
disabledState,
|
||||
headerHeight = 80,
|
||||
footerHeight = 80,
|
||||
canvasHeight,
|
||||
} = properties;
|
||||
const { isDisabled, isVisible, isLoading } = useExposeState(
|
||||
properties.loadingState,
|
||||
|
|
@ -65,6 +56,10 @@ export const Form = function Form(props) {
|
|||
);
|
||||
const backgroundColor =
|
||||
['#fff', '#ffffffff'].includes(styles.backgroundColor) && darkMode ? '#232E3C' : styles.backgroundColor;
|
||||
|
||||
const computedFormBodyHeight = getBodyHeight(height, showHeader, showFooter, headerHeight, footerHeight);
|
||||
const computedBorderRadius = `${borderRadius ? parseFloat(borderRadius) : 0}px`;
|
||||
|
||||
const computedStyles = {
|
||||
backgroundColor,
|
||||
borderRadius: borderRadius ? parseFloat(borderRadius) : 0,
|
||||
|
|
@ -74,16 +69,7 @@ export const Form = function Form(props) {
|
|||
position: 'relative',
|
||||
boxShadow,
|
||||
flexDirection: 'column',
|
||||
};
|
||||
|
||||
const formHeader = {
|
||||
flexShrink: 0,
|
||||
paddingBottom: '3px',
|
||||
paddingTop: '7px',
|
||||
paddingLeft: `${CONTAINER_FORM_CANVAS_PADDING}px`,
|
||||
paddingRight: `${CONTAINER_FORM_CANVAS_PADDING}px`,
|
||||
backgroundColor:
|
||||
['#fff', '#ffffffff'].includes(headerBackgroundColor) && darkMode ? '#1F2837' : headerBackgroundColor,
|
||||
clipPath: `inset(0 round ${computedBorderRadius})`,
|
||||
};
|
||||
|
||||
const formContent = {
|
||||
|
|
@ -96,13 +82,6 @@ export const Form = function Form(props) {
|
|||
paddingRight: `${CONTAINER_FORM_CANVAS_PADDING}px`,
|
||||
};
|
||||
|
||||
const formFooter = {
|
||||
flexShrink: 0,
|
||||
padding: `${CONTAINER_FORM_CANVAS_PADDING}px`,
|
||||
backgroundColor:
|
||||
['#fff', '#ffffffff'].includes(footerBackgroundColor) && darkMode ? '#1F2837' : footerBackgroundColor,
|
||||
};
|
||||
|
||||
const parentRef = useRef(null);
|
||||
const childDataRef = useRef({});
|
||||
|
||||
|
|
@ -110,8 +89,6 @@ export const Form = function Form(props) {
|
|||
const [isValid, setValidation] = useState(true);
|
||||
const [uiComponents, setUIComponents] = useState([]);
|
||||
const mounted = useMounted();
|
||||
const canvasHeaderHeight = getCanvasHeight(headerHeight) / 10;
|
||||
const canvasFooterHeight = getCanvasHeight(footerHeight) / 10;
|
||||
|
||||
useEffect(() => {
|
||||
const exposedVariables = {
|
||||
|
|
@ -287,9 +264,61 @@ export const Form = function Form(props) {
|
|||
setChildrenData(childDataRef.current);
|
||||
};
|
||||
|
||||
const mode = useStore((state) => state.currentMode, shallow);
|
||||
const isEditing = mode === 'edit';
|
||||
|
||||
const activeSlot = useActiveSlot(isEditing ? id : null); // Track the active slot for this widget
|
||||
const setComponentProperty = useStore((state) => state.setComponentProperty, shallow);
|
||||
// const updateContainerAutoHeight = useStore((state) => state.updateContainerAutoHeight);
|
||||
|
||||
const updateHeaderSizeInStore = ({ newHeight }) => {
|
||||
const _height = parseInt(newHeight, 10);
|
||||
setComponentProperty(id, `headerHeight`, _height, 'properties', 'value', false);
|
||||
};
|
||||
|
||||
const updateFooterSizeInStore = ({ newHeight }) => {
|
||||
const _height = parseInt(newHeight, 10);
|
||||
setComponentProperty(id, `footerHeight`, _height, 'properties', 'value', false);
|
||||
};
|
||||
|
||||
const [canHeight, setCanHeight] = useState('100%');
|
||||
useEffect(() => {
|
||||
// const newHeight = parseInt(height, 10) - 14;
|
||||
|
||||
// const autoCanvasHeight = document.querySelector(`#canvas-${id}`)?.scrollHeight;
|
||||
const wrapHeight = parseInt(computedFormBodyHeight, 10);
|
||||
// Set height to the larger value between computed body height and canvas scroll height
|
||||
const maxHeight = Math.max(wrapHeight, canvasHeight || 10);
|
||||
|
||||
const roundedHeight = Math.round(maxHeight / 10) * 10;
|
||||
setCanHeight(`${roundedHeight}px`);
|
||||
}, [computedFormBodyHeight, canvasHeight]);
|
||||
const headerMaxHeight = parseInt(height, 10) - parseInt(footerHeight, 10) - 100 - 10;
|
||||
const footerMaxHeight = parseInt(height, 10) - parseInt(headerHeight, 10) - 100 - 10;
|
||||
const formFooter = {
|
||||
flexShrink: 0,
|
||||
paddingTop: '3px',
|
||||
paddingBottom: '7px',
|
||||
paddingLeft: `${CONTAINER_FORM_CANVAS_PADDING}px`,
|
||||
paddingRight: `${CONTAINER_FORM_CANVAS_PADDING}px`,
|
||||
maxHeight: `${footerMaxHeight}px`,
|
||||
backgroundColor:
|
||||
['#fff', '#ffffffff'].includes(footerBackgroundColor) && darkMode ? '#1F2837' : footerBackgroundColor,
|
||||
};
|
||||
const formHeader = {
|
||||
flexShrink: 0,
|
||||
paddingBottom: '3px',
|
||||
paddingTop: '7px',
|
||||
paddingLeft: `${CONTAINER_FORM_CANVAS_PADDING}px`,
|
||||
paddingRight: `${CONTAINER_FORM_CANVAS_PADDING}px`,
|
||||
maxHeight: `${headerMaxHeight}px`,
|
||||
backgroundColor:
|
||||
['#fff', '#ffffffff'].includes(headerBackgroundColor) && darkMode ? '#1F2837' : headerBackgroundColor,
|
||||
};
|
||||
|
||||
return (
|
||||
<form
|
||||
className={`jet-container ${advanced && 'jet-container-json-form'}`}
|
||||
className={`jet-container jet-form-widget ${advanced && 'jet-container-json-form'}`}
|
||||
id={id}
|
||||
data-cy={dataCy}
|
||||
ref={parentRef}
|
||||
|
|
@ -299,32 +328,22 @@ export const Form = function Form(props) {
|
|||
if (e.target.className === 'real-canvas') onComponentClick(id, component);
|
||||
}} //Hack, should find a better solution - to prevent losing z index+1 when container element is clicked
|
||||
>
|
||||
{showHeader && (
|
||||
<div style={formHeader} className="wj-form-header">
|
||||
<SubContainer
|
||||
id={`${id}-header`}
|
||||
canvasHeight={canvasHeaderHeight}
|
||||
canvasWidth={width}
|
||||
allowContainerSelect={false}
|
||||
darkMode={darkMode}
|
||||
styles={{
|
||||
backgroundColor: 'transparent',
|
||||
height: headerHeight,
|
||||
}}
|
||||
componentType="Form"
|
||||
/>
|
||||
{isDisabled && (
|
||||
<div
|
||||
id={`${id}-header-disabled`}
|
||||
className="tj-form-disabled-overlay"
|
||||
style={{ height: headerHeight || '100%' }}
|
||||
onClick={() => {}}
|
||||
onDrop={(e) => e.stopPropagation()}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{!advanced && showHeader && (
|
||||
<HorizontalSlot
|
||||
slotName="header"
|
||||
slotStyle={formHeader}
|
||||
isEditing={isEditing}
|
||||
id={`${id}-header`}
|
||||
height={headerHeight}
|
||||
width={width}
|
||||
darkMode={darkMode}
|
||||
isDisabled={isDisabled}
|
||||
isActive={activeSlot === `${id}-header`}
|
||||
onResize={updateHeaderSizeInStore}
|
||||
/>
|
||||
)}
|
||||
<div className="jet-form-body" style={formContent}>
|
||||
|
||||
<div className="jet-form-body sub-container-overflow-wrap" style={formContent}>
|
||||
{isLoading ? (
|
||||
<div className="p-2 tw-flex tw-items-center tw-justify-center" style={{ margin: '0px auto' }}>
|
||||
<div className="spinner-border" role="status"></div>
|
||||
|
|
@ -332,14 +351,17 @@ export const Form = function Form(props) {
|
|||
) : (
|
||||
<fieldset disabled={isDisabled} style={{ width: '100%' }}>
|
||||
{!advanced && (
|
||||
<div className={'json-form-wrapper-disabled'} style={{ width: '100%', height: '100%' }}>
|
||||
<div className={'json-form-wrapper-disabled'} style={{ width: '100%', height: canHeight || '100%' }}>
|
||||
<SubContainer
|
||||
id={id}
|
||||
canvasHeight={computedStyles.height}
|
||||
canvasHeight={parseInt(computedFormBodyHeight, 10)}
|
||||
canvasWidth={width}
|
||||
onOptionChange={onOptionChange}
|
||||
onOptionsChange={onOptionsChange}
|
||||
styles={{ backgroundColor: computedStyles.backgroundColor }}
|
||||
styles={{
|
||||
backgroundColor: computedStyles.backgroundColor,
|
||||
height: canHeight,
|
||||
}}
|
||||
darkMode={darkMode}
|
||||
componentType="Form"
|
||||
/>
|
||||
|
|
@ -381,31 +403,19 @@ export const Form = function Form(props) {
|
|||
</fieldset>
|
||||
)}
|
||||
</div>
|
||||
{showFooter && (
|
||||
<div className="jet-form-footer wj-form-footer" style={formFooter}>
|
||||
<SubContainer
|
||||
id={`${id}-footer`}
|
||||
canvasHeight={canvasFooterHeight}
|
||||
canvasWidth={width}
|
||||
allowContainerSelect={false}
|
||||
darkMode={darkMode}
|
||||
styles={{
|
||||
margin: 0,
|
||||
backgroundColor: 'transparent',
|
||||
height: footerHeight,
|
||||
}}
|
||||
componentType="Form"
|
||||
/>
|
||||
{isDisabled && (
|
||||
<div
|
||||
id={`${id}-footer-disabled`}
|
||||
className="tj-form-disabled-overlay"
|
||||
style={{ height: footerHeight || '100%' }}
|
||||
onClick={() => {}}
|
||||
onDrop={(e) => e.stopPropagation()}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{!advanced && showFooter && (
|
||||
<HorizontalSlot
|
||||
slotName="footer"
|
||||
slotStyle={formFooter}
|
||||
isEditing={isEditing}
|
||||
id={`${id}-footer`}
|
||||
height={footerHeight}
|
||||
width={width}
|
||||
darkMode={darkMode}
|
||||
isDisabled={isDisabled}
|
||||
onResize={updateFooterSizeInStore}
|
||||
isActive={activeSlot === `${id}-footer`}
|
||||
/>
|
||||
)}
|
||||
</form>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -533,3 +533,22 @@ const validBooleanChecker = (input) => {
|
|||
if (/^(true|false)$/i.test(input) == true) return JSON.parse(input);
|
||||
return true;
|
||||
};
|
||||
|
||||
export const getBodyHeight = (height, showHeader, showFooter, headerHeight = 60, footerHeight = 60) => {
|
||||
let modalHeight = height ? parseInt(height, 10) : 0;
|
||||
let parsedHeaderHeight = showHeader ? parseInt(headerHeight, 10) : 0;
|
||||
let parsedFooterHeight = showFooter ? parseInt(footerHeight, 10) : 0;
|
||||
|
||||
if (showHeader) {
|
||||
// 10 is header padding
|
||||
modalHeight = modalHeight - parsedHeaderHeight - 10;
|
||||
}
|
||||
if (showFooter) {
|
||||
// 14 is footer padding
|
||||
modalHeight = modalHeight - parsedFooterHeight - 14;
|
||||
}
|
||||
|
||||
const rounded = Math.ceil(modalHeight / 10) * 10;
|
||||
|
||||
return `${Math.max(rounded - 20, 40)}px`;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,17 @@
|
|||
.jet-form-widget {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.wj-form-header {
|
||||
position: relative;
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: -7px;
|
||||
right: -7px;
|
||||
left: -2px;
|
||||
right: -2px;
|
||||
height: 1px;
|
||||
background-color: var(--border-weak);
|
||||
}
|
||||
|
|
@ -17,8 +23,8 @@
|
|||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -7px;
|
||||
right: -7px;
|
||||
left: -2px;
|
||||
right: -2px;
|
||||
height: 1px;
|
||||
background-color: var(--border-weak);
|
||||
}
|
||||
|
|
@ -38,3 +44,77 @@
|
|||
box-sizing: content-box;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.resizable-slot {
|
||||
position: relative;
|
||||
height: auto;
|
||||
box-shadow: 0 0 0 1px transparent; /* Acts as a border */
|
||||
transition: box-shadow 0.15s ease-in-out;
|
||||
max-height: 100%;
|
||||
|
||||
&.is-editing:hover {
|
||||
box-shadow: 0 0 0 1px var(--border-weak);
|
||||
}
|
||||
|
||||
&.is-editing.active {
|
||||
box-shadow: 0 0 0 1px var(--border-accent-weak);
|
||||
}
|
||||
|
||||
&.is-editing.dragging {
|
||||
box-shadow: 0 0 0 1px var(--border-accent-strong);
|
||||
}
|
||||
|
||||
.resize-handle {
|
||||
position: absolute;
|
||||
bottom: -4px;
|
||||
left: 50%; /* Center horizontally */
|
||||
transform: translateX(-50%); /* Ensure proper centering */
|
||||
width: 24px;
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
background-color: initial;
|
||||
border: 1px solid var(--background-accent-strong);
|
||||
cursor: ns-resize;
|
||||
z-index: 1;
|
||||
visibility: hidden;
|
||||
transition: visibility 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
&.active .resize-handle {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.only-bottom {
|
||||
}
|
||||
|
||||
.jet-form-header {
|
||||
min-height: 10px;
|
||||
}
|
||||
|
||||
.jet-form-body {
|
||||
min-height: 100px;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.jet-form-footer {
|
||||
min-height: 10px;
|
||||
}
|
||||
|
||||
.jet-form-footer .resize-handle {
|
||||
top: -4px;
|
||||
bottom: unset;
|
||||
}
|
||||
|
||||
.jet-container.jet-container-json-form {
|
||||
padding: 0px;
|
||||
|
||||
.wj-form-header::after,
|
||||
.wj-form-footer::after {
|
||||
left: -3px;
|
||||
right: -3px;
|
||||
}
|
||||
.jet-form-body fieldset {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -277,7 +277,7 @@ export const Modal = function Modal({
|
|||
|
||||
<Modal.Component
|
||||
show={showModal}
|
||||
contentClassName="modal-component"
|
||||
contentClassName="modal-component tj-modal--container"
|
||||
container={document.getElementsByClassName('real-canvas')[0]}
|
||||
size={size}
|
||||
keyboard={true}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ export const ModalWidget = ({ ...restProps }) => {
|
|||
return (
|
||||
<BootstrapModal
|
||||
{...restProps}
|
||||
contentClassName="modal-component tj-modal-widget-content"
|
||||
contentClassName="modal-component tj-modal--container tj-modal-widget-content"
|
||||
animation={true}
|
||||
onEscapeKeyDown={(e) => {
|
||||
e.preventDefault();
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ export const Header = memo(
|
|||
</div>
|
||||
</div>
|
||||
{showFilter && (
|
||||
<Filter table={table} darkMode={darkMode} setFilters={setFilters} setShowFilter={setShowFilter} />
|
||||
<Filter id={id} table={table} darkMode={darkMode} setFilters={setFilters} setShowFilter={setShowFilter} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { FilterFooter } from './FilterFooter';
|
|||
import { FilterHeader } from './FilterHeader';
|
||||
import { debounce, isEqual } from 'lodash';
|
||||
|
||||
export const Filter = memo(({ table, darkMode, setFilters, setShowFilter }) => {
|
||||
export const Filter = memo(({ id, table, darkMode, setFilters, setShowFilter }) => {
|
||||
const { t } = useTranslation();
|
||||
const [localFilters, setLocalFilters] = useState(table.getState().columnFilters);
|
||||
|
||||
|
|
@ -142,6 +142,7 @@ export const Filter = memo(({ table, darkMode, setFilters, setShowFilter }) => {
|
|||
<div className="card-body">
|
||||
{localFilters.map((filter, index) => (
|
||||
<FilterRow
|
||||
id={id}
|
||||
key={index}
|
||||
filter={filter}
|
||||
index={index}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
import React, { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Select from '@/_ui/Select';
|
||||
import { components } from 'react-select';
|
||||
import defaultStyles from '@/_ui/Select/styles';
|
||||
import { FILTER_OPTIONS } from './filterConstants';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
|
||||
export const FilterRow = memo(
|
||||
({ filter, index, columns, darkMode, onColumnChange, onOperationChange, onValueChange, onRemove }) => {
|
||||
({ id, filter, index, columns, darkMode, onColumnChange, onOperationChange, onValueChange, onRemove }) => {
|
||||
const { t } = useTranslation();
|
||||
const isDragging = useStore((state) => state.draggingComponentId === id);
|
||||
|
||||
const selectStyles = (width) => {
|
||||
return {
|
||||
|
|
@ -15,6 +18,10 @@ export const FilterRow = memo(
|
|||
menuList: (base) => ({
|
||||
...base,
|
||||
}),
|
||||
menu: (base) => ({
|
||||
...base,
|
||||
display: isDragging ? 'none' : 'block',
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -29,11 +36,13 @@ export const FilterRow = memo(
|
|||
value={filter.id}
|
||||
search={true}
|
||||
onChange={(value) => onColumnChange(index, value)}
|
||||
components={{ ValueContainer: CustomValueContainer }}
|
||||
placeholder={t('globals.select', 'Select') + '...'}
|
||||
className={`${darkMode ? 'select-search-dark' : 'select-search'} mb-0`}
|
||||
styles={selectStyles('100%')}
|
||||
useCustomStyles={true}
|
||||
darkMode={darkMode}
|
||||
openMenuOnFocus={true}
|
||||
/>
|
||||
</div>
|
||||
<div data-cy={`select-operation-dropdown-${index}`} className="col" style={{ maxWidth: '180px' }}>
|
||||
|
|
@ -42,11 +51,13 @@ export const FilterRow = memo(
|
|||
value={filter.value.condition}
|
||||
search={true}
|
||||
onChange={(value) => onOperationChange(index, value)}
|
||||
components={{ ValueContainer: CustomValueContainer }}
|
||||
className={`${darkMode ? 'select-search-dark' : 'select-search'}`}
|
||||
placeholder={t('globals.select', 'Select') + '...'}
|
||||
styles={selectStyles('100%')}
|
||||
useCustomStyles={true}
|
||||
darkMode={darkMode}
|
||||
openMenuOnFocus={true}
|
||||
/>
|
||||
</div>
|
||||
<div className="col">
|
||||
|
|
@ -74,3 +85,15 @@ export const FilterRow = memo(
|
|||
);
|
||||
}
|
||||
);
|
||||
|
||||
const CustomValueContainer = (props) => {
|
||||
const handleClick = (e) => {
|
||||
if (props.selectProps?.selectRef?.current) {
|
||||
props.selectProps.selectRef.current.focus();
|
||||
}
|
||||
if (props.innerProps?.onMouseDown) {
|
||||
props.innerProps.onMouseDown(e);
|
||||
}
|
||||
};
|
||||
return <components.ValueContainer {...props} innerProps={{ ...props.innerProps, onMouseDown: handleClick }} />;
|
||||
};
|
||||
|
|
|
|||
84
frontend/src/AppBuilder/_hooks/useActiveSlot.js
Normal file
84
frontend/src/AppBuilder/_hooks/useActiveSlot.js
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
const useIsWidgetSelected = (id) => {
|
||||
// Get selected components from store using shallow comparison
|
||||
const selectedComponents = useStore((state) => state.selectedComponents, shallow);
|
||||
|
||||
// Check if the only selected component is the provided `id`
|
||||
return selectedComponents.length === 1 && selectedComponents[0] === id;
|
||||
};
|
||||
|
||||
export const useActiveSlot = (widgetId) => {
|
||||
const [activeSlot, setActiveSlot] = useState(''); // Default to widget ID
|
||||
const isSelected = useIsWidgetSelected(widgetId); // Check if widget is selected
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSelected) {
|
||||
setActiveSlot('');
|
||||
}
|
||||
}, [isSelected]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleDoubleClick = (event) => {
|
||||
let target = event.target;
|
||||
|
||||
if (!widgetId) {
|
||||
setActiveSlot(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Traverse up to find a slot with an id
|
||||
while (target && target !== document.body) {
|
||||
if (target.id && target.id.startsWith('canvas-')) {
|
||||
const slotId = target.id.replace(/^canvas-/, ''); // ✅ Strip "canvas-"
|
||||
setActiveSlot(slotId);
|
||||
return;
|
||||
}
|
||||
target = target.parentElement;
|
||||
}
|
||||
|
||||
// If no slot is found, reset to widget ID
|
||||
setActiveSlot(widgetId);
|
||||
};
|
||||
const handleSingleClick = (event) => {
|
||||
let target = event.target;
|
||||
|
||||
if (!widgetId) {
|
||||
setActiveSlot(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Traverse up to find a valid main slot (not header/footer)
|
||||
while (target && target !== document.body) {
|
||||
if (
|
||||
target.id &&
|
||||
target.id.startsWith('canvas-') &&
|
||||
!target.id.endsWith('-header') &&
|
||||
!target.id.endsWith('-footer')
|
||||
) {
|
||||
const slotId = target.id.replace(/^canvas-/, ''); // Strip "canvas-"
|
||||
setActiveSlot(slotId);
|
||||
return;
|
||||
}
|
||||
target = target.parentElement;
|
||||
}
|
||||
|
||||
// If no main slot is found, fallback to widget ID
|
||||
setActiveSlot(widgetId);
|
||||
};
|
||||
|
||||
// Attach single click if the widget is selected, otherwise listen for double-click
|
||||
|
||||
document.addEventListener('dblclick', handleDoubleClick);
|
||||
document.addEventListener('click', handleSingleClick);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('dblclick', handleDoubleClick);
|
||||
document.removeEventListener('click', handleSingleClick);
|
||||
};
|
||||
}, [widgetId]); // Re-run when widgetId or selection state changes
|
||||
|
||||
return activeSlot;
|
||||
};
|
||||
135
frontend/src/AppBuilder/_hooks/useMoveable.js
Normal file
135
frontend/src/AppBuilder/_hooks/useMoveable.js
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
import { useRef, useState } from 'react';
|
||||
|
||||
const defaultProps = {
|
||||
minHeight: 50,
|
||||
maxHeight: 600,
|
||||
minWidth: 50,
|
||||
maxWidth: 600,
|
||||
lockHorizontal: false,
|
||||
lockVertical: false,
|
||||
stepHeight: 10, // Default step size for height
|
||||
stepWidth: 10, // Default step size for width
|
||||
onResize: null,
|
||||
onDragStart: null,
|
||||
onDragEnd: null,
|
||||
isReverseVerticalDrag: false,
|
||||
};
|
||||
|
||||
export const useResizable = (options = {}) => {
|
||||
const props = { ...defaultProps, ...options };
|
||||
const parentRef = useRef(null);
|
||||
const [isDragging, setIsDragging] = useState(false); // ✅ Track dragging state
|
||||
|
||||
const [height, setHeight] = useState(
|
||||
typeof props.initialHeight === 'string' ? props.initialHeight : `${props.initialHeight || 200}px`
|
||||
);
|
||||
const [width, setWidth] = useState(
|
||||
typeof props.initialWidth === 'string' ? props.initialWidth : `${props.initialWidth || 200}px`
|
||||
);
|
||||
|
||||
const getRootProps = () => ({
|
||||
ref: parentRef,
|
||||
style: { height, width },
|
||||
});
|
||||
|
||||
const getResizeState = () => ({
|
||||
height,
|
||||
width,
|
||||
isDragging,
|
||||
});
|
||||
|
||||
const getHandleProps = () => {
|
||||
const handleMouseDown = (e) => {
|
||||
// Prevent right-click drag activation (button === 2)
|
||||
if (e.button === 2) return;
|
||||
|
||||
if (!parentRef.current) return;
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
const startHeight = parseInt(parentRef.current.clientHeight);
|
||||
const startWidth = parseInt(parentRef.current.clientWidth);
|
||||
const parentWidth = parentRef.current.parentElement ? parentRef.current.parentElement.clientWidth : startWidth;
|
||||
const startY = e.clientY;
|
||||
const startX = e.clientX;
|
||||
const isPercentage = typeof props.initialWidth === 'string' && props.initialWidth.includes('%');
|
||||
|
||||
setIsDragging(true); // ✅ Set dragging state to true
|
||||
|
||||
if (props.onDragStart) {
|
||||
props.onDragStart({ newHeight: startHeight, newWidth: startWidth });
|
||||
}
|
||||
|
||||
const handleMouseMove = (moveEvent) => {
|
||||
moveEvent.stopPropagation();
|
||||
moveEvent.preventDefault();
|
||||
let newHeight = startHeight;
|
||||
let newWidth = startWidth;
|
||||
|
||||
if (!props.lockVertical) {
|
||||
const deltaY = props.isReverseVerticalDrag ? startY - moveEvent.clientY : moveEvent.clientY - startY;
|
||||
newHeight = startHeight + deltaY;
|
||||
newHeight = Math.max(props.minHeight, Math.min(props.maxHeight, newHeight));
|
||||
newHeight = Math.round(newHeight / props.stepHeight) * props.stepHeight; // Snap to stepHeight
|
||||
}
|
||||
|
||||
if (!props.lockHorizontal) {
|
||||
newWidth = startWidth + (moveEvent.clientX - startX);
|
||||
newWidth = Math.max(props.minWidth, Math.min(props.maxWidth, newWidth));
|
||||
newWidth = Math.round(newWidth / props.stepWidth) * props.stepWidth; // Snap to stepWidth
|
||||
|
||||
if (isPercentage) {
|
||||
newWidth = (newWidth / parentWidth) * 100; // Convert to percentage
|
||||
newWidth = `${newWidth.toFixed(2)}%`;
|
||||
} else {
|
||||
newWidth = `${newWidth}px`;
|
||||
}
|
||||
}
|
||||
|
||||
setHeight(`${newHeight}px`);
|
||||
setWidth(newWidth);
|
||||
|
||||
if (parentRef.current) {
|
||||
parentRef.current.style.height = `${newHeight}px`;
|
||||
parentRef.current.style.width = newWidth;
|
||||
}
|
||||
|
||||
if (props.onResize) {
|
||||
props.onResize({
|
||||
newHeight,
|
||||
newWidth,
|
||||
heightDiff: newHeight - startHeight,
|
||||
widthDiff: isPercentage
|
||||
? parseInt(newWidth) - (startWidth / parentWidth) * 100
|
||||
: parseInt(newWidth) - startWidth,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
setIsDragging(false); // ✅ Set dragging state to false
|
||||
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseup', handleMouseUp);
|
||||
|
||||
if (props.onDragEnd) {
|
||||
// Get the updated height and width from the DOM instead of relying on state
|
||||
const finalHeight = parentRef.current ? parseInt(parentRef.current.clientHeight) : parseInt(height);
|
||||
const finalWidth = parentRef.current ? parseInt(parentRef.current.clientWidth) : parseInt(width);
|
||||
|
||||
props.onDragEnd({ newHeight: finalHeight, newWidth: finalWidth });
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('mouseup', handleMouseUp);
|
||||
};
|
||||
|
||||
return {
|
||||
onMouseDown: handleMouseDown,
|
||||
};
|
||||
};
|
||||
|
||||
return { rootRef: parentRef, getRootProps, getHandleProps, getResizeState };
|
||||
};
|
||||
|
||||
export default useResizable;
|
||||
|
|
@ -1896,4 +1896,28 @@ export const createComponentsSlice = (set, get) => ({
|
|||
state.modalsOpenOnCanvas = newModalOpenOnCanvas;
|
||||
});
|
||||
},
|
||||
updateContainerAutoHeight: (componentId) => {
|
||||
if (
|
||||
!componentId ||
|
||||
componentId === 'canvas' ||
|
||||
componentId.includes('-header') ||
|
||||
componentId.includes('-footer')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const { currentLayout, getCurrentPageComponents, setComponentProperty } = get();
|
||||
const allComponents = getCurrentPageComponents();
|
||||
|
||||
const childComponents = getAllChildComponents(allComponents, componentId);
|
||||
const maxHeight = Object.values(childComponents).reduce((max, component) => {
|
||||
const layout = component?.layouts?.[currentLayout];
|
||||
if (!layout) {
|
||||
return max;
|
||||
}
|
||||
const sum = layout.top + layout.height;
|
||||
return Math.max(max, sum);
|
||||
}, 0);
|
||||
|
||||
setComponentProperty(componentId, `canvasHeight`, maxHeight, 'properties', 'value', false);
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1092,5 +1092,23 @@ export const createQueryPanelSlice = (set, get) => ({
|
|||
isQuerySelected: (queryId) => {
|
||||
return get().queryPanel.selectedQuery?.id === queryId;
|
||||
},
|
||||
runQueryOnShortcut: () => {
|
||||
const { queryPanel } = get();
|
||||
const { runQuery, selectedQuery } = queryPanel;
|
||||
runQuery(selectedQuery?.id, selectedQuery?.name, undefined, 'edit', {}, true);
|
||||
},
|
||||
previewQueryOnShortcut: (moduleId = 'canvas') => {
|
||||
const { queryPanel } = get();
|
||||
const { previewQuery, selectedQuery, selectedDataSource } = queryPanel;
|
||||
const query = {
|
||||
data_source_id: selectedDataSource.id === 'null' ? null : selectedDataSource.id,
|
||||
pluginId: selectedDataSource.pluginId,
|
||||
options: { ...selectedQuery?.options },
|
||||
kind: selectedDataSource.kind,
|
||||
name: selectedQuery?.name ?? '',
|
||||
id: selectedQuery?.id,
|
||||
};
|
||||
previewQuery(query, false, undefined, moduleId);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -161,8 +161,8 @@ export const ColorPicker = function ({
|
|||
: { display: 'none' };
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ baseStyle, boxShadow }} className="form-control" data-cy={dataCy}>
|
||||
<div className="h-100">
|
||||
<div style={{ baseStyle, boxShadow, height: '100%' }} className="form-control" data-cy={dataCy}>
|
||||
<div
|
||||
className="d-flex h-100 justify-content-between align-items-center"
|
||||
onClick={() => setShowColorPicker(true)}
|
||||
|
|
|
|||
|
|
@ -1,87 +1,118 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { components } from 'react-select';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import Loader from '@/ToolJetUI/Loader/Loader';
|
||||
import './dropdownV2.scss';
|
||||
import { FormCheck } from 'react-bootstrap';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import cx from 'classnames';
|
||||
|
||||
const { MenuList } = components;
|
||||
|
||||
// This Menulist also used in MultiselectV2
|
||||
const CustomMenuList = ({ selectProps, ...props }) => {
|
||||
const {
|
||||
onInputChange,
|
||||
onMenuInputFocus,
|
||||
showAllOption,
|
||||
isSelectAllSelected,
|
||||
optionsLoadingState,
|
||||
darkMode,
|
||||
setSelected,
|
||||
setIsSelectAllSelected,
|
||||
fireEvent,
|
||||
inputValue,
|
||||
menuId,
|
||||
} = selectProps;
|
||||
const { onInputChange, onMenuInputFocus, optionsLoadingState, darkMode, inputValue, menuId, showSearchInput } =
|
||||
selectProps;
|
||||
|
||||
const handleSelectAll = (e) => {
|
||||
e.target.checked && fireEvent();
|
||||
if (e.target.checked) {
|
||||
setSelected(props.options);
|
||||
} else {
|
||||
setSelected([]);
|
||||
const parentRef = useRef(null);
|
||||
const virtualizer = useVirtualizer({
|
||||
count: props?.children?.length || 0,
|
||||
getScrollElement: () => parentRef.current,
|
||||
estimateSize: () => 40,
|
||||
overscan: 15,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const searchInput = document.querySelector('.dropdown-multiselect-widget-search-box');
|
||||
if (searchInput) {
|
||||
searchInput.focus();
|
||||
}
|
||||
setIsSelectAllSelected(e.target.checked);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
id={`dropdown-multiselect-widget-custom-menu-list-${menuId}`}
|
||||
className={cx('dropdown-multiselect-widget-custom-menu-list', { 'theme-dark dark-theme': darkMode })}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onTouchEnd={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="dropdown-multiselect-widget-search-box-wrapper">
|
||||
<span>
|
||||
<SolidIcon name="search01" width="14" />
|
||||
</span>
|
||||
<input
|
||||
autoCorrect="off"
|
||||
autoComplete="off"
|
||||
spellCheck="false"
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={(e) =>
|
||||
onInputChange(e.currentTarget.value, {
|
||||
action: 'input-change',
|
||||
})
|
||||
}
|
||||
onMouseDown={(e) => {
|
||||
e.stopPropagation();
|
||||
e.target.focus();
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
e.target.focus();
|
||||
}}
|
||||
onFocus={onMenuInputFocus}
|
||||
placeholder="Search"
|
||||
className="dropdown-multiselect-widget-search-box"
|
||||
/>
|
||||
</div>
|
||||
{showAllOption && !optionsLoadingState && (
|
||||
<label htmlFor="select-all-checkbox" className="multiselect-custom-menulist-select-all">
|
||||
<FormCheck id="select-all-checkbox" checked={isSelectAllSelected} onChange={handleSelectAll} />
|
||||
<span style={{ marginLeft: '4px' }}>Select all</span>
|
||||
</label>
|
||||
{showSearchInput && (
|
||||
<div className="dropdown-multiselect-widget-search-box-wrapper">
|
||||
<span>
|
||||
<SolidIcon name="search01" width="14" />
|
||||
</span>
|
||||
<input
|
||||
autoCorrect="off"
|
||||
autoComplete="off"
|
||||
spellCheck="false"
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={(e) =>
|
||||
onInputChange(e.currentTarget.value, {
|
||||
action: 'input-change',
|
||||
})
|
||||
}
|
||||
onMouseDown={(e) => {
|
||||
e.stopPropagation();
|
||||
e.target.focus();
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
e.target.focus();
|
||||
}}
|
||||
onFocus={onMenuInputFocus}
|
||||
placeholder="Search"
|
||||
className="dropdown-multiselect-widget-search-box"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<MenuList {...props} selectProps={selectProps}>
|
||||
{optionsLoadingState ? (
|
||||
<div class="text-center py-4" style={{ minHeight: '188px' }}>
|
||||
<Loader style={{ zIndex: 3, position: 'absolute' }} width="36" />
|
||||
{!optionsLoadingState && (
|
||||
<div
|
||||
ref={parentRef}
|
||||
className="dropdown-multiselect-widget-custom-menu-list-body"
|
||||
style={{
|
||||
maxHeight: selectProps.maxMenuHeight || 300,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: `${virtualizer.getTotalSize() || 38}px`,
|
||||
position: 'relative',
|
||||
marginTop: '5px',
|
||||
}}
|
||||
>
|
||||
{!virtualizer.getTotalSize() && props.children}
|
||||
{virtualizer.getVirtualItems().map((virtualItem) => {
|
||||
const option = props.options[virtualItem.index];
|
||||
const child = props.children[virtualItem.index];
|
||||
const isSelectAll = option?.value === 'multiselect-custom-menulist-select-all';
|
||||
return (
|
||||
<div
|
||||
key={option.value}
|
||||
style={{
|
||||
position: isSelectAll ? 'sticky' : 'absolute',
|
||||
width: '100%',
|
||||
top: 0,
|
||||
zIndex: isSelectAll && 10,
|
||||
transform: `translateY(${virtualItem.start}px)`,
|
||||
}}
|
||||
data-index={virtualItem.index}
|
||||
ref={virtualizer.measureElement}
|
||||
>
|
||||
<MenuList {...props} selectProps={selectProps}>
|
||||
<div>{child}</div>
|
||||
</MenuList>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
props.children
|
||||
)}
|
||||
</MenuList>
|
||||
</div>
|
||||
)}
|
||||
{optionsLoadingState && (
|
||||
<div className="text-center py-4" style={{ minHeight: '188px' }}>
|
||||
<Loader style={{ zIndex: 3, position: 'absolute' }} width="36" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,7 +6,17 @@ import { highlightText } from './utils';
|
|||
|
||||
const CustomOption = (props) => {
|
||||
return (
|
||||
<components.Option {...props}>
|
||||
<components.Option
|
||||
{...props}
|
||||
innerProps={{
|
||||
...props.innerProps,
|
||||
onTouchEnd: (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
props.selectOption(props.data);
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div className="cursor-pointer">
|
||||
{props.isSelected && (
|
||||
<span style={{ maxHeight: '20px', marginRight: '8px', marginLeft: '-28px' }}>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import Label from '@/_ui/Label';
|
|||
import cx from 'classnames';
|
||||
import { getInputBackgroundColor, getInputBorderColor, getInputFocusedColor, sortArray } from './utils';
|
||||
import { isMobileDevice } from '@/_helpers/appUtils';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
|
||||
const { DropdownIndicator, ClearIndicator } = components;
|
||||
const INDICATOR_CONTAINER_WIDTH = 60;
|
||||
|
|
@ -69,6 +68,8 @@ export const DropdownV2 = ({
|
|||
disabledState,
|
||||
optionsLoadingState,
|
||||
sort,
|
||||
showClearBtn,
|
||||
showSearchInput,
|
||||
} = properties;
|
||||
const {
|
||||
selectedTextColor,
|
||||
|
|
@ -104,8 +105,6 @@ export const DropdownV2 = ({
|
|||
const [isDropdownDisabled, setIsDropdownDisabled] = useState(disabledState);
|
||||
const [searchInputValue, setSearchInputValue] = useState('');
|
||||
const [userInteracted, setUserInteracted] = useState(false);
|
||||
const currentMode = useStore((state) => state.currentMode);
|
||||
const isEditor = currentMode === 'edit';
|
||||
|
||||
const _height = padding === 'default' ? `${height}px` : `${height + 4}px`;
|
||||
const labelRef = useRef();
|
||||
|
|
@ -173,12 +172,43 @@ export const DropdownV2 = ({
|
|||
setExposedVariable('isValid', validationStatus?.isValid);
|
||||
};
|
||||
|
||||
const handleClickInEditor = (e) => {
|
||||
if (e.target.className.includes('clear-indicator') || isMenuOpen) return;
|
||||
e.stopPropagation();
|
||||
selectRef.current?.onControlMouseDown(e);
|
||||
const handleClickInsideSelect = () => {
|
||||
if (isDropdownDisabled || isDropdownLoading) return;
|
||||
if (isMenuOpen) {
|
||||
setIsMenuOpen(false);
|
||||
fireEvent('onBlur');
|
||||
setSearchInputValue('');
|
||||
} else {
|
||||
setIsMenuOpen(true);
|
||||
fireEvent('onFocus');
|
||||
if (!showSearchInput) {
|
||||
selectRef.current.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleClickOutsideSelect = (event) => {
|
||||
const menu = document.querySelector(`._tooljet-${componentName}`);
|
||||
if (
|
||||
isMenuOpen &&
|
||||
menu &&
|
||||
dropdownRef.current &&
|
||||
!dropdownRef.current.contains(event.target) &&
|
||||
!menu.contains(event.target)
|
||||
) {
|
||||
setIsMenuOpen(false);
|
||||
fireEvent('onBlur');
|
||||
setSearchInputValue('');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('mousedown', handleClickOutsideSelect);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutsideSelect);
|
||||
};
|
||||
}, [isMenuOpen, componentName]);
|
||||
|
||||
useEffect(() => {
|
||||
setInputValue(findDefaultItem(advanced ? schema : options));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
|
@ -331,8 +361,8 @@ export const DropdownV2 = ({
|
|||
selectedTextColor !== '#1B1F24'
|
||||
? selectedTextColor
|
||||
: isDropdownDisabled || isDropdownLoading
|
||||
? 'var(--text-disabled)'
|
||||
: 'var(--text-primary)',
|
||||
? 'var(--text-disabled)'
|
||||
: 'var(--text-primary)',
|
||||
maxWidth:
|
||||
ref?.current?.offsetWidth -
|
||||
(iconVisibility ? INDICATOR_CONTAINER_WIDTH + ICON_WIDTH : INDICATOR_CONTAINER_WIDTH),
|
||||
|
|
@ -373,8 +403,8 @@ export const DropdownV2 = ({
|
|||
selectedTextColor !== '#1B1F24'
|
||||
? selectedTextColor
|
||||
: isDropdownDisabled || isDropdownLoading
|
||||
? 'var(--text-disabled)'
|
||||
: 'var(--text-primary)',
|
||||
? 'var(--text-disabled)'
|
||||
: 'var(--text-primary)',
|
||||
borderRadius: _state.isFocused && '8px',
|
||||
padding: '8px 6px 8px 38px',
|
||||
'&:hover': {
|
||||
|
|
@ -386,7 +416,7 @@ export const DropdownV2 = ({
|
|||
}),
|
||||
menuList: (provided) => ({
|
||||
...provided,
|
||||
padding: '8px',
|
||||
padding: '0 8px',
|
||||
borderRadius: '8px',
|
||||
// this is needed otherwise :active state doesn't look nice, gap is required
|
||||
display: 'flex',
|
||||
|
|
@ -410,8 +440,8 @@ export const DropdownV2 = ({
|
|||
data-cy={`label-${String(componentName).toLowerCase()} `}
|
||||
className={cx('dropdown-widget', 'd-flex', {
|
||||
[alignment === 'top' &&
|
||||
((labelWidth != 0 && label?.length != 0) ||
|
||||
(labelAutoWidth && labelWidth == 0 && label && label?.length != 0))
|
||||
((labelWidth != 0 && label?.length != 0) ||
|
||||
(labelAutoWidth && labelWidth == 0 && label && label?.length != 0))
|
||||
? 'flex-column'
|
||||
: 'align-items-center']: true,
|
||||
'flex-row-reverse': direction === 'right' && alignment === 'side',
|
||||
|
|
@ -446,7 +476,8 @@ export const DropdownV2 = ({
|
|||
<div
|
||||
className="w-100 px-0 h-100 dropdownV2-widget"
|
||||
ref={ref}
|
||||
onMouseDownCapture={isEditor && handleClickInEditor}
|
||||
onClick={handleClickInsideSelect}
|
||||
onTouchEnd={handleClickInsideSelect}
|
||||
>
|
||||
<Select
|
||||
ref={selectRef}
|
||||
|
|
@ -456,17 +487,21 @@ export const DropdownV2 = ({
|
|||
onChange={(selectedOption, actionProps) => {
|
||||
if (actionProps.action === 'clear') {
|
||||
setInputValue(null);
|
||||
fireEvent('onSelect');
|
||||
}
|
||||
if (actionProps.action === 'select-option') {
|
||||
setInputValue(selectedOption.value);
|
||||
fireEvent('onSelect');
|
||||
if (currentValue === selectedOption.value) {
|
||||
setInputValue(null);
|
||||
} else setInputValue(selectedOption.value);
|
||||
}
|
||||
fireEvent('onSelect');
|
||||
setSearchInputValue('');
|
||||
setIsMenuOpen(false);
|
||||
setUserInteracted(true);
|
||||
}}
|
||||
options={selectOptions}
|
||||
styles={customStyles}
|
||||
isLoading={isDropdownLoading}
|
||||
showSearchInput={showSearchInput}
|
||||
onInputChange={onSearchTextChange}
|
||||
inputValue={searchInputValue}
|
||||
placeholder={placeholder}
|
||||
|
|
@ -477,7 +512,7 @@ export const DropdownV2 = ({
|
|||
Option: CustomOption,
|
||||
LoadingIndicator: () => <Loader style={{ right: '11px', zIndex: 3, position: 'absolute' }} width="16" />,
|
||||
DropdownIndicator: isDropdownLoading ? () => null : CustomDropdownIndicator,
|
||||
ClearIndicator: CustomClearIndicator,
|
||||
ClearIndicator: showClearBtn ? CustomClearIndicator : () => null,
|
||||
}}
|
||||
isClearable
|
||||
tabSelectsValue={false}
|
||||
|
|
@ -488,24 +523,20 @@ export const DropdownV2 = ({
|
|||
darkMode={darkMode}
|
||||
optionsLoadingState={optionsLoadingState && advanced}
|
||||
menuPlacement="auto"
|
||||
onMenuOpen={() => {
|
||||
setIsMenuOpen(true);
|
||||
fireEvent('onFocus');
|
||||
}}
|
||||
onMenuClose={() => {
|
||||
setIsMenuOpen(false);
|
||||
fireEvent('onBlur');
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && !isMenuOpen) {
|
||||
if (e.key === 'Enter' && !isMenuOpen && !isDropdownLoading) {
|
||||
setIsMenuOpen(true);
|
||||
fireEvent('onFocus');
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.key === 'Escape' && isMenuOpen) {
|
||||
setIsMenuOpen(false);
|
||||
fireEvent('onBlur');
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
// This is not setting minheight, required to help calculate menuPlacement by providing fixed height upfront before rendering (required in the case of modal)
|
||||
minMenuHeight={300}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
.dropdown-multiselect-widget-custom-menu-list-body {
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
padding: 0 0 5px;
|
||||
background-color: var(--surfaces-surface-01) !important;
|
||||
border-radius: 0 0 8px 8px;
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ export const Icon = ({
|
|||
}) => {
|
||||
const isInitialRender = useRef(true);
|
||||
const { icon, loadingState, disabledState } = properties;
|
||||
const { iconAlign, iconColor } = styles;
|
||||
const { iconAlign, iconColor, boxShadow } = styles;
|
||||
// eslint-disable-next-line import/namespace
|
||||
const IconElement = Icons[icon];
|
||||
|
||||
|
|
@ -84,10 +84,10 @@ export const Icon = ({
|
|||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={cx('icon-widget', { 'd-none': !visibility }, { 'cursor-pointer': false })}
|
||||
className={cx('icon-widget h-100', { 'd-none': !visibility }, { 'cursor-pointer': false })}
|
||||
data-cy={dataCy}
|
||||
data-disabled={isDisabled}
|
||||
style={{ textAlign: iconAlign }}
|
||||
style={{ textAlign: iconAlign, boxShadow }}
|
||||
onMouseEnter={(event) => {
|
||||
event.stopPropagation();
|
||||
fireEvent('onHover');
|
||||
|
|
@ -97,7 +97,7 @@ export const Icon = ({
|
|||
color={color}
|
||||
style={{
|
||||
width: height < width ? 'auto' : width,
|
||||
height: height < width ? height : 'auto',
|
||||
height: height < width ? '100%' : 'auto',
|
||||
color: iconColor,
|
||||
}}
|
||||
onClick={(event) => {
|
||||
|
|
|
|||
|
|
@ -19,8 +19,7 @@ export const Link = ({ height, properties, styles, fireEvent, setExposedVariable
|
|||
const computedStyles = {
|
||||
display: 'flex',
|
||||
alignItems: verticalAlignment === 'top' ? 'flex-start' : verticalAlignment === 'center' ? 'center' : 'flex-end',
|
||||
justifyContent:
|
||||
horizontalAlignment === 'left' ? 'flex-start' : horizontalAlignment === 'center' ? 'center' : 'flex-end',
|
||||
textAlign: horizontalAlignment === 'left' ? 'left' : horizontalAlignment === 'center' ? 'center' : 'right',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
boxShadow,
|
||||
|
|
@ -113,10 +112,20 @@ export const Link = ({ height, properties, styles, fireEvent, setExposedVariable
|
|||
onMouseOver={() => {
|
||||
fireEvent('onHover');
|
||||
}}
|
||||
style={{ color: textColor, fontSize: textSize, cursor: isDisabled ? 'not-allowed' : 'pointer' }}
|
||||
style={{ width: '100%' }}
|
||||
ref={clickRef}
|
||||
>
|
||||
<span className="d-flex justify-content-center">
|
||||
<span
|
||||
className="d-flex"
|
||||
style={{
|
||||
fontSize: textSize,
|
||||
cursor: 'auto',
|
||||
justifyContent:
|
||||
horizontalAlignment === 'left' ? 'flex-start' : horizontalAlignment === 'center' ? 'center' : 'flex-end',
|
||||
color: textColor,
|
||||
paddingBottom: verticalAlignment === 'bottom' ? '1px' : '0px',
|
||||
}}
|
||||
>
|
||||
{iconVisibility && (
|
||||
<IconElement
|
||||
style={{
|
||||
|
|
@ -130,7 +139,7 @@ export const Link = ({ height, properties, styles, fireEvent, setExposedVariable
|
|||
stroke={1.5}
|
||||
/>
|
||||
)}
|
||||
{linkTextState}
|
||||
<span className="link-text">{linkTextState}</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,18 @@
|
|||
.link-widget {
|
||||
a {
|
||||
text-underline-offset: 32%; // Adds space between text and underline
|
||||
&:hover {
|
||||
color: var(--link-hover-color) !important;
|
||||
}
|
||||
text-decoration: none !important;
|
||||
pointer-events: none;
|
||||
cursor: none !important;
|
||||
|
||||
.link-text {
|
||||
pointer-events: all;
|
||||
text-underline-offset: 32%; // Adds space between text and underline
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: var(--link-hover-color) !important;
|
||||
color: var(--link-hover-color) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,23 @@ import { highlightText } from '../DropdownV2/utils';
|
|||
|
||||
const CustomOption = (props) => {
|
||||
return (
|
||||
<Option {...props}>
|
||||
<Option
|
||||
{...props}
|
||||
innerProps={{
|
||||
...props.innerProps,
|
||||
onTouchEnd: (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
props.selectOption(props.data);
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div className="d-flex multiselct-widget-option">
|
||||
<FormCheck checked={props.isSelected} disabled={props?.isDisabled} />
|
||||
<span style={{ marginLeft: '5px' }}>
|
||||
{highlightText(props.label?.toString(), props.selectProps.inputValue)}
|
||||
{props.label?.includes('Select all')
|
||||
? 'Select all'
|
||||
: highlightText(props.label?.toString(), props.selectProps.inputValue)}
|
||||
</span>
|
||||
</div>
|
||||
</Option>
|
||||
|
|
|
|||
|
|
@ -36,13 +36,13 @@ const CustomValueContainer = ({ children, ...props }) => {
|
|||
</Placeholder>
|
||||
) : (
|
||||
<span className="text-truncate" {...props} id="options" style={{ maxWidth: valueContainerWidth }}>
|
||||
{isAllOptionsSelected ? 'All items are selected.' : values.join(', ')}
|
||||
{selectProps?.showAllSelectedLabel && isAllOptionsSelected ? 'All items are selected.' : values.join(', ')}
|
||||
</span>
|
||||
)}
|
||||
{/* Rendering children except Placeholder component to preserve the default behavior of react-select like focus
|
||||
handling */}
|
||||
{React.Children.map(children, (child) => {
|
||||
if (child.type !== Placeholder) {
|
||||
if (child?.type !== Placeholder) {
|
||||
return child;
|
||||
}
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import Label from '@/_ui/Label';
|
|||
const tinycolor = require('tinycolor2');
|
||||
import { CustomDropdownIndicator, CustomClearIndicator } from '../DropdownV2/DropdownV2';
|
||||
import { getInputBackgroundColor, getInputBorderColor, getInputFocusedColor, sortArray } from '../DropdownV2/utils';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
|
||||
export const MultiselectV2 = ({
|
||||
id,
|
||||
|
|
@ -38,6 +37,9 @@ export const MultiselectV2 = ({
|
|||
loadingState: multiSelectLoadingState,
|
||||
optionsLoadingState,
|
||||
sort,
|
||||
showAllSelectedLabel,
|
||||
showClearBtn,
|
||||
showSearchInput,
|
||||
} = properties;
|
||||
const {
|
||||
selectedTextColor,
|
||||
|
|
@ -73,12 +75,9 @@ export const MultiselectV2 = ({
|
|||
const [visibility, setVisibility] = useState(properties.visibility);
|
||||
const [isMultiSelectLoading, setIsMultiSelectLoading] = useState(multiSelectLoadingState);
|
||||
const [isMultiSelectDisabled, setIsMultiSelectDisabled] = useState(disabledState);
|
||||
const [isSelectAllSelected, setIsSelectAllSelected] = useState(false);
|
||||
const [searchInputValue, setSearchInputValue] = useState('');
|
||||
const _height = padding === 'default' ? `${height}px` : `${height + 4}px`;
|
||||
const [userInteracted, setUserInteracted] = useState(false);
|
||||
const currentMode = useStore((state) => state.currentMode);
|
||||
const isEditor = currentMode === 'edit';
|
||||
|
||||
const [isMultiselectOpen, setIsMultiselectOpen] = useState(false);
|
||||
useEffect(() => {
|
||||
|
|
@ -93,18 +92,29 @@ export const MultiselectV2 = ({
|
|||
const _options = advanced ? schema : options;
|
||||
let _selectOptions = Array.isArray(_options)
|
||||
? _options
|
||||
.filter((data) => data?.visible ?? true)
|
||||
.map((data) => ({
|
||||
...data,
|
||||
label: data?.label,
|
||||
value: data?.value,
|
||||
isDisabled: data?.disable ?? false,
|
||||
}))
|
||||
.filter((data) => data?.visible ?? true)
|
||||
.map((data) => ({
|
||||
...data,
|
||||
label: data?.label,
|
||||
value: data?.value,
|
||||
isDisabled: data?.disable ?? false,
|
||||
}))
|
||||
: [];
|
||||
return sortArray(_selectOptions, sort);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [advanced, JSON.stringify(schema), JSON.stringify(options), sort]);
|
||||
|
||||
const modifiedSelectOptions = useMemo(() => {
|
||||
// Adding select all option dynamically to the options
|
||||
if (showAllOption && !optionsLoadingState) {
|
||||
return [
|
||||
// Appended search input value so that it is always visible
|
||||
{ label: `Select all ${searchInputValue}`, value: 'multiselect-custom-menulist-select-all' },
|
||||
...selectOptions,
|
||||
];
|
||||
} else return selectOptions;
|
||||
}, [showAllOption, JSON.stringify(selectOptions), optionsLoadingState, searchInputValue]);
|
||||
|
||||
function findDefaultItem(value, isAdvanced, isDefault) {
|
||||
if (isAdvanced) {
|
||||
const foundItem = Array.isArray(schema) ? schema.filter((item) => item?.visible && item?.default) : [];
|
||||
|
|
@ -129,8 +139,28 @@ export const MultiselectV2 = ({
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const onChangeHandler = (items, action) => {
|
||||
setInputValue(items);
|
||||
const SELECT_ALL = 'multiselect-custom-menulist-select-all';
|
||||
|
||||
if (action.option?.value === SELECT_ALL) {
|
||||
// Case 1 - If select all is selected
|
||||
if (action.action === 'select-option') {
|
||||
setInputValue(modifiedSelectOptions);
|
||||
} else {
|
||||
setInputValue([]);
|
||||
}
|
||||
} else if (items?.some((item) => item.value === SELECT_ALL)) {
|
||||
// Case 2 - If select all is not selected but selected options include select all
|
||||
setInputValue(items.filter((item) => item.value !== SELECT_ALL));
|
||||
} else if (selectOptions?.length === items?.length) {
|
||||
// Case 3 - If all options are selected except select all
|
||||
setInputValue(modifiedSelectOptions);
|
||||
} else {
|
||||
// Case 4 - Normal selection
|
||||
setInputValue(items);
|
||||
}
|
||||
|
||||
fireEvent('onSelect');
|
||||
setUserInteracted(true);
|
||||
};
|
||||
|
|
@ -270,26 +300,34 @@ export const MultiselectV2 = ({
|
|||
fireEvent('onSearchTextChanged');
|
||||
}
|
||||
};
|
||||
const handleClickOutside = (event) => {
|
||||
const handleClickOutsideSelect = (event) => {
|
||||
let menu = document.getElementById(`dropdown-multiselect-widget-custom-menu-list-${id}`);
|
||||
if (
|
||||
isMultiselectOpen &&
|
||||
multiselectRef.current &&
|
||||
!multiselectRef.current.contains(event.target) &&
|
||||
menu &&
|
||||
!menu.contains(event.target)
|
||||
) {
|
||||
if (isMultiselectOpen) {
|
||||
fireEvent('onBlur');
|
||||
setIsMultiselectOpen(false);
|
||||
setSearchInputValue('');
|
||||
}
|
||||
setIsMultiselectOpen(false);
|
||||
fireEvent('onBlur');
|
||||
setSearchInputValue('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleClickInEditor = (e) => {
|
||||
if (e.target.className.includes('clear-indicator') || isMultiselectOpen) return;
|
||||
e.stopPropagation();
|
||||
selectRef.current?.onControlMouseDown(e);
|
||||
const handleClickInsideSelect = () => {
|
||||
if (isMultiSelectDisabled || isMultiSelectLoading) return;
|
||||
if (isMultiselectOpen) {
|
||||
setIsMultiselectOpen(false);
|
||||
fireEvent('onBlur');
|
||||
setSearchInputValue('');
|
||||
} else {
|
||||
setIsMultiselectOpen(true);
|
||||
fireEvent('onFocus');
|
||||
if (!showSearchInput) {
|
||||
selectRef.current.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const setInputValue = (values) => {
|
||||
|
|
@ -304,20 +342,11 @@ export const MultiselectV2 = ({
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('mousedown', handleClickOutside, { capture: true });
|
||||
document.addEventListener('mousedown', handleClickOutsideSelect, { capture: true });
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside, { capture: true });
|
||||
document.removeEventListener('mousedown', handleClickOutsideSelect, { capture: true });
|
||||
};
|
||||
}, [isMultiselectOpen]);
|
||||
|
||||
// Handle Select all logic
|
||||
useEffect(() => {
|
||||
if (selectOptions?.length === selected?.length) {
|
||||
setIsSelectAllSelected(true);
|
||||
} else {
|
||||
setIsSelectAllSelected(false);
|
||||
}
|
||||
}, [selectOptions, selected]);
|
||||
}, [isMultiselectOpen, componentName]);
|
||||
|
||||
const customStyles = {
|
||||
container: (base) => ({
|
||||
|
|
@ -365,8 +394,8 @@ export const MultiselectV2 = ({
|
|||
selectedTextColor !== '#1B1F24'
|
||||
? selectedTextColor
|
||||
: isMultiSelectLoading || isMultiSelectDisabled
|
||||
? 'var(--text-disabled)'
|
||||
: 'var(--text-primary)',
|
||||
? 'var(--text-disabled)'
|
||||
: 'var(--text-primary)',
|
||||
}),
|
||||
|
||||
input: (provided, _state) => ({
|
||||
|
|
@ -401,10 +430,10 @@ export const MultiselectV2 = ({
|
|||
color: _state.isDisabled
|
||||
? 'var(_--text-disbled)'
|
||||
: selectedTextColor !== '#1B1F24'
|
||||
? selectedTextColor
|
||||
: isMultiSelectDisabled || isMultiSelectLoading
|
||||
? 'var(--text-disabled)'
|
||||
: 'var(--text-primary)',
|
||||
? selectedTextColor
|
||||
: isMultiSelectDisabled || isMultiSelectLoading
|
||||
? 'var(--text-disabled)'
|
||||
: 'var(--text-primary)',
|
||||
borderRadius: _state.isFocused && '8px',
|
||||
padding: '8px 6px 8px 12px',
|
||||
'&:hover': {
|
||||
|
|
@ -415,7 +444,7 @@ export const MultiselectV2 = ({
|
|||
}),
|
||||
menuList: (provided) => ({
|
||||
...provided,
|
||||
padding: '4px',
|
||||
padding: '0 4px',
|
||||
// this is needed otherwise :active state doesn't look nice, gap is required
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
|
|
@ -426,6 +455,7 @@ export const MultiselectV2 = ({
|
|||
menu: (provided) => ({
|
||||
...provided,
|
||||
marginTop: '5px',
|
||||
borderRadius: '8px',
|
||||
}),
|
||||
};
|
||||
const _width = (labelWidth / 100) * 70; // Max width which label can go is 70% for better UX calculate width based on this value
|
||||
|
|
@ -436,7 +466,7 @@ export const MultiselectV2 = ({
|
|||
data-cy={`label-${String(componentName).toLowerCase()} `}
|
||||
className={cx('multiselect-widget', 'd-flex', {
|
||||
[alignment === 'top' &&
|
||||
((labelWidth != 0 && label?.length != 0) || (auto && labelWidth == 0 && label && label?.length != 0))
|
||||
((labelWidth != 0 && label?.length != 0) || (auto && labelWidth == 0 && label && label?.length != 0))
|
||||
? 'flex-column'
|
||||
: 'align-items-center']: true,
|
||||
'flex-row-reverse': direction === 'right' && alignment === 'side',
|
||||
|
|
@ -468,17 +498,18 @@ export const MultiselectV2 = ({
|
|||
_width={_width}
|
||||
top={'1px'}
|
||||
/>
|
||||
<div className="w-100 px-0 h-100" onMouseDownCapture={isEditor && handleClickInEditor}>
|
||||
<div className="w-100 px-0 h-100" onClick={handleClickInsideSelect} onTouchEnd={handleClickInsideSelect}>
|
||||
<Select
|
||||
ref={selectRef}
|
||||
menuId={id}
|
||||
isDisabled={isMultiSelectDisabled}
|
||||
value={selected}
|
||||
onChange={onChangeHandler}
|
||||
options={selectOptions}
|
||||
options={modifiedSelectOptions}
|
||||
styles={customStyles}
|
||||
// Only show loading when dynamic options are enabled
|
||||
isLoading={isMultiSelectLoading}
|
||||
showSearchInput={showSearchInput}
|
||||
onInputChange={onSearchTextChange}
|
||||
inputValue={searchInputValue}
|
||||
menuIsOpen={isMultiselectOpen}
|
||||
|
|
@ -488,7 +519,7 @@ export const MultiselectV2 = ({
|
|||
ValueContainer: CustomValueContainer,
|
||||
Option: CustomOption,
|
||||
LoadingIndicator: () => <Loader style={{ right: '11px', zIndex: 3, position: 'absolute' }} width="16" />,
|
||||
ClearIndicator: CustomClearIndicator,
|
||||
ClearIndicator: showClearBtn ? CustomClearIndicator : () => null,
|
||||
DropdownIndicator: isMultiSelectLoading ? () => null : CustomDropdownIndicator,
|
||||
}}
|
||||
isClearable
|
||||
|
|
@ -498,21 +529,15 @@ export const MultiselectV2 = ({
|
|||
tabSelectsValue={false}
|
||||
controlShouldRenderValue={false}
|
||||
isSearchable={false}
|
||||
onMenuOpen={() => {
|
||||
setIsMultiselectOpen(true);
|
||||
fireEvent('onFocus');
|
||||
}}
|
||||
onMenuClose={() => {
|
||||
setIsMultiselectOpen(false);
|
||||
fireEvent('onBlur');
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && !isMultiselectOpen) {
|
||||
if (e.key === 'Enter' && !isMultiselectOpen && !isMultiSelectLoading) {
|
||||
setIsMultiselectOpen(true);
|
||||
fireEvent('onFocus');
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.key === 'Escape' && isMultiselectOpen) {
|
||||
setIsMultiselectOpen(false);
|
||||
fireEvent('onBlur');
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
|
|
@ -520,21 +545,14 @@ export const MultiselectV2 = ({
|
|||
icon={icon}
|
||||
doShowIcon={iconVisibility}
|
||||
containerRef={valueContainerRef}
|
||||
showAllOption={showAllOption}
|
||||
isSelectAllSelected={isSelectAllSelected}
|
||||
setIsSelectAllSelected={(value) => {
|
||||
setIsSelectAllSelected(value);
|
||||
if (!value) {
|
||||
fireEvent('onSelect');
|
||||
}
|
||||
}}
|
||||
setSelected={setInputValue}
|
||||
showAllSelectedLabel={showAllSelectedLabel}
|
||||
iconColor={iconColor}
|
||||
optionsLoadingState={optionsLoadingState && advanced}
|
||||
darkMode={darkMode}
|
||||
fireEvent={() => fireEvent('onSelect')}
|
||||
menuPlacement="auto"
|
||||
menuPortalTarget={document.body}
|
||||
// This is not setting minheight, required to help calculate menuPlacement by providing fixed height upfront before rendering (required in the case of modal)
|
||||
minMenuHeight={300}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -123,6 +123,19 @@ export const buttonGroupConfig = {
|
|||
defaultValue: '#007bff',
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
selected: [1],
|
||||
|
|
@ -148,6 +161,7 @@ export const buttonGroupConfig = {
|
|||
disabledState: { value: '{{false}}' },
|
||||
selectedTextColor: { value: '' },
|
||||
selectedBackgroundColor: { value: '' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -126,6 +126,20 @@ export const checkboxConfig = {
|
|||
],
|
||||
accordian: 'label',
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
accordian: 'switch',
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
value: false,
|
||||
|
|
@ -189,6 +203,7 @@ export const checkboxConfig = {
|
|||
handleColor: { value: '#FFFFFF' },
|
||||
alignment: { value: 'right' },
|
||||
boxShadow: { value: '0px 0px 0px 0px #00000090' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
validation: {
|
||||
mandatory: { value: '{{false}}' },
|
||||
|
|
|
|||
|
|
@ -26,6 +26,19 @@ export const colorPickerConfig = {
|
|||
},
|
||||
styles: {
|
||||
visibility: { type: 'toggle', displayName: 'Visibility' },
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
selectedColorHex: '#000000',
|
||||
|
|
@ -45,6 +58,7 @@ export const colorPickerConfig = {
|
|||
events: [],
|
||||
styles: {
|
||||
visibility: { value: '{{true}}' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ export const containerConfig = {
|
|||
displayName: 'Container',
|
||||
description: 'Group components',
|
||||
defaultSize: {
|
||||
width: 5,
|
||||
width: 10,
|
||||
height: 200,
|
||||
},
|
||||
component: 'Container',
|
||||
|
|
@ -44,13 +44,19 @@ export const containerConfig = {
|
|||
displayName: 'Show header',
|
||||
validation: {
|
||||
schema: { type: 'boolean' },
|
||||
defaultValue: false,
|
||||
defaultValue: true,
|
||||
},
|
||||
},
|
||||
headerHeight: {
|
||||
type: 'numberInput',
|
||||
displayName: 'Header height',
|
||||
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
|
||||
},
|
||||
},
|
||||
defaultChildren: [
|
||||
{
|
||||
componentName: 'Text',
|
||||
slotName: 'header',
|
||||
layout: {
|
||||
top: 20,
|
||||
left: 1,
|
||||
|
|
@ -98,15 +104,6 @@ export const containerConfig = {
|
|||
},
|
||||
accordian: 'container',
|
||||
},
|
||||
headerHeight: {
|
||||
type: 'numberInput',
|
||||
displayName: 'Height',
|
||||
validation: {
|
||||
schema: { type: 'number' },
|
||||
defaultValue: 80,
|
||||
},
|
||||
accordian: 'header',
|
||||
},
|
||||
borderRadius: {
|
||||
type: 'numberInput',
|
||||
displayName: 'Border',
|
||||
|
|
@ -154,10 +151,11 @@ export const containerConfig = {
|
|||
showOnMobile: { value: '{{false}}' },
|
||||
},
|
||||
properties: {
|
||||
showHeader: { value: `{{false}}` },
|
||||
showHeader: { value: `{{true}}` },
|
||||
loadingState: { value: `{{false}}` },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
headerHeight: { value: `{{80}}` },
|
||||
},
|
||||
events: [],
|
||||
styles: {
|
||||
|
|
|
|||
|
|
@ -75,6 +75,18 @@ export const dropdownV2Config = {
|
|||
accordian: 'Options',
|
||||
isFxNotRequired: true,
|
||||
},
|
||||
showClearBtn: {
|
||||
type: 'toggle',
|
||||
displayName: 'Show clear selection button',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
section: 'additionalActions',
|
||||
},
|
||||
showSearchInput: {
|
||||
type: 'toggle',
|
||||
displayName: 'Show search in options',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
section: 'additionalActions',
|
||||
},
|
||||
loadingState: {
|
||||
type: 'toggle',
|
||||
displayName: 'Loading state',
|
||||
|
|
@ -314,6 +326,8 @@ export const dropdownV2Config = {
|
|||
optionsLoadingState: { value: '{{false}}' },
|
||||
sort: { value: 'asc' },
|
||||
placeholder: { value: 'Select an option' },
|
||||
showClearBtn: { value: '{{true}}' },
|
||||
showSearchInput: { value: '{{true}}' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
loadingState: { value: '{{false}}' },
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ export const formConfig = {
|
|||
description: 'Wrapper for multiple components',
|
||||
defaultSize: {
|
||||
width: 13,
|
||||
height: 480,
|
||||
height: 450,
|
||||
},
|
||||
defaultChildren: [
|
||||
{
|
||||
|
|
@ -19,8 +19,8 @@ export const formConfig = {
|
|||
accessorKey: 'text',
|
||||
styles: ['fontWeight', 'textSize', 'textColor'],
|
||||
defaultValue: {
|
||||
text: 'Form title',
|
||||
textSize: 20,
|
||||
text: 'Form',
|
||||
textSize: 16,
|
||||
textColor: '#000',
|
||||
},
|
||||
},
|
||||
|
|
@ -34,203 +34,68 @@ export const formConfig = {
|
|||
},
|
||||
properties: ['text'],
|
||||
defaultValue: {
|
||||
text: 'Button2',
|
||||
text: 'Submit',
|
||||
padding: 'none',
|
||||
},
|
||||
},
|
||||
{
|
||||
componentName: 'Text',
|
||||
layout: {
|
||||
top: 40,
|
||||
left: 10,
|
||||
height: 30,
|
||||
width: 17,
|
||||
},
|
||||
properties: ['text'],
|
||||
styles: [
|
||||
'textSize',
|
||||
'fontWeight',
|
||||
'fontStyle',
|
||||
'textColor',
|
||||
'isScrollRequired',
|
||||
'lineHeight',
|
||||
'textIndent',
|
||||
'textAlign',
|
||||
'verticalAlignment',
|
||||
'decoration',
|
||||
'transformation',
|
||||
'letterSpacing',
|
||||
'wordSpacing',
|
||||
'fontVariant',
|
||||
'backgroundColor',
|
||||
'borderColor',
|
||||
'borderRadius',
|
||||
'boxShadow',
|
||||
'padding',
|
||||
],
|
||||
defaultValue: {
|
||||
text: 'User Details',
|
||||
fontWeight: 'bold',
|
||||
textSize: 18,
|
||||
textColor: '#000',
|
||||
backgroundColor: '#fff00000',
|
||||
textAlign: 'left',
|
||||
decoration: 'none',
|
||||
transformation: 'none',
|
||||
fontStyle: 'normal',
|
||||
lineHeight: 1.5,
|
||||
textIndent: '0',
|
||||
letterSpacing: '0',
|
||||
wordSpacing: '0',
|
||||
fontVariant: 'normal',
|
||||
verticalAlignment: 'top',
|
||||
padding: 'default',
|
||||
boxShadow: '0px 0px 0px 0px #00000090',
|
||||
borderRadius: '0',
|
||||
isScrollRequired: 'enabled',
|
||||
},
|
||||
},
|
||||
{
|
||||
componentName: 'Text',
|
||||
layout: {
|
||||
top: 90,
|
||||
left: 10,
|
||||
height: 30,
|
||||
},
|
||||
properties: ['text'],
|
||||
styles: [
|
||||
'textSize',
|
||||
'fontWeight',
|
||||
'fontStyle',
|
||||
'textColor',
|
||||
'isScrollRequired',
|
||||
'lineHeight',
|
||||
'textIndent',
|
||||
'textAlign',
|
||||
'verticalAlignment',
|
||||
'decoration',
|
||||
'transformation',
|
||||
'letterSpacing',
|
||||
'wordSpacing',
|
||||
'fontVariant',
|
||||
'backgroundColor',
|
||||
'borderColor',
|
||||
'borderRadius',
|
||||
'boxShadow',
|
||||
'padding',
|
||||
],
|
||||
defaultValue: {
|
||||
text: 'Name',
|
||||
fontWeight: 'normal',
|
||||
textSize: 14,
|
||||
textColor: '#000',
|
||||
backgroundColor: '#fff00000',
|
||||
textAlign: 'left',
|
||||
decoration: 'none',
|
||||
transformation: 'none',
|
||||
fontStyle: 'normal',
|
||||
lineHeight: 1.5,
|
||||
textIndent: '0',
|
||||
letterSpacing: '0',
|
||||
wordSpacing: '0',
|
||||
fontVariant: 'normal',
|
||||
verticalAlignment: 'top',
|
||||
padding: 'default',
|
||||
boxShadow: '0px 0px 0px 0px #00000090',
|
||||
borderRadius: '0',
|
||||
isScrollRequired: 'enabled',
|
||||
},
|
||||
},
|
||||
{
|
||||
componentName: 'Text',
|
||||
layout: {
|
||||
top: 160,
|
||||
left: 10,
|
||||
height: 30,
|
||||
},
|
||||
properties: ['text'],
|
||||
styles: [
|
||||
'textSize',
|
||||
'fontWeight',
|
||||
'fontStyle',
|
||||
'textColor',
|
||||
'isScrollRequired',
|
||||
'lineHeight',
|
||||
'textIndent',
|
||||
'textAlign',
|
||||
'verticalAlignment',
|
||||
'decoration',
|
||||
'transformation',
|
||||
'letterSpacing',
|
||||
'wordSpacing',
|
||||
'fontVariant',
|
||||
'backgroundColor',
|
||||
'borderColor',
|
||||
'borderRadius',
|
||||
'boxShadow',
|
||||
'padding',
|
||||
],
|
||||
defaultValue: {
|
||||
text: 'Age',
|
||||
fontWeight: 'normal',
|
||||
textSize: 14,
|
||||
textColor: '#000',
|
||||
backgroundColor: '#fff00000',
|
||||
textAlign: 'left',
|
||||
decoration: 'none',
|
||||
transformation: 'none',
|
||||
fontStyle: 'normal',
|
||||
lineHeight: 1.5,
|
||||
textIndent: '0',
|
||||
letterSpacing: '0',
|
||||
wordSpacing: '0',
|
||||
fontVariant: 'normal',
|
||||
verticalAlignment: 'top',
|
||||
padding: 'default',
|
||||
boxShadow: '0px 0px 0px 0px #00000090',
|
||||
borderRadius: '0',
|
||||
isScrollRequired: 'enabled',
|
||||
},
|
||||
},
|
||||
{
|
||||
componentName: 'TextInput',
|
||||
layout: {
|
||||
top: 120,
|
||||
left: 10,
|
||||
height: 30,
|
||||
width: 25,
|
||||
top: 20,
|
||||
left: 5,
|
||||
height: 40,
|
||||
width: 31,
|
||||
},
|
||||
properties: ['placeholder', 'label'],
|
||||
styles: ['alignment', 'width', 'auto', 'padding', 'direction'],
|
||||
defaultValue: {
|
||||
placeholder: 'Enter your name',
|
||||
label: '',
|
||||
label: 'Name',
|
||||
width: '{{60}}',
|
||||
direction: 'left',
|
||||
alignment: 'side',
|
||||
auto: '{{false}}',
|
||||
padding: 'default',
|
||||
},
|
||||
},
|
||||
{
|
||||
componentName: 'NumberInput',
|
||||
layout: {
|
||||
top: 190,
|
||||
left: 10,
|
||||
height: 30,
|
||||
width: 25,
|
||||
top: 80,
|
||||
left: 5,
|
||||
height: 40,
|
||||
width: 31,
|
||||
},
|
||||
properties: ['value', 'label'],
|
||||
properties: ['placeholder', 'label'],
|
||||
styles: ['alignment', 'width', 'auto', 'padding', 'direction'],
|
||||
defaultValue: {
|
||||
value: 24,
|
||||
label: '',
|
||||
placeholder: 'Age',
|
||||
label: 'Age',
|
||||
width: '{{60}}',
|
||||
direction: 'left',
|
||||
alignment: 'side',
|
||||
auto: '{{false}}',
|
||||
padding: 'default',
|
||||
},
|
||||
},
|
||||
{
|
||||
componentName: 'Button',
|
||||
componentName: 'TextInput',
|
||||
layout: {
|
||||
top: 240,
|
||||
left: 10,
|
||||
height: 30,
|
||||
width: 10,
|
||||
top: 140,
|
||||
left: 5,
|
||||
height: 40,
|
||||
width: 31,
|
||||
},
|
||||
properties: ['text'],
|
||||
properties: ['placeholder', 'label'],
|
||||
styles: ['alignment', 'width', 'auto', 'padding', 'direction'],
|
||||
defaultValue: {
|
||||
text: 'Submit',
|
||||
placeholder: 'Tomy',
|
||||
label: 'Pet name',
|
||||
width: '{{60}}',
|
||||
alignment: 'side',
|
||||
direction: 'left',
|
||||
auto: '{{false}}',
|
||||
padding: 'default',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
@ -276,6 +141,24 @@ export const formConfig = {
|
|||
},
|
||||
showHeader: { type: 'toggle', displayName: 'Header' },
|
||||
showFooter: { type: 'toggle', displayName: 'Footer' },
|
||||
headerHeight: {
|
||||
type: 'numberInput',
|
||||
displayName: 'Header height',
|
||||
isHidden: true,
|
||||
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
|
||||
},
|
||||
canvasHeight: {
|
||||
type: 'numberInput',
|
||||
displayName: 'Canvas height',
|
||||
isHidden: true,
|
||||
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
|
||||
},
|
||||
footerHeight: {
|
||||
type: 'numberInput',
|
||||
displayName: 'Footer height',
|
||||
isHidden: true,
|
||||
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
|
||||
},
|
||||
visibility: {
|
||||
type: 'toggle',
|
||||
displayName: 'Visibility',
|
||||
|
|
@ -294,6 +177,13 @@ export const formConfig = {
|
|||
defaultValue: false,
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
type: 'code',
|
||||
displayName: 'Tooltip',
|
||||
validation: { schema: { type: 'string' } },
|
||||
section: 'additionalActions',
|
||||
placeholder: 'Enter tooltip text',
|
||||
},
|
||||
},
|
||||
events: {
|
||||
onSubmit: { displayName: 'On submit' },
|
||||
|
|
@ -316,24 +206,8 @@ export const formConfig = {
|
|||
defaultValue: '#ffffffff',
|
||||
},
|
||||
},
|
||||
headerHeight: {
|
||||
type: 'code',
|
||||
displayName: 'Header height',
|
||||
validation: {
|
||||
schema: { type: 'string' },
|
||||
defaultValue: '80px',
|
||||
},
|
||||
},
|
||||
footerHeight: {
|
||||
type: 'code',
|
||||
displayName: 'Footer height',
|
||||
validation: {
|
||||
schema: { type: 'string' },
|
||||
defaultValue: '80px',
|
||||
},
|
||||
},
|
||||
backgroundColor: {
|
||||
type: 'color',
|
||||
type: 'colorSwatches',
|
||||
displayName: 'Background color',
|
||||
validation: {
|
||||
schema: { type: 'string' },
|
||||
|
|
@ -351,7 +225,7 @@ export const formConfig = {
|
|||
},
|
||||
},
|
||||
borderColor: {
|
||||
type: 'color',
|
||||
type: 'colorSwatches',
|
||||
displayName: 'Border color',
|
||||
validation: {
|
||||
schema: { type: 'string' },
|
||||
|
|
@ -403,18 +277,18 @@ export const formConfig = {
|
|||
value:
|
||||
"{{ {title: 'User registration form', properties: {firstname: {type: 'textinput',value: 'Maria',label:'First name', validation:{maxLength:6}, styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},lastname:{type: 'textinput',value: 'Doe', label:'Last name', styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},age:{type:'number', label:'Age'},}, submitButton: {value: 'Submit', styles: {backgroundColor: '#3a433b',borderColor:'#595959'}}} }}",
|
||||
},
|
||||
showHeader: { value: '{{false}}' },
|
||||
showFooter: { value: '{{false}}' },
|
||||
showHeader: { value: '{{true}}' },
|
||||
showFooter: { value: '{{true}}' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
headerHeight: { value: 60 },
|
||||
footerHeight: { value: 60 },
|
||||
},
|
||||
events: [],
|
||||
styles: {
|
||||
backgroundColor: { value: '#fff' },
|
||||
borderRadius: { value: '0' },
|
||||
borderColor: { value: '#fff' },
|
||||
headerHeight: { value: '60px' },
|
||||
footerHeight: { value: '60px' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -78,6 +78,29 @@ export const iconConfig = {
|
|||
},
|
||||
accordian: 'Icon',
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
accordian: 'Icon',
|
||||
},
|
||||
boxShadow: {
|
||||
type: 'boxShadow',
|
||||
displayName: 'Box shadow',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: '0px 0px 0px 0px #00000040',
|
||||
},
|
||||
accordian: 'Icon',
|
||||
},
|
||||
},
|
||||
exposedVariables: {},
|
||||
actions: [
|
||||
|
|
@ -116,6 +139,8 @@ export const iconConfig = {
|
|||
styles: {
|
||||
iconColor: { value: '#000' },
|
||||
iconAlign: { value: 'center' },
|
||||
padding: { value: 'default' },
|
||||
boxShadow: { value: '0px 0px 0px 0px #00000040' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -121,6 +121,12 @@ export const multiselectV2Config = {
|
|||
},
|
||||
accordian: 'Options',
|
||||
},
|
||||
showAllSelectedLabel: {
|
||||
type: 'toggle',
|
||||
displayName: 'Show "All items are selected"',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
accordian: 'Options',
|
||||
},
|
||||
optionsLoadingState: {
|
||||
type: 'toggle',
|
||||
displayName: 'Options loading state',
|
||||
|
|
@ -142,6 +148,18 @@ export const multiselectV2Config = {
|
|||
accordian: 'Options',
|
||||
isFxNotRequired: true,
|
||||
},
|
||||
showClearBtn: {
|
||||
type: 'toggle',
|
||||
displayName: 'Show clear selection button',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
section: 'additionalActions',
|
||||
},
|
||||
showSearchInput: {
|
||||
type: 'toggle',
|
||||
displayName: 'Show search in options',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
section: 'additionalActions',
|
||||
},
|
||||
loadingState: {
|
||||
type: 'toggle',
|
||||
displayName: 'Loading state',
|
||||
|
|
@ -327,6 +345,9 @@ export const multiselectV2Config = {
|
|||
optionsLoadingState: { value: '{{false}}' },
|
||||
sort: { value: 'asc' },
|
||||
placeholder: { value: 'Select the options' },
|
||||
showAllSelectedLabel: { value: '{{true}}' },
|
||||
showClearBtn: { value: '{{true}}' },
|
||||
showSearchInput: { value: '{{true}}' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
loadingState: { value: '{{false}}' },
|
||||
|
|
|
|||
|
|
@ -84,6 +84,19 @@ export const rangeSliderConfig = {
|
|||
defaultValue: true,
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
value: null,
|
||||
|
|
@ -111,6 +124,7 @@ export const rangeSliderConfig = {
|
|||
handleColor: { value: '' },
|
||||
trackColor: { value: '' },
|
||||
visibility: { value: '{{true}}' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -89,6 +89,19 @@ export const starratingConfig = {
|
|||
defaultValue: false,
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
value: 0,
|
||||
|
|
@ -112,6 +125,7 @@ export const starratingConfig = {
|
|||
labelColor: { value: '' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -38,6 +38,19 @@ export const tagsConfig = {
|
|||
defaultValue: true,
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
},
|
||||
exposedVariables: {},
|
||||
definition: {
|
||||
|
|
@ -54,6 +67,7 @@ export const tagsConfig = {
|
|||
events: [],
|
||||
styles: {
|
||||
visibility: { value: '{{true}}' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -126,6 +126,20 @@ export const toggleSwitchV2Config = {
|
|||
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } },
|
||||
accordian: 'switch',
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
accordian: 'switch',
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
value: false,
|
||||
|
|
@ -187,6 +201,7 @@ export const toggleSwitchV2Config = {
|
|||
handleColor: { value: '#FFFFFF' },
|
||||
alignment: { value: 'right' },
|
||||
boxShadow: { value: '0px 0px 0px 0px #00000090' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1250,6 +1250,7 @@ $border-radius: 4px;
|
|||
color: var(--slate12) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.data-source-exists {
|
||||
.cm-editor {
|
||||
border-radius: 0 4px 4px 0 !important;
|
||||
|
|
@ -1834,6 +1835,7 @@ $border-radius: 4px;
|
|||
|
||||
.cm-scroller {
|
||||
border-bottom-left-radius: 4px;
|
||||
overscroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1853,22 +1855,23 @@ $border-radius: 4px;
|
|||
margin-left: 32px !important;
|
||||
}
|
||||
|
||||
.tjdb-codhinter-wrapper{
|
||||
.codehinter-input{
|
||||
.cm-editor{
|
||||
.tjdb-codhinter-wrapper {
|
||||
.codehinter-input {
|
||||
.cm-editor {
|
||||
// height: 30px !important;
|
||||
min-height: 30px !important;
|
||||
max-height:100px !important;
|
||||
max-height: 100px !important;
|
||||
border-radius: 0 !important;
|
||||
border-right: 0 ;
|
||||
}
|
||||
border-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tjdb-limit-offset-codehinter{
|
||||
.cm-editor{
|
||||
|
||||
.tjdb-limit-offset-codehinter {
|
||||
.cm-editor {
|
||||
// height: 30px !important;
|
||||
min-height: 30px !important;
|
||||
max-height:100px !important;
|
||||
max-height: 100px !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1896,7 +1899,7 @@ $border-radius: 4px;
|
|||
margin-right: auto;
|
||||
|
||||
p {
|
||||
display: flex;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
|
|
@ -1917,8 +1920,13 @@ $border-radius: 4px;
|
|||
}
|
||||
|
||||
.restapi-key-value {
|
||||
.code-hinter-wrapper, .code-editor-basic-wrapper, .codehinter-container, .cm-codehinter, .code-editor-query-panel{
|
||||
height:100%;
|
||||
|
||||
.code-hinter-wrapper,
|
||||
.code-editor-basic-wrapper,
|
||||
.codehinter-container,
|
||||
.cm-codehinter,
|
||||
.code-editor-query-panel {
|
||||
height: 100%;
|
||||
max-height: 100px;
|
||||
}
|
||||
}
|
||||
|
|
@ -8280,6 +8280,10 @@ tbody {
|
|||
}
|
||||
}
|
||||
|
||||
.query-manager-btn-shortcut {
|
||||
color: var(--text-disabled) !important;
|
||||
}
|
||||
|
||||
.font-weight-500 {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
|
|
|||
19
frontend/src/_ui/Icon/solidIcons/Play01.jsx
Normal file
19
frontend/src/_ui/Icon/solidIcons/Play01.jsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import React from 'react';
|
||||
|
||||
const Play01 = ({ fill = '#6A727C', width = '24', className = '', viewBox = '0 0 24 24' }) => (
|
||||
<svg
|
||||
width={width}
|
||||
height={width}
|
||||
viewBox={viewBox}
|
||||
className={className}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19.5904 14.6487L7.43642 21.5939C5.40287 22.7559 2.87265 21.2875 2.87265 18.9454V5.05514C2.87265 2.71301 5.40287 1.24466 7.43642 2.40669L19.5904 9.35182C21.6397 10.5228 21.6397 13.4777 19.5904 14.6487Z"
|
||||
fill={fill}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default Play01;
|
||||
|
|
@ -87,6 +87,7 @@ import Pin from './Pin.jsx';
|
|||
import Unpin from './Unpin.jsx';
|
||||
import AlignRight from './AlignRight';
|
||||
import Play from './Play.jsx';
|
||||
import Play01 from './Play01.jsx';
|
||||
import Plus from './Plus.jsx';
|
||||
import Plus01 from './Plus01.jsx';
|
||||
import Reload from './Reload.jsx';
|
||||
|
|
@ -698,6 +699,8 @@ const Icon = (props) => {
|
|||
return <StudentIcon {...props} />;
|
||||
case 'ai-crown':
|
||||
return <AICrown {...props} />;
|
||||
case 'play01':
|
||||
return <Play01 {...props} />;
|
||||
default:
|
||||
return <Apps {...props} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import Select from 'react-select';
|
|||
import defaultStyles from './styles';
|
||||
|
||||
export const SelectComponent = ({ options = [], value, onChange, closeMenuOnSelect, darkMode, ...restProps }) => {
|
||||
const selectRef = React.useRef(null);
|
||||
const isDarkMode = darkMode ?? localStorage.getItem('darkMode') === 'true';
|
||||
const {
|
||||
isMulti = false,
|
||||
|
|
@ -22,6 +23,7 @@ export const SelectComponent = ({ options = [], value, onChange, closeMenuOnSele
|
|||
useCustomStyles = false,
|
||||
isDisabled = false,
|
||||
borderRadius,
|
||||
openMenuOnFocus = false,
|
||||
} = restProps;
|
||||
|
||||
const customStyles = useCustomStyles ? styles : defaultStyles(isDarkMode, width, height, styles, borderRadius);
|
||||
|
|
@ -56,6 +58,8 @@ export const SelectComponent = ({ options = [], value, onChange, closeMenuOnSele
|
|||
return (
|
||||
<Select
|
||||
{...restProps}
|
||||
ref={selectRef}
|
||||
selectRef={selectRef} // Exposed ref for custom components if needed
|
||||
isLoading={isLoading}
|
||||
isDisabled={isDisabled || isLoading}
|
||||
options={selectOptions}
|
||||
|
|
@ -64,6 +68,7 @@ export const SelectComponent = ({ options = [], value, onChange, closeMenuOnSele
|
|||
onChange={handleOnChange}
|
||||
placeholder={placeholder}
|
||||
styles={customStyles}
|
||||
openMenuOnFocus={openMenuOnFocus}
|
||||
formatOptionLabel={(option) => renderCustomOption(option)}
|
||||
menuPlacement={menuPlacement}
|
||||
maxMenuHeight={maxMenuHeight}
|
||||
|
|
|
|||
|
|
@ -133,10 +133,20 @@ const Button = forwardRef(
|
|||
const iconFillColor = !defaultButtonFillColour.includes(fill) && fill ? fill : getDefaultIconFillColor(variant);
|
||||
const Comp = asChild ? Slot : 'Button';
|
||||
const leadingIconElement = leadingIcon && (
|
||||
<SolidIcon name={leadingIcon} height={getIconSize(size)} width={getIconSize(size)} fill={iconFillColor} />
|
||||
<div
|
||||
className="tw-flex tw-justify-center tw-items-center"
|
||||
style={{ width: getIconSize(size), height: getIconSize(size) }}
|
||||
>
|
||||
<SolidIcon name={leadingIcon} fill={iconFillColor} />
|
||||
</div>
|
||||
);
|
||||
const trailingIconElement = trailingIcon && (
|
||||
<SolidIcon name={trailingIcon} height={getIconSize(size)} width={getIconSize(size)} fill={iconFillColor} />
|
||||
<div
|
||||
className="tw-flex tw-justify-center tw-items-center"
|
||||
style={{ width: getIconSize(size), height: getIconSize(size) }}
|
||||
>
|
||||
<SolidIcon name={trailingIcon} fill={iconFillColor} />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ export class UpdateVisibilityFrIconComponent1737039401111 implements MigrationIn
|
|||
const properties = component.properties;
|
||||
const styles = component.styles;
|
||||
const general = component.general;
|
||||
const generalStyles = component.generalStyles;
|
||||
|
||||
if (styles.visibility) {
|
||||
properties.visibility = styles.visibility;
|
||||
|
|
@ -41,6 +42,11 @@ export class UpdateVisibilityFrIconComponent1737039401111 implements MigrationIn
|
|||
delete general?.tooltip;
|
||||
}
|
||||
|
||||
if (generalStyles?.boxShadow) {
|
||||
styles.boxShadow = generalStyles?.boxShadow;
|
||||
delete generalStyles?.boxShadow;
|
||||
}
|
||||
|
||||
await entityManager.update(Component, component.id, {
|
||||
properties,
|
||||
styles,
|
||||
|
|
@ -49,5 +55,5 @@ export class UpdateVisibilityFrIconComponent1737039401111 implements MigrationIn
|
|||
}
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {}
|
||||
public async down(queryRunner: QueryRunner): Promise<void> { }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
import { Component } from 'src/entities/component.entity';
|
||||
|
||||
import { processDataInBatches } from '@helpers/migration.helper';
|
||||
import { EntityManager, MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class UpdateContainerHeaderProperty1744097765065 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const componentTypes = ['Container'];
|
||||
const batchSize = 100;
|
||||
const entityManager = queryRunner.manager;
|
||||
|
||||
for (const componentType of componentTypes) {
|
||||
await processDataInBatches(
|
||||
entityManager,
|
||||
async (entityManager: EntityManager) => {
|
||||
return await entityManager.find(Component, {
|
||||
where: { type: componentType },
|
||||
order: { createdAt: 'ASC' },
|
||||
});
|
||||
},
|
||||
async (entityManager: EntityManager, components: Component[]) => {
|
||||
await this.processUpdates(entityManager, components);
|
||||
},
|
||||
batchSize
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async processUpdates(entityManager: EntityManager, components: Component[]) {
|
||||
for (const component of components) {
|
||||
const properties = component.properties;
|
||||
const styles = component.styles;
|
||||
const general = component.general;
|
||||
|
||||
// Update showHeader property to false for old instances
|
||||
if (!properties.showHeader) {
|
||||
properties.showHeader = { value: '{{false}}' };
|
||||
}
|
||||
|
||||
// Update the modal component with the modified properties
|
||||
await entityManager.update(Component, component.id, {
|
||||
properties,
|
||||
styles,
|
||||
general,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {}
|
||||
}
|
||||
|
|
@ -131,6 +131,19 @@ export const buttonGroupConfig = {
|
|||
defaultValue: 'left',
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
selected: [1],
|
||||
|
|
@ -164,6 +177,7 @@ export const buttonGroupConfig = {
|
|||
disabledState: { value: '{{false}}' },
|
||||
selectedTextColor: { value: '#FFFFFF' },
|
||||
selectedBackgroundColor: { value: 'var(--primary-brand)' },
|
||||
padding: { value: 'default' },
|
||||
alignment: { value: 'left' },
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -126,6 +126,20 @@ export const checkboxConfig = {
|
|||
],
|
||||
accordian: 'label',
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
accordian: 'switch',
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
value: false,
|
||||
|
|
@ -189,6 +203,7 @@ export const checkboxConfig = {
|
|||
handleColor: { value: '#FFFFFF' },
|
||||
alignment: { value: 'right' },
|
||||
boxShadow: { value: '0px 0px 0px 0px #00000090' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
validation: {
|
||||
mandatory: { value: '{{false}}' },
|
||||
|
|
|
|||
|
|
@ -28,6 +28,19 @@ export const colorPickerConfig = {
|
|||
},
|
||||
styles: {
|
||||
visibility: { type: 'toggle', displayName: 'Visibility' },
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
selectedColorHex: '#000000',
|
||||
|
|
@ -47,6 +60,7 @@ export const colorPickerConfig = {
|
|||
events: [],
|
||||
styles: {
|
||||
visibility: { value: '{{true}}' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ export const containerConfig = {
|
|||
displayName: 'Container',
|
||||
description: 'Group components',
|
||||
defaultSize: {
|
||||
width: 5,
|
||||
width: 10,
|
||||
height: 200,
|
||||
},
|
||||
component: 'Container',
|
||||
|
|
@ -44,13 +44,19 @@ export const containerConfig = {
|
|||
displayName: 'Show header',
|
||||
validation: {
|
||||
schema: { type: 'boolean' },
|
||||
defaultValue: false,
|
||||
defaultValue: true,
|
||||
},
|
||||
},
|
||||
headerHeight: {
|
||||
type: 'numberInput',
|
||||
displayName: 'Header height',
|
||||
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
|
||||
},
|
||||
},
|
||||
defaultChildren: [
|
||||
{
|
||||
componentName: 'Text',
|
||||
slotName: 'header',
|
||||
layout: {
|
||||
top: 20,
|
||||
left: 1,
|
||||
|
|
@ -98,15 +104,6 @@ export const containerConfig = {
|
|||
},
|
||||
accordian: 'container',
|
||||
},
|
||||
headerHeight: {
|
||||
type: 'numberInput',
|
||||
displayName: 'Height',
|
||||
validation: {
|
||||
schema: { type: 'number' },
|
||||
defaultValue: 80,
|
||||
},
|
||||
accordian: 'header',
|
||||
},
|
||||
borderRadius: {
|
||||
type: 'numberInput',
|
||||
displayName: 'Border',
|
||||
|
|
@ -154,10 +151,11 @@ export const containerConfig = {
|
|||
showOnMobile: { value: '{{false}}' },
|
||||
},
|
||||
properties: {
|
||||
showHeader: { value: `{{false}}` },
|
||||
showHeader: { value: `{{true}}` },
|
||||
loadingState: { value: `{{false}}` },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
headerHeight: { value: `{{80}}` },
|
||||
},
|
||||
events: [],
|
||||
styles: {
|
||||
|
|
|
|||
|
|
@ -75,6 +75,18 @@ export const dropdownV2Config = {
|
|||
accordian: 'Options',
|
||||
isFxNotRequired: true,
|
||||
},
|
||||
showClearBtn: {
|
||||
type: 'toggle',
|
||||
displayName: 'Show clear selection button',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
section: 'additionalActions',
|
||||
},
|
||||
showSearchInput: {
|
||||
type: 'toggle',
|
||||
displayName: 'Show search in options',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
section: 'additionalActions',
|
||||
},
|
||||
loadingState: {
|
||||
type: 'toggle',
|
||||
displayName: 'Loading state',
|
||||
|
|
@ -314,6 +326,8 @@ export const dropdownV2Config = {
|
|||
optionsLoadingState: { value: '{{false}}' },
|
||||
sort: { value: 'asc' },
|
||||
placeholder: { value: 'Select an option' },
|
||||
showClearBtn: { value: '{{true}}' },
|
||||
showSearchInput: { value: '{{true}}' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
loadingState: { value: '{{false}}' },
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ export const formConfig = {
|
|||
description: 'Wrapper for multiple components',
|
||||
defaultSize: {
|
||||
width: 13,
|
||||
height: 480,
|
||||
height: 450,
|
||||
},
|
||||
defaultChildren: [
|
||||
{
|
||||
|
|
@ -19,8 +19,8 @@ export const formConfig = {
|
|||
accessorKey: 'text',
|
||||
styles: ['fontWeight', 'textSize', 'textColor'],
|
||||
defaultValue: {
|
||||
text: 'Form title',
|
||||
textSize: 20,
|
||||
text: 'Form',
|
||||
textSize: 16,
|
||||
textColor: '#000',
|
||||
},
|
||||
},
|
||||
|
|
@ -34,203 +34,68 @@ export const formConfig = {
|
|||
},
|
||||
properties: ['text'],
|
||||
defaultValue: {
|
||||
text: 'Button2',
|
||||
text: 'Submit',
|
||||
padding: 'none',
|
||||
},
|
||||
},
|
||||
{
|
||||
componentName: 'Text',
|
||||
layout: {
|
||||
top: 40,
|
||||
left: 10,
|
||||
height: 30,
|
||||
width: 17,
|
||||
},
|
||||
properties: ['text'],
|
||||
styles: [
|
||||
'textSize',
|
||||
'fontWeight',
|
||||
'fontStyle',
|
||||
'textColor',
|
||||
'isScrollRequired',
|
||||
'lineHeight',
|
||||
'textIndent',
|
||||
'textAlign',
|
||||
'verticalAlignment',
|
||||
'decoration',
|
||||
'transformation',
|
||||
'letterSpacing',
|
||||
'wordSpacing',
|
||||
'fontVariant',
|
||||
'backgroundColor',
|
||||
'borderColor',
|
||||
'borderRadius',
|
||||
'boxShadow',
|
||||
'padding',
|
||||
],
|
||||
defaultValue: {
|
||||
text: 'User Details',
|
||||
fontWeight: 'bold',
|
||||
textSize: 18,
|
||||
textColor: '#000',
|
||||
backgroundColor: '#fff00000',
|
||||
textAlign: 'left',
|
||||
decoration: 'none',
|
||||
transformation: 'none',
|
||||
fontStyle: 'normal',
|
||||
lineHeight: 1.5,
|
||||
textIndent: '0',
|
||||
letterSpacing: '0',
|
||||
wordSpacing: '0',
|
||||
fontVariant: 'normal',
|
||||
verticalAlignment: 'top',
|
||||
padding: 'default',
|
||||
boxShadow: '0px 0px 0px 0px #00000090',
|
||||
borderRadius: '0',
|
||||
isScrollRequired: 'enabled',
|
||||
},
|
||||
},
|
||||
{
|
||||
componentName: 'Text',
|
||||
layout: {
|
||||
top: 90,
|
||||
left: 10,
|
||||
height: 30,
|
||||
},
|
||||
properties: ['text'],
|
||||
styles: [
|
||||
'textSize',
|
||||
'fontWeight',
|
||||
'fontStyle',
|
||||
'textColor',
|
||||
'isScrollRequired',
|
||||
'lineHeight',
|
||||
'textIndent',
|
||||
'textAlign',
|
||||
'verticalAlignment',
|
||||
'decoration',
|
||||
'transformation',
|
||||
'letterSpacing',
|
||||
'wordSpacing',
|
||||
'fontVariant',
|
||||
'backgroundColor',
|
||||
'borderColor',
|
||||
'borderRadius',
|
||||
'boxShadow',
|
||||
'padding',
|
||||
],
|
||||
defaultValue: {
|
||||
text: 'Name',
|
||||
fontWeight: 'normal',
|
||||
textSize: 14,
|
||||
textColor: '#000',
|
||||
backgroundColor: '#fff00000',
|
||||
textAlign: 'left',
|
||||
decoration: 'none',
|
||||
transformation: 'none',
|
||||
fontStyle: 'normal',
|
||||
lineHeight: 1.5,
|
||||
textIndent: '0',
|
||||
letterSpacing: '0',
|
||||
wordSpacing: '0',
|
||||
fontVariant: 'normal',
|
||||
verticalAlignment: 'top',
|
||||
padding: 'default',
|
||||
boxShadow: '0px 0px 0px 0px #00000090',
|
||||
borderRadius: '0',
|
||||
isScrollRequired: 'enabled',
|
||||
},
|
||||
},
|
||||
{
|
||||
componentName: 'Text',
|
||||
layout: {
|
||||
top: 160,
|
||||
left: 10,
|
||||
height: 30,
|
||||
},
|
||||
properties: ['text'],
|
||||
styles: [
|
||||
'textSize',
|
||||
'fontWeight',
|
||||
'fontStyle',
|
||||
'textColor',
|
||||
'isScrollRequired',
|
||||
'lineHeight',
|
||||
'textIndent',
|
||||
'textAlign',
|
||||
'verticalAlignment',
|
||||
'decoration',
|
||||
'transformation',
|
||||
'letterSpacing',
|
||||
'wordSpacing',
|
||||
'fontVariant',
|
||||
'backgroundColor',
|
||||
'borderColor',
|
||||
'borderRadius',
|
||||
'boxShadow',
|
||||
'padding',
|
||||
],
|
||||
defaultValue: {
|
||||
text: 'Age',
|
||||
fontWeight: 'normal',
|
||||
textSize: 14,
|
||||
textColor: '#000',
|
||||
backgroundColor: '#fff00000',
|
||||
textAlign: 'left',
|
||||
decoration: 'none',
|
||||
transformation: 'none',
|
||||
fontStyle: 'normal',
|
||||
lineHeight: 1.5,
|
||||
textIndent: '0',
|
||||
letterSpacing: '0',
|
||||
wordSpacing: '0',
|
||||
fontVariant: 'normal',
|
||||
verticalAlignment: 'top',
|
||||
padding: 'default',
|
||||
boxShadow: '0px 0px 0px 0px #00000090',
|
||||
borderRadius: '0',
|
||||
isScrollRequired: 'enabled',
|
||||
},
|
||||
},
|
||||
{
|
||||
componentName: 'TextInput',
|
||||
layout: {
|
||||
top: 120,
|
||||
left: 10,
|
||||
height: 30,
|
||||
width: 25,
|
||||
top: 20,
|
||||
left: 5,
|
||||
height: 40,
|
||||
width: 31,
|
||||
},
|
||||
properties: ['placeholder', 'label'],
|
||||
styles: ['alignment', 'width', 'auto', 'padding', 'direction'],
|
||||
defaultValue: {
|
||||
placeholder: 'Enter your name',
|
||||
label: '',
|
||||
label: 'Name',
|
||||
width: '{{60}}',
|
||||
direction: 'left',
|
||||
alignment: 'side',
|
||||
auto: '{{false}}',
|
||||
padding: 'default',
|
||||
},
|
||||
},
|
||||
{
|
||||
componentName: 'NumberInput',
|
||||
layout: {
|
||||
top: 190,
|
||||
left: 10,
|
||||
height: 30,
|
||||
width: 25,
|
||||
top: 80,
|
||||
left: 5,
|
||||
height: 40,
|
||||
width: 31,
|
||||
},
|
||||
properties: ['value', 'label'],
|
||||
properties: ['placeholder', 'label'],
|
||||
styles: ['alignment', 'width', 'auto', 'padding', 'direction'],
|
||||
defaultValue: {
|
||||
value: 24,
|
||||
label: '',
|
||||
placeholder: 'Age',
|
||||
label: 'Age',
|
||||
width: '{{60}}',
|
||||
direction: 'left',
|
||||
alignment: 'side',
|
||||
auto: '{{false}}',
|
||||
padding: 'default',
|
||||
},
|
||||
},
|
||||
{
|
||||
componentName: 'Button',
|
||||
componentName: 'TextInput',
|
||||
layout: {
|
||||
top: 240,
|
||||
left: 10,
|
||||
height: 30,
|
||||
width: 10,
|
||||
top: 140,
|
||||
left: 5,
|
||||
height: 40,
|
||||
width: 31,
|
||||
},
|
||||
properties: ['text'],
|
||||
properties: ['placeholder', 'label'],
|
||||
styles: ['alignment', 'width', 'auto', 'padding', 'direction'],
|
||||
defaultValue: {
|
||||
text: 'Submit',
|
||||
placeholder: 'Tomy',
|
||||
label: 'Pet name',
|
||||
width: '{{60}}',
|
||||
alignment: 'side',
|
||||
direction: 'left',
|
||||
auto: '{{false}}',
|
||||
padding: 'default',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
@ -276,6 +141,24 @@ export const formConfig = {
|
|||
},
|
||||
showHeader: { type: 'toggle', displayName: 'Header' },
|
||||
showFooter: { type: 'toggle', displayName: 'Footer' },
|
||||
headerHeight: {
|
||||
type: 'numberInput',
|
||||
displayName: 'Header height',
|
||||
isHidden: true,
|
||||
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
|
||||
},
|
||||
canvasHeight: {
|
||||
type: 'numberInput',
|
||||
displayName: 'Canvas height',
|
||||
isHidden: true,
|
||||
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
|
||||
},
|
||||
footerHeight: {
|
||||
type: 'numberInput',
|
||||
displayName: 'Footer height',
|
||||
isHidden: true,
|
||||
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
|
||||
},
|
||||
visibility: {
|
||||
type: 'toggle',
|
||||
displayName: 'Visibility',
|
||||
|
|
@ -294,6 +177,13 @@ export const formConfig = {
|
|||
defaultValue: false,
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
type: 'code',
|
||||
displayName: 'Tooltip',
|
||||
validation: { schema: { type: 'string' } },
|
||||
section: 'additionalActions',
|
||||
placeholder: 'Enter tooltip text',
|
||||
},
|
||||
},
|
||||
events: {
|
||||
onSubmit: { displayName: 'On submit' },
|
||||
|
|
@ -316,22 +206,6 @@ export const formConfig = {
|
|||
defaultValue: '#ffffffff',
|
||||
},
|
||||
},
|
||||
headerHeight: {
|
||||
type: 'code',
|
||||
displayName: 'Header height',
|
||||
validation: {
|
||||
schema: { type: 'string' },
|
||||
defaultValue: '80px',
|
||||
},
|
||||
},
|
||||
footerHeight: {
|
||||
type: 'code',
|
||||
displayName: 'Footer height',
|
||||
validation: {
|
||||
schema: { type: 'string' },
|
||||
defaultValue: '80px',
|
||||
},
|
||||
},
|
||||
backgroundColor: {
|
||||
type: 'colorSwatches',
|
||||
displayName: 'Background color',
|
||||
|
|
@ -403,19 +277,18 @@ export const formConfig = {
|
|||
value:
|
||||
"{{ {title: 'User registration form', properties: {firstname: {type: 'textinput',value: 'Maria',label:'First name', validation:{maxLength:6}, styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},lastname:{type: 'textinput',value: 'Doe', label:'Last name', styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},age:{type:'number', label:'Age'},}, submitButton: {value: 'Submit', styles: {backgroundColor: '#3a433b',borderColor:'#595959'}}} }}",
|
||||
},
|
||||
buttonToSubmit: { value: '{{"none"}}' },
|
||||
showHeader: { value: '{{false}}' },
|
||||
showFooter: { value: '{{false}}' },
|
||||
showHeader: { value: '{{true}}' },
|
||||
showFooter: { value: '{{true}}' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
headerHeight: { value: 60 },
|
||||
footerHeight: { value: 60 },
|
||||
},
|
||||
events: [],
|
||||
styles: {
|
||||
backgroundColor: { value: '#fff' },
|
||||
borderRadius: { value: '0' },
|
||||
borderColor: { value: '#fff' },
|
||||
headerHeight: { value: '60px' },
|
||||
footerHeight: { value: '60px' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -78,6 +78,29 @@ export const iconConfig = {
|
|||
},
|
||||
accordian: 'Icon',
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
accordian: 'Icon',
|
||||
},
|
||||
boxShadow: {
|
||||
type: 'boxShadow',
|
||||
displayName: 'Box shadow',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: '0px 0px 0px 0px #00000040',
|
||||
},
|
||||
accordian: 'Icon',
|
||||
},
|
||||
},
|
||||
exposedVariables: {},
|
||||
actions: [
|
||||
|
|
@ -116,6 +139,7 @@ export const iconConfig = {
|
|||
styles: {
|
||||
iconColor: { value: '#000' },
|
||||
iconAlign: { value: 'center' },
|
||||
},
|
||||
padding: { value: 'default' },
|
||||
boxShadow: { value: '0px 0px 0px 0px #00000040' },
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -121,6 +121,12 @@ export const multiselectV2Config = {
|
|||
},
|
||||
accordian: 'Options',
|
||||
},
|
||||
showAllSelectedLabel: {
|
||||
type: 'toggle',
|
||||
displayName: 'Show "All items are selected"',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
accordian: 'Options',
|
||||
},
|
||||
optionsLoadingState: {
|
||||
type: 'toggle',
|
||||
displayName: 'Options loading state',
|
||||
|
|
@ -142,6 +148,18 @@ export const multiselectV2Config = {
|
|||
accordian: 'Options',
|
||||
isFxNotRequired: true,
|
||||
},
|
||||
showClearBtn: {
|
||||
type: 'toggle',
|
||||
displayName: 'Show clear selection button',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
section: 'additionalActions',
|
||||
},
|
||||
showSearchInput: {
|
||||
type: 'toggle',
|
||||
displayName: 'Show search in options',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
section: 'additionalActions',
|
||||
},
|
||||
loadingState: {
|
||||
type: 'toggle',
|
||||
displayName: 'Loading state',
|
||||
|
|
@ -327,6 +345,9 @@ export const multiselectV2Config = {
|
|||
optionsLoadingState: { value: '{{false}}' },
|
||||
sort: { value: 'asc' },
|
||||
placeholder: { value: 'Select the options' },
|
||||
showAllSelectedLabel: { value: '{{true}}' },
|
||||
showClearBtn: { value: '{{true}}' },
|
||||
showSearchInput: { value: '{{true}}' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
loadingState: { value: '{{false}}' },
|
||||
|
|
|
|||
|
|
@ -84,6 +84,19 @@ export const rangeSliderConfig = {
|
|||
defaultValue: true,
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
value: null,
|
||||
|
|
@ -111,6 +124,7 @@ export const rangeSliderConfig = {
|
|||
handleColor: { value: '' },
|
||||
trackColor: { value: 'var(--primary-brand)' },
|
||||
visibility: { value: '{{true}}' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -89,6 +89,19 @@ export const starratingConfig = {
|
|||
defaultValue: false,
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
value: 0,
|
||||
|
|
@ -112,6 +125,7 @@ export const starratingConfig = {
|
|||
labelColor: { value: '' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -38,6 +38,19 @@ export const tagsConfig = {
|
|||
defaultValue: true,
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
alignment: {
|
||||
type: 'alignButtons',
|
||||
displayName: 'Alignment',
|
||||
|
|
@ -62,6 +75,7 @@ export const tagsConfig = {
|
|||
events: [],
|
||||
styles: {
|
||||
visibility: { value: '{{true}}' },
|
||||
padding: { value: 'default' },
|
||||
alignment: { value: 'left' },
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -126,6 +126,20 @@ export const toggleSwitchV2Config = {
|
|||
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } },
|
||||
accordian: 'switch',
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
accordian: 'switch',
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
value: false,
|
||||
|
|
@ -187,6 +201,7 @@ export const toggleSwitchV2Config = {
|
|||
handleColor: { value: '#FFFFFF' },
|
||||
alignment: { value: 'right' },
|
||||
boxShadow: { value: '0px 0px 0px 0px #00000090' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue