Merge branch 'develop' into user-fixes
This commit is contained in:
commit
6077814823
|
@ -162,6 +162,7 @@
|
||||||
"translation"
|
"translation"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
"login": "mslourens",
|
"login": "mslourens",
|
||||||
"name": "Maurits Lourens",
|
"name": "Maurits Lourens",
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/1907152?v=4",
|
"avatar_url": "https://avatars.githubusercontent.com/u/1907152?v=4",
|
||||||
|
|
|
@ -69,6 +69,28 @@ jobs:
|
||||||
env:
|
env:
|
||||||
KUBECONFIG_FILE: '${{ secrets.RELEASE_KUBECONFIG }}'
|
KUBECONFIG_FILE: '${{ secrets.RELEASE_KUBECONFIG }}'
|
||||||
|
|
||||||
|
- name: Re roll app-service
|
||||||
|
uses: actions-hub/kubectl@master
|
||||||
|
env:
|
||||||
|
KUBE_CONFIG: ${{ secrets.RELEASE_KUBECONFIG_BASE64 }}
|
||||||
|
with:
|
||||||
|
args: rollout restart deployment app-service -n budibase
|
||||||
|
|
||||||
|
- name: Re roll proxy-service
|
||||||
|
uses: actions-hub/kubectl@master
|
||||||
|
env:
|
||||||
|
KUBE_CONFIG: ${{ secrets.RELEASE_KUBECONFIG_BASE64 }}
|
||||||
|
with:
|
||||||
|
args: rollout restart deployment proxy-service -n budibase
|
||||||
|
|
||||||
|
- name: Re roll worker-service
|
||||||
|
uses: actions-hub/kubectl@master
|
||||||
|
env:
|
||||||
|
KUBE_CONFIG: ${{ secrets.RELEASE_KUBECONFIG_BASE64 }}
|
||||||
|
with:
|
||||||
|
args: rollout restart deployment worker-service -n budibase
|
||||||
|
|
||||||
|
|
||||||
- name: Discord Webhook Action
|
- name: Discord Webhook Action
|
||||||
uses: tsickert/discord-webhook@v4.0.0
|
uses: tsickert/discord-webhook@v4.0.0
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -18,8 +18,9 @@ on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Posthog token used by ui at build time
|
# Posthog token used by ui at build time
|
||||||
POSTHOG_TOKEN: phc_uDYOfnFt6wAbBAXkC6STjcrTpAFiWIhqgFcsC1UVO5F
|
# disable unless needed for testing
|
||||||
|
# POSTHOG_TOKEN: phc_uDYOfnFt6wAbBAXkC6STjcrTpAFiWIhqgFcsC1UVO5F
|
||||||
INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }}
|
INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }}
|
||||||
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
||||||
FEATURE_PREVIEW_URL: https://budirelease.live
|
FEATURE_PREVIEW_URL: https://budirelease.live
|
||||||
|
@ -119,6 +120,27 @@ jobs:
|
||||||
]
|
]
|
||||||
env:
|
env:
|
||||||
KUBECONFIG_FILE: '${{ secrets.RELEASE_KUBECONFIG }}'
|
KUBECONFIG_FILE: '${{ secrets.RELEASE_KUBECONFIG }}'
|
||||||
|
|
||||||
|
- name: Re roll app-service
|
||||||
|
uses: actions-hub/kubectl@master
|
||||||
|
env:
|
||||||
|
KUBE_CONFIG: ${{ secrets.RELEASE_KUBECONFIG_BASE64 }}
|
||||||
|
with:
|
||||||
|
args: rollout restart deployment app-service -n budibase
|
||||||
|
|
||||||
|
- name: Re roll proxy-service
|
||||||
|
uses: actions-hub/kubectl@master
|
||||||
|
env:
|
||||||
|
KUBE_CONFIG: ${{ secrets.RELEASE_KUBECONFIG_BASE64 }}
|
||||||
|
with:
|
||||||
|
args: rollout restart deployment proxy-service -n budibase
|
||||||
|
|
||||||
|
- name: Re roll worker-service
|
||||||
|
uses: actions-hub/kubectl@master
|
||||||
|
env:
|
||||||
|
KUBE_CONFIG: ${{ secrets.RELEASE_KUBECONFIG_BASE64 }}
|
||||||
|
with:
|
||||||
|
args: rollout restart deployment worker-service -n budibase
|
||||||
|
|
||||||
- name: Discord Webhook Action
|
- name: Discord Webhook Action
|
||||||
uses: tsickert/discord-webhook@v4.0.0
|
uses: tsickert/discord-webhook@v4.0.0
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
name: Budibase Smoke Test
|
name: Budibase Nightly Tests
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
@ -6,7 +6,7 @@ on:
|
||||||
- cron: "0 5 * * *" # every day at 5AM
|
- cron: "0 5 * * *" # every day at 5AM
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
nightly:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
@ -43,6 +43,18 @@ jobs:
|
||||||
name: Test Reports
|
name: Test Reports
|
||||||
path: packages/builder/cypress/reports/testReport.html
|
path: packages/builder/cypress/reports/testReport.html
|
||||||
|
|
||||||
|
# TODO: enable once running in QA test env
|
||||||
|
# - name: Configure AWS Credentials
|
||||||
|
# uses: aws-actions/configure-aws-credentials@v1
|
||||||
|
# with:
|
||||||
|
# aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
|
# aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
|
# aws-region: eu-west-1
|
||||||
|
|
||||||
|
# - name: Upload test results HTML
|
||||||
|
# uses: aws-actions/configure-aws-credentials@v1
|
||||||
|
# run: aws s3 cp packages/builder/cypress/reports/testReport.html s3://{{ secrets.BUDI_QA_REPORTS_BUCKET_NAME }}/$GITHUB_RUN_ID/index.html
|
||||||
|
|
||||||
- name: Cypress Discord Notify
|
- name: Cypress Discord Notify
|
||||||
run: yarn test:e2e:ci:notify
|
run: yarn test:e2e:ci:notify
|
||||||
env:
|
env:
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"singleQuote": false,
|
"singleQuote": false,
|
||||||
"trailingComma": "es5",
|
"trailingComma": "es5",
|
||||||
"arrowParens": "avoid",
|
"arrowParens": "avoid",
|
||||||
"jsxBracketSameLine": false,
|
"bracketSameLine": false,
|
||||||
"plugins": ["prettier-plugin-svelte"],
|
"plugins": ["prettier-plugin-svelte"],
|
||||||
"svelteSortOrder": "options-scripts-markup-styles"
|
"svelteSortOrder": "options-scripts-markup-styles"
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,6 +134,18 @@ spec:
|
||||||
- name: NODE_DEBUG
|
- name: NODE_DEBUG
|
||||||
value: {{ .Values.services.apps.nodeDebug | quote }}
|
value: {{ .Values.services.apps.nodeDebug | quote }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{ if .Values.globals.elasticApmEnabled }}
|
||||||
|
- name: ELASTIC_APM_ENABLED
|
||||||
|
value: {{ .Values.globals.elasticApmEnabled | quote }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Values.globals.elasticApmSecretToken }}
|
||||||
|
- name: ELASTIC_APM_SECRET_TOKEN
|
||||||
|
value: {{ .Values.globals.elasticApmSecretToken | quote }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Values.globals.elasticApmServerUrl }}
|
||||||
|
- name: ELASTIC_APM_SERVER_URL
|
||||||
|
value: {{ .Values.globals.elasticApmServerUrl | quote }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
image: budibase/apps:{{ .Values.globals.appVersion }}
|
image: budibase/apps:{{ .Values.globals.appVersion }}
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
|
|
|
@ -27,6 +27,8 @@ spec:
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- env:
|
- env:
|
||||||
|
- name: BUDIBASE_ENVIRONMENT
|
||||||
|
value: {{ .Values.globals.budibaseEnv }}
|
||||||
- name: DEPLOYMENT_ENVIRONMENT
|
- name: DEPLOYMENT_ENVIRONMENT
|
||||||
value: "kubernetes"
|
value: "kubernetes"
|
||||||
- name: CLUSTER_PORT
|
- name: CLUSTER_PORT
|
||||||
|
@ -125,6 +127,19 @@ spec:
|
||||||
value: {{ .Values.globals.google.secret | quote }}
|
value: {{ .Values.globals.google.secret | quote }}
|
||||||
- name: TENANT_FEATURE_FLAGS
|
- name: TENANT_FEATURE_FLAGS
|
||||||
value: {{ .Values.globals.tenantFeatureFlags | quote }}
|
value: {{ .Values.globals.tenantFeatureFlags | quote }}
|
||||||
|
{{ if .Values.globals.elasticApmEnabled }}
|
||||||
|
- name: ELASTIC_APM_ENABLED
|
||||||
|
value: {{ .Values.globals.elasticApmEnabled | quote }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Values.globals.elasticApmSecretToken }}
|
||||||
|
- name: ELASTIC_APM_SECRET_TOKEN
|
||||||
|
value: {{ .Values.globals.elasticApmSecretToken | quote }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Values.globals.elasticApmServerUrl }}
|
||||||
|
- name: ELASTIC_APM_SERVER_URL
|
||||||
|
value: {{ .Values.globals.elasticApmServerUrl | quote }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
image: budibase/worker:{{ .Values.globals.appVersion }}
|
image: budibase/worker:{{ .Values.globals.appVersion }}
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
|
|
|
@ -114,6 +114,10 @@ globals:
|
||||||
smtp:
|
smtp:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
|
# elasticApmEnabled:
|
||||||
|
# elasticApmSecretToken:
|
||||||
|
# elasticApmServerUrl:
|
||||||
|
|
||||||
services:
|
services:
|
||||||
budibaseVersion: latest
|
budibaseVersion: latest
|
||||||
dns: cluster.local
|
dns: cluster.local
|
||||||
|
|
|
@ -15,7 +15,10 @@ http {
|
||||||
|
|
||||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
'$status $body_bytes_sent "$http_referer" '
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
'"$http_user_agent" "$http_x_forwarded_for" '
|
||||||
|
'response_time=$upstream_response_time proxy_host=$proxy_host upstream_addr=$upstream_addr';
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log main;
|
||||||
|
|
||||||
map $http_upgrade $connection_upgrade {
|
map $http_upgrade $connection_upgrade {
|
||||||
default "upgrade";
|
default "upgrade";
|
||||||
|
@ -62,6 +65,10 @@ http {
|
||||||
proxy_pass http://{{ address }}:4001;
|
proxy_pass http://{{ address }}:4001;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /preview {
|
||||||
|
proxy_pass http://{{ address }}:4001;
|
||||||
|
}
|
||||||
|
|
||||||
location /builder {
|
location /builder {
|
||||||
proxy_pass http://{{ address }}:3000;
|
proxy_pass http://{{ address }}:3000;
|
||||||
rewrite ^/builder(.*)$ /builder/$1 break;
|
rewrite ^/builder(.*)$ /builder/$1 break;
|
||||||
|
|
|
@ -33,7 +33,10 @@ http {
|
||||||
|
|
||||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
'$status $body_bytes_sent "$http_referer" '
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
'"$http_user_agent" "$http_x_forwarded_for" '
|
||||||
|
'response_time=$upstream_response_time proxy_host=$proxy_host upstream_addr=$upstream_addr';
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log main;
|
||||||
|
|
||||||
map $http_upgrade $connection_upgrade {
|
map $http_upgrade $connection_upgrade {
|
||||||
default "upgrade";
|
default "upgrade";
|
||||||
|
@ -85,6 +88,10 @@ http {
|
||||||
proxy_pass http://$apps:4002;
|
proxy_pass http://$apps:4002;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /preview {
|
||||||
|
proxy_pass http://$apps:4002;
|
||||||
|
}
|
||||||
|
|
||||||
location = / {
|
location = / {
|
||||||
proxy_pass http://$apps:4002;
|
proxy_pass http://$apps:4002;
|
||||||
}
|
}
|
||||||
|
@ -94,6 +101,7 @@ http {
|
||||||
proxy_pass http://$watchtower:8080;
|
proxy_pass http://$watchtower:8080;
|
||||||
}
|
}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
location ~ ^/(builder|app_) {
|
location ~ ^/(builder|app_) {
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Connection $connection_upgrade;
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
|
|
@ -3,15 +3,18 @@
|
||||||
echo ${TARGETBUILD} > /buildtarget.txt
|
echo ${TARGETBUILD} > /buildtarget.txt
|
||||||
if [[ "${TARGETBUILD}" = "aas" ]]; then
|
if [[ "${TARGETBUILD}" = "aas" ]]; then
|
||||||
# Azure AppService uses /home for persisent data & SSH on port 2222
|
# Azure AppService uses /home for persisent data & SSH on port 2222
|
||||||
mkdir -p /home/{search,minio,couch}
|
DATA_DIR=/home
|
||||||
mkdir -p /home/couch/{dbs,views}
|
mkdir -p $DATA_DIR/{search,minio,couchdb}
|
||||||
chown -R couchdb:couchdb /home/couch/
|
mkdir -p $DATA_DIR/couchdb/{dbs,views}
|
||||||
|
chown -R couchdb:couchdb $DATA_DIR/couchdb/
|
||||||
apt update
|
apt update
|
||||||
apt-get install -y openssh-server
|
apt-get install -y openssh-server
|
||||||
sed -i 's#dir=/opt/couchdb/data/search#dir=/home/search#' /opt/clouseau/clouseau.ini
|
|
||||||
sed -i 's#/minio/minio server /minio &#/minio/minio server /home/minio &#' /runner.sh
|
|
||||||
sed -i 's#database_dir = ./data#database_dir = /home/couch/dbs#' /opt/couchdb/etc/default.ini
|
|
||||||
sed -i 's#view_index_dir = ./data#view_index_dir = /home/couch/views#' /opt/couchdb/etc/default.ini
|
|
||||||
sed -i "s/#Port 22/Port 2222/" /etc/ssh/sshd_config
|
sed -i "s/#Port 22/Port 2222/" /etc/ssh/sshd_config
|
||||||
/etc/init.d/ssh restart
|
/etc/init.d/ssh restart
|
||||||
fi
|
sed -i "s#DATA_DIR#/home#g" /opt/clouseau/clouseau.ini
|
||||||
|
sed -i "s#DATA_DIR#/home#g" /opt/couchdb/etc/local.ini
|
||||||
|
else
|
||||||
|
sed -i "s#DATA_DIR#/data#g" /opt/clouseau/clouseau.ini
|
||||||
|
sed -i "s#DATA_DIR#/data#g" /opt/couchdb/etc/local.ini
|
||||||
|
|
||||||
|
fi
|
|
@ -20,10 +20,10 @@ RUN node /pinVersions.js && yarn && yarn build && /cleanup.sh
|
||||||
|
|
||||||
FROM couchdb:3.2.1
|
FROM couchdb:3.2.1
|
||||||
# TARGETARCH can be amd64 or arm e.g. docker build --build-arg TARGETARCH=amd64
|
# TARGETARCH can be amd64 or arm e.g. docker build --build-arg TARGETARCH=amd64
|
||||||
ARG TARGETARCH amd64
|
ARG TARGETARCH=amd64
|
||||||
#TARGETBUILD can be set to single (for single docker image) or aas (for azure app service)
|
#TARGETBUILD can be set to single (for single docker image) or aas (for azure app service)
|
||||||
# e.g. docker build --build-arg TARGETBUILD=aas ....
|
# e.g. docker build --build-arg TARGETBUILD=aas ....
|
||||||
ARG TARGETBUILD single
|
ARG TARGETBUILD=single
|
||||||
ENV TARGETBUILD $TARGETBUILD
|
ENV TARGETBUILD $TARGETBUILD
|
||||||
|
|
||||||
COPY --from=build /app /app
|
COPY --from=build /app /app
|
||||||
|
@ -35,6 +35,7 @@ ENV \
|
||||||
BUDIBASE_ENVIRONMENT=PRODUCTION \
|
BUDIBASE_ENVIRONMENT=PRODUCTION \
|
||||||
CLUSTER_PORT=80 \
|
CLUSTER_PORT=80 \
|
||||||
# CUSTOM_DOMAIN=budi001.custom.com \
|
# CUSTOM_DOMAIN=budi001.custom.com \
|
||||||
|
DATA_DIR=/data \
|
||||||
DEPLOYMENT_ENVIRONMENT=docker \
|
DEPLOYMENT_ENVIRONMENT=docker \
|
||||||
MINIO_URL=http://localhost:9000 \
|
MINIO_URL=http://localhost:9000 \
|
||||||
POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU \
|
POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU \
|
||||||
|
@ -114,6 +115,7 @@ RUN chmod +x ./healthcheck.sh
|
||||||
ADD hosting/scripts/build-target-paths.sh .
|
ADD hosting/scripts/build-target-paths.sh .
|
||||||
RUN chmod +x ./build-target-paths.sh
|
RUN chmod +x ./build-target-paths.sh
|
||||||
|
|
||||||
|
# Script below sets the path for storing data based on $DATA_DIR
|
||||||
# For Azure App Service install SSH & point data locations to /home
|
# For Azure App Service install SSH & point data locations to /home
|
||||||
RUN /build-target-paths.sh
|
RUN /build-target-paths.sh
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ name=clouseau@127.0.0.1
|
||||||
cookie=monster
|
cookie=monster
|
||||||
|
|
||||||
; the path where you would like to store the search index files
|
; the path where you would like to store the search index files
|
||||||
dir=/data/search
|
dir=DATA_DIR/search
|
||||||
|
|
||||||
; the number of search indexes that can be open simultaneously
|
; the number of search indexes that can be open simultaneously
|
||||||
max_indexes_open=500
|
max_indexes_open=500
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
; CouchDB Configuration Settings
|
; CouchDB Configuration Settings
|
||||||
|
|
||||||
[couchdb]
|
[couchdb]
|
||||||
database_dir = /data/couch/dbs
|
database_dir = DATA_DIR/couchdb/dbs
|
||||||
view_index_dir = /data/couch/views
|
view_index_dir = DATA_DIR/couchdb/views
|
||||||
|
|
|
@ -3,6 +3,11 @@ healthy=true
|
||||||
|
|
||||||
if [ -f "/data/.env" ]; then
|
if [ -f "/data/.env" ]; then
|
||||||
export $(cat /data/.env | xargs)
|
export $(cat /data/.env | xargs)
|
||||||
|
elif [ -f "/home/.env" ]; then
|
||||||
|
export $(cat /home/.env | xargs)
|
||||||
|
else
|
||||||
|
echo "No .env file found"
|
||||||
|
healthy=false
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $(curl -Lfk -s -w "%{http_code}\n" http://localhost/ -o /dev/null) -ne 200 ]]; then
|
if [[ $(curl -Lfk -s -w "%{http_code}\n" http://localhost/ -o /dev/null) -ne 200 ]]; then
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
declare -a ENV_VARS=("COUCHDB_USER" "COUCHDB_PASSWORD" "MINIO_ACCESS_KEY" "MINIO_SECRET_KEY" "INTERNAL_API_KEY" "JWT_SECRET" "REDIS_PASSWORD")
|
declare -a ENV_VARS=("COUCHDB_USER" "COUCHDB_PASSWORD" "DATA_DIR" "MINIO_ACCESS_KEY" "MINIO_SECRET_KEY" "INTERNAL_API_KEY" "JWT_SECRET" "REDIS_PASSWORD")
|
||||||
if [ -f "/data/.env" ]; then
|
|
||||||
export $(cat /data/.env | xargs)
|
# Azure App Service customisations
|
||||||
|
if [[ "${TARGETBUILD}" = "aas" ]]; then
|
||||||
|
DATA_DIR=/home
|
||||||
|
/etc/init.d/ssh start
|
||||||
|
else
|
||||||
|
DATA_DIR=${DATA_DIR:-/data}
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "${DATA_DIR}/.env" ]; then
|
||||||
|
export $(cat ${DATA_DIR}/.env | xargs)
|
||||||
fi
|
fi
|
||||||
# first randomise any unset environment variables
|
# first randomise any unset environment variables
|
||||||
for ENV_VAR in "${ENV_VARS[@]}"
|
for ENV_VAR in "${ENV_VARS[@]}"
|
||||||
|
@ -14,21 +23,26 @@ done
|
||||||
if [[ -z "${COUCH_DB_URL}" ]]; then
|
if [[ -z "${COUCH_DB_URL}" ]]; then
|
||||||
export COUCH_DB_URL=http://$COUCHDB_USER:$COUCHDB_PASSWORD@localhost:5984
|
export COUCH_DB_URL=http://$COUCHDB_USER:$COUCHDB_PASSWORD@localhost:5984
|
||||||
fi
|
fi
|
||||||
if [ ! -f "/data/.env" ]; then
|
if [ ! -f "${DATA_DIR}/.env" ]; then
|
||||||
touch /data/.env
|
touch ${DATA_DIR}/.env
|
||||||
for ENV_VAR in "${ENV_VARS[@]}"
|
for ENV_VAR in "${ENV_VARS[@]}"
|
||||||
do
|
do
|
||||||
temp=$(eval "echo \$$ENV_VAR")
|
temp=$(eval "echo \$$ENV_VAR")
|
||||||
echo "$ENV_VAR=$temp" >> /data/.env
|
echo "$ENV_VAR=$temp" >> ${DATA_DIR}/.env
|
||||||
done
|
done
|
||||||
|
echo "COUCH_DB_URL=${COUCH_DB_URL}" >> ${DATA_DIR}/.env
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
export COUCH_DB_URL=http://$COUCHDB_USER:$COUCHDB_PASSWORD@localhost:5984
|
||||||
|
|
||||||
# make these directories in runner, incase of mount
|
# make these directories in runner, incase of mount
|
||||||
mkdir -p /data/couch/{dbs,views} /home/couch/{dbs,views}
|
mkdir -p ${DATA_DIR}/couchdb/{dbs,views}
|
||||||
chown -R couchdb:couchdb /data/couch /home/couch
|
mkdir -p ${DATA_DIR}/minio
|
||||||
|
mkdir -p ${DATA_DIR}/search
|
||||||
|
chown -R couchdb:couchdb ${DATA_DIR}/couchdb
|
||||||
redis-server --requirepass $REDIS_PASSWORD &
|
redis-server --requirepass $REDIS_PASSWORD &
|
||||||
/opt/clouseau/bin/clouseau &
|
/opt/clouseau/bin/clouseau &
|
||||||
/minio/minio server /data/minio &
|
/minio/minio server ${DATA_DIR}/minio &
|
||||||
/docker-entrypoint.sh /opt/couchdb/bin/couchdb &
|
/docker-entrypoint.sh /opt/couchdb/bin/couchdb &
|
||||||
/etc/init.d/nginx restart
|
/etc/init.d/nginx restart
|
||||||
if [[ ! -z "${CUSTOM_DOMAIN}" ]]; then
|
if [[ ! -z "${CUSTOM_DOMAIN}" ]]; then
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "1.2.47",
|
"version": "1.2.57",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/backend-core",
|
"name": "@budibase/backend-core",
|
||||||
"version": "1.2.47",
|
"version": "1.2.57",
|
||||||
"description": "Budibase backend core libraries used in server and worker",
|
"description": "Budibase backend core libraries used in server and worker",
|
||||||
"main": "dist/src/index.js",
|
"main": "dist/src/index.js",
|
||||||
"types": "dist/src/index.d.ts",
|
"types": "dist/src/index.d.ts",
|
||||||
|
@ -20,13 +20,14 @@
|
||||||
"test:watch": "jest --watchAll"
|
"test:watch": "jest --watchAll"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/types": "^1.2.47",
|
"@budibase/types": "^1.2.57",
|
||||||
"@techpass/passport-openidconnect": "0.3.2",
|
"@techpass/passport-openidconnect": "0.3.2",
|
||||||
"aws-sdk": "2.1030.0",
|
"aws-sdk": "2.1030.0",
|
||||||
"bcrypt": "5.0.1",
|
"bcrypt": "5.0.1",
|
||||||
"dotenv": "16.0.1",
|
"dotenv": "16.0.1",
|
||||||
"emitter-listener": "1.1.2",
|
"emitter-listener": "1.1.2",
|
||||||
"ioredis": "4.28.0",
|
"ioredis": "4.28.0",
|
||||||
|
"joi": "17.6.0",
|
||||||
"jsonwebtoken": "8.5.1",
|
"jsonwebtoken": "8.5.1",
|
||||||
"koa-passport": "4.1.4",
|
"koa-passport": "4.1.4",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
|
|
|
@ -233,6 +233,10 @@ export async function getAllApps({ dev, all, idsOnly, efficient }: any = {}) {
|
||||||
}
|
}
|
||||||
let dbs = await getAllDbs({ efficient })
|
let dbs = await getAllDbs({ efficient })
|
||||||
const appDbNames = dbs.filter((dbName: any) => {
|
const appDbNames = dbs.filter((dbName: any) => {
|
||||||
|
if (env.isTest() && !dbName) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
const split = dbName.split(SEPARATOR)
|
const split = dbName.split(SEPARATOR)
|
||||||
// it is an app, check the tenantId
|
// it is an app, check the tenantId
|
||||||
if (split[0] === DocumentType.APP) {
|
if (split[0] === DocumentType.APP) {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
const Joi = require("joi")
|
||||||
|
|
||||||
function validate(schema, property) {
|
function validate(schema, property) {
|
||||||
// Return a Koa middleware function
|
// Return a Koa middleware function
|
||||||
return (ctx, next) => {
|
return (ctx, next) => {
|
||||||
|
@ -10,6 +12,12 @@ function validate(schema, property) {
|
||||||
} else if (ctx.request[property] != null) {
|
} else if (ctx.request[property] != null) {
|
||||||
params = ctx.request[property]
|
params = ctx.request[property]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
schema = schema.append({
|
||||||
|
createdAt: Joi.any().optional(),
|
||||||
|
updatedAt: Joi.any().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
const { error } = schema.validate(params)
|
const { error } = schema.validate(params)
|
||||||
if (error) {
|
if (error) {
|
||||||
ctx.throw(400, `Invalid ${property} - ${error.message}`)
|
ctx.throw(400, `Invalid ${property} - ${error.message}`)
|
||||||
|
|
|
@ -66,15 +66,13 @@ const PUBLIC_BUCKETS = [ObjectStoreBuckets.APPS, ObjectStoreBuckets.GLOBAL]
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
export const ObjectStore = (bucket: any) => {
|
export const ObjectStore = (bucket: any) => {
|
||||||
AWS.config.update({
|
|
||||||
accessKeyId: env.MINIO_ACCESS_KEY,
|
|
||||||
secretAccessKey: env.MINIO_SECRET_KEY,
|
|
||||||
region: env.AWS_REGION,
|
|
||||||
})
|
|
||||||
const config: any = {
|
const config: any = {
|
||||||
s3ForcePathStyle: true,
|
s3ForcePathStyle: true,
|
||||||
signatureVersion: "v4",
|
signatureVersion: "v4",
|
||||||
apiVersion: "2006-03-01",
|
apiVersion: "2006-03-01",
|
||||||
|
accessKeyId: env.MINIO_ACCESS_KEY,
|
||||||
|
secretAccessKey: env.MINIO_SECRET_KEY,
|
||||||
|
region: env.AWS_REGION,
|
||||||
}
|
}
|
||||||
if (bucket) {
|
if (bucket) {
|
||||||
config.params = {
|
config.params = {
|
||||||
|
|
|
@ -291,6 +291,18 @@
|
||||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||||
|
|
||||||
|
"@hapi/hoek@^9.0.0":
|
||||||
|
version "9.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb"
|
||||||
|
integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==
|
||||||
|
|
||||||
|
"@hapi/topo@^5.0.0":
|
||||||
|
version "5.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012"
|
||||||
|
integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==
|
||||||
|
dependencies:
|
||||||
|
"@hapi/hoek" "^9.0.0"
|
||||||
|
|
||||||
"@istanbuljs/load-nyc-config@^1.0.0":
|
"@istanbuljs/load-nyc-config@^1.0.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
|
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
|
||||||
|
@ -539,6 +551,23 @@
|
||||||
koa "^2.13.4"
|
koa "^2.13.4"
|
||||||
node-mocks-http "^1.5.8"
|
node-mocks-http "^1.5.8"
|
||||||
|
|
||||||
|
"@sideway/address@^4.1.3":
|
||||||
|
version "4.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0"
|
||||||
|
integrity sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==
|
||||||
|
dependencies:
|
||||||
|
"@hapi/hoek" "^9.0.0"
|
||||||
|
|
||||||
|
"@sideway/formula@^3.0.0":
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.0.tgz#fe158aee32e6bd5de85044be615bc08478a0a13c"
|
||||||
|
integrity sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==
|
||||||
|
|
||||||
|
"@sideway/pinpoint@^2.0.0":
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df"
|
||||||
|
integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==
|
||||||
|
|
||||||
"@sindresorhus/is@^0.14.0":
|
"@sindresorhus/is@^0.14.0":
|
||||||
version "0.14.0"
|
version "0.14.0"
|
||||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
|
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
|
||||||
|
@ -3193,6 +3222,17 @@ jmespath@0.15.0:
|
||||||
resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217"
|
resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217"
|
||||||
integrity sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w==
|
integrity sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w==
|
||||||
|
|
||||||
|
joi@17.6.0:
|
||||||
|
version "17.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/joi/-/joi-17.6.0.tgz#0bb54f2f006c09a96e75ce687957bd04290054b2"
|
||||||
|
integrity sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==
|
||||||
|
dependencies:
|
||||||
|
"@hapi/hoek" "^9.0.0"
|
||||||
|
"@hapi/topo" "^5.0.0"
|
||||||
|
"@sideway/address" "^4.1.3"
|
||||||
|
"@sideway/formula" "^3.0.0"
|
||||||
|
"@sideway/pinpoint" "^2.0.0"
|
||||||
|
|
||||||
join-component@^1.1.0:
|
join-component@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/join-component/-/join-component-1.1.0.tgz#b8417b750661a392bee2c2537c68b2a9d4977cd5"
|
resolved "https://registry.yarnpkg.com/join-component/-/join-component-1.1.0.tgz#b8417b750661a392bee2c2537c68b2a9d4977cd5"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/bbui",
|
"name": "@budibase/bbui",
|
||||||
"description": "A UI solution used in the different Budibase projects.",
|
"description": "A UI solution used in the different Budibase projects.",
|
||||||
"version": "1.2.47",
|
"version": "1.2.57",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"module": "dist/bbui.es.js",
|
"module": "dist/bbui.es.js",
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
|
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
|
||||||
"@budibase/string-templates": "^1.2.47",
|
"@budibase/string-templates": "^1.2.57",
|
||||||
"@spectrum-css/actionbutton": "^1.0.1",
|
"@spectrum-css/actionbutton": "^1.0.1",
|
||||||
"@spectrum-css/actiongroup": "^1.0.1",
|
"@spectrum-css/actiongroup": "^1.0.1",
|
||||||
"@spectrum-css/avatar": "^3.0.2",
|
"@spectrum-css/avatar": "^3.0.2",
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
export let appendTo = undefined
|
export let appendTo = undefined
|
||||||
export let timeOnly = false
|
export let timeOnly = false
|
||||||
export let ignoreTimezones = false
|
export let ignoreTimezones = false
|
||||||
|
export let time24hr = false
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const flatpickrId = `${uuid()}-wrapper`
|
const flatpickrId = `${uuid()}-wrapper`
|
||||||
|
@ -37,6 +38,7 @@
|
||||||
enableTime: timeOnly || enableTime || false,
|
enableTime: timeOnly || enableTime || false,
|
||||||
noCalendar: timeOnly || false,
|
noCalendar: timeOnly || false,
|
||||||
altInput: true,
|
altInput: true,
|
||||||
|
time_24hr: time24hr || false,
|
||||||
altFormat: timeOnly ? "H:i" : enableTime ? "F j Y, H:i" : "F j, Y",
|
altFormat: timeOnly ? "H:i" : enableTime ? "F j Y, H:i" : "F j, Y",
|
||||||
wrap: true,
|
wrap: true,
|
||||||
appendTo,
|
appendTo,
|
||||||
|
@ -49,6 +51,12 @@
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: redrawOptions = {
|
||||||
|
timeOnly,
|
||||||
|
enableTime,
|
||||||
|
time24hr,
|
||||||
|
}
|
||||||
|
|
||||||
const handleChange = event => {
|
const handleChange = event => {
|
||||||
const [dates] = event.detail
|
const [dates] = event.detail
|
||||||
const noTimezone = enableTime && !timeOnly && ignoreTimezones
|
const noTimezone = enableTime && !timeOnly && ignoreTimezones
|
||||||
|
@ -149,7 +157,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#key timeOnly}
|
{#key redrawOptions}
|
||||||
<Flatpickr
|
<Flatpickr
|
||||||
bind:flatpickr
|
bind:flatpickr
|
||||||
value={parseDate(value)}
|
value={parseDate(value)}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let fileSizeLimit = BYTES_IN_MB * 20
|
export let fileSizeLimit = BYTES_IN_MB * 20
|
||||||
export let processFiles = null
|
export let processFiles = null
|
||||||
|
export let deleteAttachments = null
|
||||||
export let handleFileTooLarge = null
|
export let handleFileTooLarge = null
|
||||||
export let handleTooManyFiles = null
|
export let handleTooManyFiles = null
|
||||||
export let gallery = true
|
export let gallery = true
|
||||||
|
@ -94,6 +95,11 @@
|
||||||
"change",
|
"change",
|
||||||
value.filter((x, idx) => idx !== selectedImageIdx)
|
value.filter((x, idx) => idx !== selectedImageIdx)
|
||||||
)
|
)
|
||||||
|
if (deleteAttachments) {
|
||||||
|
await deleteAttachments(
|
||||||
|
value.filter((x, idx) => idx === selectedImageIdx).map(item => item.key)
|
||||||
|
)
|
||||||
|
}
|
||||||
selectedImageIdx = 0
|
selectedImageIdx = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
$: toggleOption = makeToggleOption(selectedLookupMap, value)
|
$: toggleOption = makeToggleOption(selectedLookupMap, value)
|
||||||
|
|
||||||
const getFieldText = (value, map, placeholder) => {
|
const getFieldText = (value, map, placeholder) => {
|
||||||
if (value?.length) {
|
if (Array.isArray(value) && value.length > 0) {
|
||||||
if (!map) {
|
if (!map) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
|
|
||||||
const getSelectedLookupMap = value => {
|
const getSelectedLookupMap = value => {
|
||||||
let map = {}
|
let map = {}
|
||||||
if (value?.length) {
|
if (Array.isArray(value) && value.length > 0) {
|
||||||
value.forEach(option => {
|
value.forEach(option => {
|
||||||
if (option) {
|
if (option) {
|
||||||
map[option] = true
|
map[option] = true
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
export let error = null
|
export let error = null
|
||||||
export let enableTime = true
|
export let enableTime = true
|
||||||
export let timeOnly = false
|
export let timeOnly = false
|
||||||
|
export let time24hr = false
|
||||||
export let placeholder = null
|
export let placeholder = null
|
||||||
export let appendTo = undefined
|
export let appendTo = undefined
|
||||||
export let ignoreTimezones = false
|
export let ignoreTimezones = false
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
{placeholder}
|
{placeholder}
|
||||||
{enableTime}
|
{enableTime}
|
||||||
{timeOnly}
|
{timeOnly}
|
||||||
|
{time24hr}
|
||||||
{appendTo}
|
{appendTo}
|
||||||
{ignoreTimezones}
|
{ignoreTimezones}
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
export let error = null
|
export let error = null
|
||||||
export let fileSizeLimit = undefined
|
export let fileSizeLimit = undefined
|
||||||
export let processFiles = undefined
|
export let processFiles = undefined
|
||||||
|
export let deleteAttachments = undefined
|
||||||
export let handleFileTooLarge = undefined
|
export let handleFileTooLarge = undefined
|
||||||
export let handleTooManyFiles = undefined
|
export let handleTooManyFiles = undefined
|
||||||
export let gallery = true
|
export let gallery = true
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
{value}
|
{value}
|
||||||
{fileSizeLimit}
|
{fileSizeLimit}
|
||||||
{processFiles}
|
{processFiles}
|
||||||
|
{deleteAttachments}
|
||||||
{handleFileTooLarge}
|
{handleFileTooLarge}
|
||||||
{handleTooManyFiles}
|
{handleTooManyFiles}
|
||||||
{gallery}
|
{gallery}
|
||||||
|
|
|
@ -83,4 +83,9 @@
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spectrum-Icon--sizeXS {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher, getContext } from "svelte"
|
import { createEventDispatcher, getContext } from "svelte"
|
||||||
|
import Icon from "../Icon/Icon.svelte"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const actionMenu = getContext("actionMenu")
|
const actionMenu = getContext("actionMenu")
|
||||||
|
@ -8,6 +9,22 @@
|
||||||
export let icon = undefined
|
export let icon = undefined
|
||||||
export let disabled = undefined
|
export let disabled = undefined
|
||||||
export let noClose = false
|
export let noClose = false
|
||||||
|
export let keyBind = undefined
|
||||||
|
|
||||||
|
$: keys = getKeys(keyBind)
|
||||||
|
|
||||||
|
const getKeys = keyBind => {
|
||||||
|
let keys = keyBind?.split("+") || []
|
||||||
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
if (
|
||||||
|
keys[i].toLowerCase() === "ctrl" &&
|
||||||
|
navigator.platform.startsWith("Mac")
|
||||||
|
) {
|
||||||
|
keys[i] = "⌘"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
const onClick = () => {
|
const onClick = () => {
|
||||||
if (actionMenu && !noClose) {
|
if (actionMenu && !noClose) {
|
||||||
|
@ -26,20 +43,54 @@
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
{#if icon}
|
{#if icon}
|
||||||
<svg
|
<div class="icon">
|
||||||
class="spectrum-Icon spectrum-Icon--sizeS spectrum-Menu-itemIcon"
|
<Icon name={icon} size="S" />
|
||||||
focusable="false"
|
</div>
|
||||||
aria-hidden="true"
|
|
||||||
aria-label={icon}
|
|
||||||
>
|
|
||||||
<use xlink:href="#spectrum-icon-18-{icon}" />
|
|
||||||
</svg>
|
|
||||||
{/if}
|
{/if}
|
||||||
<span class="spectrum-Menu-itemLabel"><slot /></span>
|
<span class="spectrum-Menu-itemLabel"><slot /></span>
|
||||||
|
{#if keys?.length}
|
||||||
|
<div class="keys">
|
||||||
|
{#each keys as key}
|
||||||
|
<div class="key">
|
||||||
|
{#if key.startsWith("!")}
|
||||||
|
<Icon size="XS" name={key.split("!")[1]} />
|
||||||
|
{:else}
|
||||||
|
{key}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.spectrum-Menu-itemIcon {
|
.icon {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
|
margin-right: var(--spacing-s);
|
||||||
|
}
|
||||||
|
.keys {
|
||||||
|
margin-left: 30px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.key {
|
||||||
|
color: var(--spectrum-global-color-gray-900);
|
||||||
|
padding: 2px 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
background-color: var(--spectrum-global-color-gray-300);
|
||||||
|
border-radius: 4px;
|
||||||
|
min-width: 12px;
|
||||||
|
height: 16px;
|
||||||
|
text-align: center;
|
||||||
|
margin: -1px 0;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
.is-disabled .key {
|
||||||
|
color: var(--spectrum-global-color-gray-600);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
let visible = fixed || inline
|
let visible = fixed || inline
|
||||||
|
let modal
|
||||||
|
|
||||||
$: dispatch(visible ? "show" : "hide")
|
$: dispatch(visible ? "show" : "hide")
|
||||||
|
|
||||||
export function show() {
|
export function show() {
|
||||||
|
@ -41,12 +43,22 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function focusFirstInput(node) {
|
async function focusModal(node) {
|
||||||
|
await tick()
|
||||||
|
|
||||||
|
// Try to focus first input
|
||||||
const inputs = node.querySelectorAll("input")
|
const inputs = node.querySelectorAll("input")
|
||||||
if (inputs?.length) {
|
if (inputs?.length) {
|
||||||
await tick()
|
|
||||||
inputs[0].focus()
|
inputs[0].focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Otherwise try to focus confirmation button
|
||||||
|
else if (modal) {
|
||||||
|
const confirm = modal.querySelector(".confirm-wrap .spectrum-Button")
|
||||||
|
if (confirm) {
|
||||||
|
confirm.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setContext(Context.Modal, { show, hide, cancel })
|
setContext(Context.Modal, { show, hide, cancel })
|
||||||
|
@ -56,7 +68,7 @@
|
||||||
|
|
||||||
{#if inline}
|
{#if inline}
|
||||||
{#if visible}
|
{#if visible}
|
||||||
<div use:focusFirstInput class="spectrum-Modal inline is-open">
|
<div use:focusModal bind:this={modal} class="spectrum-Modal inline is-open">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -70,17 +82,18 @@
|
||||||
-->
|
-->
|
||||||
<Portal target=".modal-container">
|
<Portal target=".modal-container">
|
||||||
{#if visible}
|
{#if visible}
|
||||||
<div
|
<div class="spectrum-Underlay is-open" on:mousedown|self={cancel}>
|
||||||
class="spectrum-Underlay is-open"
|
<div
|
||||||
in:fade={{ duration: 200 }}
|
class="background"
|
||||||
out:fade|local={{ duration: 200 }}
|
in:fade={{ duration: 200 }}
|
||||||
on:mousedown|self={cancel}
|
out:fade|local={{ duration: 200 }}
|
||||||
>
|
/>
|
||||||
<div class="modal-wrapper" on:mousedown|self={cancel}>
|
<div class="modal-wrapper" on:mousedown|self={cancel}>
|
||||||
<div class="modal-inner-wrapper" on:mousedown|self={cancel}>
|
<div class="modal-inner-wrapper" on:mousedown|self={cancel}>
|
||||||
<slot name="outside" />
|
<slot name="outside" />
|
||||||
<div
|
<div
|
||||||
use:focusFirstInput
|
use:focusModal
|
||||||
|
bind:this={modal}
|
||||||
class="spectrum-Modal is-open"
|
class="spectrum-Modal is-open"
|
||||||
in:fly={{ y: 30, duration: 200 }}
|
in:fly={{ y: 30, duration: 200 }}
|
||||||
out:fly|local={{ y: 30, duration: 200 }}
|
out:fly|local={{ y: 30, duration: 200 }}
|
||||||
|
@ -103,7 +116,17 @@
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
background: rgba(0, 0, 0, 0.75);
|
background: transparent;
|
||||||
|
}
|
||||||
|
.background {
|
||||||
|
background: var(--modal-background, rgba(0, 0, 0, 0.75));
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
opacity: 0.65;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-wrapper {
|
.modal-wrapper {
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.spectrum-Popover {
|
.spectrum-Popover {
|
||||||
min-width: var(--spectrum-global-dimension-size-2000) !important;
|
min-width: var(--spectrum-global-dimension-size-2000);
|
||||||
}
|
}
|
||||||
.spectrum-Popover.is-open.spectrum-Popover--withTip {
|
.spectrum-Popover.is-open.spectrum-Popover--withTip {
|
||||||
margin-top: var(--spacing-xs);
|
margin-top: var(--spacing-xs);
|
||||||
|
|
|
@ -23,7 +23,7 @@ filterTests(["smoke", "all"], () => {
|
||||||
cy.get(interact.SPECTRUM_ICON).click({ force: true })
|
cy.get(interact.SPECTRUM_ICON).click({ force: true })
|
||||||
})
|
})
|
||||||
cy.get(interact.SPECTRUM_MENU).within(() => {
|
cy.get(interact.SPECTRUM_MENU).within(() => {
|
||||||
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Force Password Reset").click({ force: true })
|
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Force password reset").click({ force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.get(interact.SPECTRUM_DIALOG_GRID)
|
cy.get(interact.SPECTRUM_DIALOG_GRID)
|
||||||
|
@ -41,10 +41,25 @@ filterTests(["smoke", "all"], () => {
|
||||||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("test")
|
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("test")
|
||||||
}
|
}
|
||||||
cy.get(interact.SPECTRUM_BUTTON).contains("Reset your password").click({ force: true })
|
cy.get(interact.SPECTRUM_BUTTON).contains("Reset your password").click({ force: true })
|
||||||
|
//cy.logoutNoAppGrid()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should verify Standard Portal", () => {
|
||||||
|
// Development access should be disabled (Admin access is already disabled)
|
||||||
|
cy.login()
|
||||||
|
cy.setUserRole("bbuser", "App User")
|
||||||
|
bbUserLogin()
|
||||||
|
|
||||||
|
// Verify Standard Portal
|
||||||
|
cy.get(interact.SPECTRUM_SIDENAV).should('not.exist') // No config sections
|
||||||
|
cy.get(interact.CREATE_APP_BUTTON).should('not.exist') // No create app button
|
||||||
|
cy.get(".app").should('not.exist') // No apps -> no roles assigned to user
|
||||||
|
cy.get(interact.CONTAINER).should('contain', bbUserEmail) // Message containing users email
|
||||||
|
|
||||||
cy.logoutNoAppGrid()
|
cy.logoutNoAppGrid()
|
||||||
})
|
})
|
||||||
|
|
||||||
xit("should verify Admin Portal", () => {
|
it("should verify Admin Portal", () => {
|
||||||
cy.login()
|
cy.login()
|
||||||
// Configure user role
|
// Configure user role
|
||||||
cy.setUserRole("bbuser", "Admin")
|
cy.setUserRole("bbuser", "Admin")
|
||||||
|
@ -86,21 +101,6 @@ filterTests(["smoke", "all"], () => {
|
||||||
cy.logOut()
|
cy.logOut()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should verify Standard Portal", () => {
|
|
||||||
// Development access should be disabled (Admin access is already disabled)
|
|
||||||
cy.login()
|
|
||||||
cy.setUserRole("bbuser", "App User")
|
|
||||||
bbUserLogin()
|
|
||||||
|
|
||||||
// Verify Standard Portal
|
|
||||||
cy.get(interact.SPECTRUM_SIDENAV).should('not.exist') // No config sections
|
|
||||||
cy.get(interact.CREATE_APP_BUTTON).should('not.exist') // No create app button
|
|
||||||
cy.get(".app").should('not.exist') // No apps -> no roles assigned to user
|
|
||||||
cy.get(interact.CONTAINER).should('contain', bbUserEmail) // Message containing users email
|
|
||||||
|
|
||||||
cy.logoutNoAppGrid()
|
|
||||||
})
|
|
||||||
|
|
||||||
const bbUserLogin = () => {
|
const bbUserLogin = () => {
|
||||||
// Login as bbuser
|
// Login as bbuser
|
||||||
cy.logOut()
|
cy.logOut()
|
||||||
|
|
|
@ -0,0 +1,178 @@
|
||||||
|
import filterTests from "../../support/filterTests"
|
||||||
|
// const interact = require("../support/interact")
|
||||||
|
|
||||||
|
filterTests(["smoke", "all"], () => {
|
||||||
|
context("Auth Configuration", () => {
|
||||||
|
before(() => {
|
||||||
|
cy.login()
|
||||||
|
})
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
cy.get(".spectrum-SideNav li").contains("Auth").click()
|
||||||
|
cy.location().should(loc => {
|
||||||
|
expect(loc.pathname).to.eq("/builder/portal/manage/auth")
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get("[data-cy=new-scope-input]").clear()
|
||||||
|
|
||||||
|
cy.get("div.content").scrollTo("bottom")
|
||||||
|
cy.get("[data-cy=oidc-active]").click()
|
||||||
|
|
||||||
|
cy.get("[data-cy=oidc-active]").should('not.be.checked')
|
||||||
|
|
||||||
|
cy.intercept("POST", "/api/global/configs").as("updateAuth")
|
||||||
|
cy.get("button[data-cy=oidc-save]").contains("Save").click({force: true})
|
||||||
|
cy.wait("@updateAuth")
|
||||||
|
cy.get("@updateAuth").its("response.statusCode").should("eq", 200)
|
||||||
|
|
||||||
|
cy.get(".spectrum-Toast-content")
|
||||||
|
.contains("Settings saved")
|
||||||
|
.should("be.visible")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should allow updating of the OIDC config", () => {
|
||||||
|
cy.get(".spectrum-SideNav li").contains("Auth").click()
|
||||||
|
cy.location().should(loc => {
|
||||||
|
expect(loc.pathname).to.eq("/builder/portal/manage/auth")
|
||||||
|
})
|
||||||
|
cy.get("div.content").scrollTo("bottom")
|
||||||
|
cy.get(".spectrum-Toast .spectrum-ClearButton").click()
|
||||||
|
|
||||||
|
cy.get("input[data-cy=configUrl]").type("http://budi-auth.com/v2")
|
||||||
|
cy.get("input[data-cy=clientID]").type("34ac6a13-f24a-4b52-c70d-fa544ffd11b2")
|
||||||
|
cy.get("input[data-cy=clientSecret]").type("12A8Q~4nS_DWhOOJ2vWIRsNyDVsdtXPD.Zxa9df_")
|
||||||
|
|
||||||
|
cy.get("button[data-cy=oidc-save]").should("not.be.disabled");
|
||||||
|
|
||||||
|
cy.intercept("POST", "/api/global/configs").as("updateAuth")
|
||||||
|
cy.get("button[data-cy=oidc-save]").contains("Save").click({force: true})
|
||||||
|
cy.wait("@updateAuth")
|
||||||
|
cy.get("@updateAuth").its("response.statusCode").should("eq", 200)
|
||||||
|
|
||||||
|
cy.get(".spectrum-Toast-content")
|
||||||
|
.contains("Settings saved")
|
||||||
|
.should("be.visible")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should display default scopes in advanced config.", () => {
|
||||||
|
cy.get(".spectrum-SideNav li").contains("Auth").click()
|
||||||
|
cy.location().should(loc => {
|
||||||
|
expect(loc.pathname).to.eq("/builder/portal/manage/auth")
|
||||||
|
})
|
||||||
|
cy.get("div.content").scrollTo("bottom")
|
||||||
|
|
||||||
|
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4)
|
||||||
|
|
||||||
|
cy.get(".spectrum-Tags-item").contains("openid")
|
||||||
|
cy.get(".spectrum-Tags-item").contains("openid").find(".spectrum-ClearButton").should("not.exist")
|
||||||
|
|
||||||
|
cy.get(".spectrum-Tags-item").contains("offline_access")
|
||||||
|
cy.get(".spectrum-Tags-item").contains("email")
|
||||||
|
cy.get(".spectrum-Tags-item").contains("profile")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Add a new scopes", () => {
|
||||||
|
cy.get(".spectrum-SideNav li").contains("Auth").click()
|
||||||
|
cy.location().should(loc => {
|
||||||
|
expect(loc.pathname).to.eq("/builder/portal/manage/auth")
|
||||||
|
})
|
||||||
|
cy.get("div.content").scrollTo("bottom")
|
||||||
|
|
||||||
|
cy.get("[data-cy=new-scope-input]").type("Sample{enter}")
|
||||||
|
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 5)
|
||||||
|
cy.get(".spectrum-Tags-item").contains("Sample")
|
||||||
|
|
||||||
|
cy.get(".auth-form input.spectrum-Textfield-input").type("Another ")
|
||||||
|
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 6)
|
||||||
|
cy.get(".spectrum-Tags-item").contains("Another")
|
||||||
|
|
||||||
|
cy.get("button[data-cy=oidc-save]").should("not.be.disabled");
|
||||||
|
|
||||||
|
cy.intercept("POST", "/api/global/configs").as("updateAuth")
|
||||||
|
cy.get("button[data-cy=oidc-save]").contains("Save").click({force: true})
|
||||||
|
cy.wait("@updateAuth")
|
||||||
|
cy.get("@updateAuth").its("response.statusCode").should("eq", 200)
|
||||||
|
|
||||||
|
cy.reload()
|
||||||
|
|
||||||
|
cy.get("div.content").scrollTo("bottom")
|
||||||
|
|
||||||
|
cy.get(".spectrum-Tags-item").contains("openid")
|
||||||
|
cy.get(".spectrum-Tags-item").contains("offline_access")
|
||||||
|
cy.get(".spectrum-Tags-item").contains("email")
|
||||||
|
cy.get(".spectrum-Tags-item").contains("profile")
|
||||||
|
cy.get(".spectrum-Tags-item").contains("Sample")
|
||||||
|
cy.get(".spectrum-Tags-item").contains("Another")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should allow the removal of auth scopes", () => {
|
||||||
|
cy.get(".spectrum-SideNav li").contains("Auth").click()
|
||||||
|
cy.location().should(loc => {
|
||||||
|
expect(loc.pathname).to.eq("/builder/portal/manage/auth")
|
||||||
|
})
|
||||||
|
cy.get("div.content").scrollTo("bottom")
|
||||||
|
|
||||||
|
cy.get(".spectrum-Tags-item").contains("offline_access").parent().find(".spectrum-ClearButton").click()
|
||||||
|
cy.get(".spectrum-Tags-item").contains("profile").parent().find(".spectrum-ClearButton").click()
|
||||||
|
|
||||||
|
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4)
|
||||||
|
|
||||||
|
cy.get(".spectrum-Tags-item").contains("offline_access").should("not.exist")
|
||||||
|
cy.get(".spectrum-Tags-item").contains("profile").should("not.exist")
|
||||||
|
|
||||||
|
cy.get("button[data-cy=oidc-save]").should("not.be.disabled");
|
||||||
|
|
||||||
|
cy.intercept("POST", "/api/global/configs").as("updateAuth")
|
||||||
|
cy.get("button[data-cy=oidc-save]").contains("Save").click({force: true})
|
||||||
|
cy.wait("@updateAuth")
|
||||||
|
cy.get("@updateAuth").its("response.statusCode").should("eq", 200)
|
||||||
|
|
||||||
|
cy.get(".spectrum-Toast-content")
|
||||||
|
.contains("Settings saved")
|
||||||
|
.should("be.visible")
|
||||||
|
|
||||||
|
cy.reload()
|
||||||
|
|
||||||
|
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4)
|
||||||
|
|
||||||
|
cy.get(".spectrum-Tags-item").contains("offline_access").should("not.exist")
|
||||||
|
cy.get(".spectrum-Tags-item").contains("profile").should("not.exist")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should allow auth scopes to be reset to the core defaults.", () => {
|
||||||
|
cy.get(".spectrum-SideNav li").contains("Auth").click()
|
||||||
|
|
||||||
|
cy.get("div.content").scrollTo("bottom")
|
||||||
|
|
||||||
|
cy.get("[data-cy=restore-oidc-default-scopes]").click({force: true})
|
||||||
|
|
||||||
|
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4)
|
||||||
|
|
||||||
|
cy.get(".spectrum-Tags-item").contains("openid")
|
||||||
|
cy.get(".spectrum-Tags-item").contains("offline_access")
|
||||||
|
cy.get(".spectrum-Tags-item").contains("email")
|
||||||
|
cy.get(".spectrum-Tags-item").contains("profile")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should not allow invalid characters in the auth scopes", () => {
|
||||||
|
cy.get("[data-cy=new-scope-input]").type("thisIsInvalid\\{enter}")
|
||||||
|
cy.get(".spectrum-Form-itemField .error").contains("Auth scopes cannot contain spaces, double quotes or backslashes")
|
||||||
|
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4)
|
||||||
|
|
||||||
|
cy.get("[data-cy=new-scope-input]").clear()
|
||||||
|
|
||||||
|
cy.get("[data-cy=new-scope-input]").type("alsoInvalid\"{enter}")
|
||||||
|
cy.get(".spectrum-Form-itemField .error").contains("Auth scopes cannot contain spaces, double quotes or backslashes")
|
||||||
|
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4)
|
||||||
|
|
||||||
|
cy.get("[data-cy=new-scope-input]").clear()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should not allow duplicate auth scopes", () => {
|
||||||
|
cy.get("[data-cy=new-scope-input]").type("offline_access{enter}")
|
||||||
|
cy.get(".spectrum-Form-itemField .error").contains("Auth scope already exists")
|
||||||
|
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
|
@ -17,7 +17,7 @@ filterTests(["smoke", "all"], () => {
|
||||||
|
|
||||||
it("should confirm App User role for a New User", () => {
|
it("should confirm App User role for a New User", () => {
|
||||||
cy.contains("bbuser").click()
|
cy.contains("bbuser").click()
|
||||||
cy.get(".spectrum-Form-itemField").eq(2).should('contain', 'App User')
|
cy.get(".spectrum-Form-itemField").eq(3).should('contain', 'App User')
|
||||||
|
|
||||||
// User should not have app access
|
// User should not have app access
|
||||||
cy.get(interact.LIST_ITEMS, { timeout: 500 }).should("contain", "No apps")
|
cy.get(interact.LIST_ITEMS, { timeout: 500 }).should("contain", "No apps")
|
||||||
|
@ -166,12 +166,12 @@ filterTests(["smoke", "all"], () => {
|
||||||
|
|
||||||
it("Should edit user details within user details page", () => {
|
it("Should edit user details within user details page", () => {
|
||||||
// Add First name
|
// Add First name
|
||||||
cy.get(interact.FIELD, { timeout: 1000 }).eq(0).within(() => {
|
cy.get(interact.FIELD, { timeout: 1000 }).eq(1).within(() => {
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).wait(500).clear().click().type("bb")
|
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).wait(500).clear().click().type("bb")
|
||||||
})
|
})
|
||||||
// Add Last name
|
// Add Last name
|
||||||
cy.get(interact.FIELD, { timeout: 1000 }).eq(1).within(() => {
|
cy.get(interact.FIELD, { timeout: 1000 }).eq(2).within(() => {
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).click().wait(500).clear().type("test")
|
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).click().wait(500).clear().type("test")
|
||||||
})
|
})
|
||||||
|
@ -180,10 +180,10 @@ filterTests(["smoke", "all"], () => {
|
||||||
cy.reload()
|
cy.reload()
|
||||||
|
|
||||||
// Confirm details have been saved
|
// Confirm details have been saved
|
||||||
cy.get(interact.FIELD, { timeout: 1000 }).eq(0).within(() => {
|
cy.get(interact.FIELD, { timeout: 1000 }).eq(1).within(() => {
|
||||||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', "bb")
|
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', "bb")
|
||||||
})
|
})
|
||||||
cy.get(interact.FIELD, { timeout: 1000 }).eq(1).within(() => {
|
cy.get(interact.FIELD, { timeout: 1000 }).eq(2).within(() => {
|
||||||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).should('have.value', "test")
|
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).should('have.value', "test")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -193,13 +193,14 @@ filterTests(["smoke", "all"], () => {
|
||||||
cy.get(interact.SPECTRUM_ICON).click({ force: true })
|
cy.get(interact.SPECTRUM_ICON).click({ force: true })
|
||||||
})
|
})
|
||||||
cy.get(interact.SPECTRUM_MENU).within(() => {
|
cy.get(interact.SPECTRUM_MENU).within(() => {
|
||||||
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Force Password Reset").click({ force: true })
|
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Force password reset").click({ force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
// Reset password modal
|
// Reset password modal
|
||||||
cy.get(interact.SPECTRUM_DIALOG_GRID)
|
cy.get(interact.SPECTRUM_DIALOG_GRID)
|
||||||
.find(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').as('pwd')
|
.find(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').as('pwd')
|
||||||
cy.get(interact.SPECTRUM_BUTTON).contains("Reset password").click({ force: true })
|
cy.get(interact.SPECTRUM_BUTTON).contains("Reset password").click({ force: true })
|
||||||
|
cy.get(interact.SPECTRUM_BUTTON).contains("Reset password").should('not.exist')
|
||||||
|
|
||||||
// Logout, then login with new password
|
// Logout, then login with new password
|
||||||
cy.logOut()
|
cy.logOut()
|
||||||
|
@ -214,6 +215,7 @@ filterTests(["smoke", "all"], () => {
|
||||||
cy.get(interact.SPECTRUM_BUTTON).contains("Reset your password").click({ force: true })
|
cy.get(interact.SPECTRUM_BUTTON).contains("Reset your password").click({ force: true })
|
||||||
|
|
||||||
// Confirm user logged in afer password change
|
// Confirm user logged in afer password change
|
||||||
|
cy.login("bbuser@test.com", "test")
|
||||||
cy.get(".avatar > .icon").click({ force: true })
|
cy.get(".avatar > .icon").click({ force: true })
|
||||||
|
|
||||||
cy.get(".spectrum-Menu-item").contains("Update user information").click({ force: true })
|
cy.get(".spectrum-Menu-item").contains("Update user information").click({ force: true })
|
||||||
|
|
|
@ -19,10 +19,10 @@ filterTests(["smoke", "all"], () => {
|
||||||
cy.contains("Users").click()
|
cy.contains("Users").click()
|
||||||
cy.contains("test@test.com").click()
|
cy.contains("test@test.com").click()
|
||||||
|
|
||||||
cy.get(interact.FIELD, { timeout: 1000 }).eq(0).within(() => {
|
cy.get(interact.FIELD, { timeout: 1000 }).eq(1).within(() => {
|
||||||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', fname)
|
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', fname)
|
||||||
})
|
})
|
||||||
cy.get(interact.FIELD).eq(1).within(() => {
|
cy.get(interact.FIELD).eq(2).within(() => {
|
||||||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', lname)
|
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', lname)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -72,7 +72,7 @@ filterTests(["smoke", "all"], () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Logout & in with new password
|
// Logout & in with new password
|
||||||
cy.logOut()
|
//cy.logOut()
|
||||||
cy.login("test@test.com", "newpwd")
|
cy.login("test@test.com", "newpwd")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -90,7 +90,6 @@ filterTests(["smoke", "all"], () => {
|
||||||
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Open developer mode").click({ force: true })
|
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Open developer mode").click({ force: true })
|
||||||
cy.get(interact.SPECTRUM_SIDENAV).should('exist') // config sections available
|
cy.get(interact.SPECTRUM_SIDENAV).should('exist') // config sections available
|
||||||
cy.get(interact.CREATE_APP_BUTTON).should('exist') // create app button available
|
cy.get(interact.CREATE_APP_BUTTON).should('exist') // create app button available
|
||||||
cy.get(interact.APP_TABLE).should('exist') // App table available
|
|
||||||
})
|
})
|
||||||
|
|
||||||
after(() => {
|
after(() => {
|
||||||
|
|
|
@ -266,7 +266,7 @@ filterTests(["all"], () => {
|
||||||
cy.reload()
|
cy.reload()
|
||||||
cy.log("Current deployment version: " + clientPackage.version)
|
cy.log("Current deployment version: " + clientPackage.version)
|
||||||
|
|
||||||
cy.get(".version-status a", { timeout: 1000 }).contains("Update").click()
|
cy.get(".version-status a", { timeout: 5000 }).contains("Update").click()
|
||||||
cy.get(".spectrum-Tabs-item.is-selected").contains("Settings")
|
cy.get(".spectrum-Tabs-item.is-selected").contains("Settings")
|
||||||
|
|
||||||
cy.get(".version-section .page-action button")
|
cy.get(".version-section .page-action button")
|
||||||
|
|
|
@ -102,7 +102,7 @@ filterTests(['all'], () => {
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 6000 })
|
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 6000 })
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
cy.get(interact.APP_TABLE_STATUS, { timeout: 1000 }).eq(0).contains("Unpublished")
|
cy.get(interact.APP_TABLE_STATUS, { timeout: 10000 }).eq(0).contains("Unpublished")
|
||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -94,6 +94,7 @@ filterTests(['smoke', 'all'], () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should create the first application from scratch with a default name", () => {
|
it("should create the first application from scratch with a default name", () => {
|
||||||
|
cy.updateUserInformation("", "")
|
||||||
cy.createApp("", false)
|
cy.createApp("", false)
|
||||||
cy.applicationInAppTable("My app")
|
cy.applicationInAppTable("My app")
|
||||||
cy.deleteApp("My app")
|
cy.deleteApp("My app")
|
||||||
|
|
|
@ -48,7 +48,7 @@ filterTests(["smoke", "all"], () => {
|
||||||
|
|
||||||
it("deletes a row", () => {
|
it("deletes a row", () => {
|
||||||
cy.get(interact.SPECTRUM_CHECKBOX_INPUT).check({ force: true })
|
cy.get(interact.SPECTRUM_CHECKBOX_INPUT).check({ force: true })
|
||||||
cy.contains("Delete 1 row(s)").click()
|
cy.contains("Delete 1 row").click()
|
||||||
cy.get(interact.SPECTRUM_MODAL).contains("Delete").click()
|
cy.get(interact.SPECTRUM_MODAL).contains("Delete").click()
|
||||||
cy.contains("RoverUpdated").should("not.exist")
|
cy.contains("RoverUpdated").should("not.exist")
|
||||||
})
|
})
|
||||||
|
|
|
@ -175,7 +175,10 @@ filterTests(["all"], () => {
|
||||||
cy.get("@query").its("response.statusCode").should("eq", 200)
|
cy.get("@query").its("response.statusCode").should("eq", 200)
|
||||||
cy.get("@query").its("response.body").should("not.be.empty")
|
cy.get("@query").its("response.body").should("not.be.empty")
|
||||||
// Save query
|
// Save query
|
||||||
|
cy.intercept("POST", "**/queries").as("saveQuery")
|
||||||
cy.get(".spectrum-Button").contains("Save Query").click({ force: true })
|
cy.get(".spectrum-Button").contains("Save Query").click({ force: true })
|
||||||
|
cy.wait("@saveQuery")
|
||||||
|
cy.get("@saveQuery").its("response.statusCode").should("eq", 200)
|
||||||
cy.get(".nav-item").should("contain", queryName)
|
cy.get(".nav-item").should("contain", queryName)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -252,7 +252,8 @@ filterTests(["all"], () => {
|
||||||
.contains("Delete Query")
|
.contains("Delete Query")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
// Confirm deletion
|
// Confirm deletion
|
||||||
cy.reload({ timeout: 5000 })
|
cy.reload()
|
||||||
|
cy.get(".nav-item", { timeout: 30000 }).contains(datasource).click({ force: true })
|
||||||
cy.get(".nav-item", { timeout: 1000 }).should("not.contain", queryRename)
|
cy.get(".nav-item", { timeout: 1000 }).should("not.contain", queryRename)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ filterTests(['smoke', 'all'], () => {
|
||||||
cy.get(interact.AREA_LABEL_REVERT).click({ force: true })
|
cy.get(interact.AREA_LABEL_REVERT).click({ force: true })
|
||||||
})
|
})
|
||||||
cy.get(interact.SPECTRUM_DIALOG_GRID).within(() => {
|
cy.get(interact.SPECTRUM_DIALOG_GRID).within(() => {
|
||||||
|
cy.get("input").type("Cypress Tests")
|
||||||
// Click Revert
|
// Click Revert
|
||||||
cy.get(interact.SPECTRUM_BUTTON).contains("Revert").click({ force: true })
|
cy.get(interact.SPECTRUM_BUTTON).contains("Revert").click({ force: true })
|
||||||
cy.wait(2000) // Wait for app to finish reverting
|
cy.wait(2000) // Wait for app to finish reverting
|
||||||
|
|
|
@ -128,7 +128,9 @@ Cypress.Commands.add("updateUserInformation", (firstName, lastName) => {
|
||||||
.should("have.value", lastName)
|
.should("have.value", lastName)
|
||||||
.blur()
|
.blur()
|
||||||
}
|
}
|
||||||
cy.get("button").contains("Update information").click({ force: true })
|
cy.get(".confirm-wrap").within(() => {
|
||||||
|
cy.get("button").contains("Update information").click({ force: true })
|
||||||
|
})
|
||||||
cy.get(".spectrum-Dialog-grid").should("not.exist")
|
cy.get(".spectrum-Dialog-grid").should("not.exist")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -140,14 +142,14 @@ Cypress.Commands.add("setUserRole", (user, role) => {
|
||||||
// Set Role
|
// Set Role
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
cy.get(".spectrum-Form-itemField")
|
cy.get(".spectrum-Form-itemField")
|
||||||
.eq(2)
|
.eq(3)
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get(".spectrum-Picker-label").click({ force: true })
|
cy.get(".spectrum-Picker-label").click({ force: true })
|
||||||
})
|
})
|
||||||
cy.get(".spectrum-Menu").within(() => {
|
cy.get(".spectrum-Menu").within(() => {
|
||||||
cy.get(".spectrum-Menu-itemLabel").contains(role).click({ force: true })
|
cy.get(".spectrum-Menu-itemLabel").contains(role).click({ force: true })
|
||||||
})
|
})
|
||||||
cy.get(".spectrum-Form-itemField").eq(2).should("contain", role)
|
cy.get(".spectrum-Form-itemField").eq(3).should("contain", role)
|
||||||
})
|
})
|
||||||
|
|
||||||
// APPLICATIONS
|
// APPLICATIONS
|
||||||
|
@ -162,7 +164,7 @@ Cypress.Commands.add("createApp", (name, addDefaultTable) => {
|
||||||
typeof addDefaultTable != "boolean" ? true : addDefaultTable
|
typeof addDefaultTable != "boolean" ? true : addDefaultTable
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 10000 })
|
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 10000 })
|
||||||
cy.wait(1000)
|
cy.url({ timeout: 30000 }).should("include", "/apps")
|
||||||
cy.get(`[data-cy="create-app-btn"]`, { timeout: 5000 }).click({ force: true })
|
cy.get(`[data-cy="create-app-btn"]`, { timeout: 5000 }).click({ force: true })
|
||||||
|
|
||||||
// If apps already exist
|
// If apps already exist
|
||||||
|
@ -432,6 +434,7 @@ Cypress.Commands.add("createAppFromScratch", appName => {
|
||||||
|
|
||||||
// TABLES
|
// TABLES
|
||||||
Cypress.Commands.add("createTable", (tableName, initialTable) => {
|
Cypress.Commands.add("createTable", (tableName, initialTable) => {
|
||||||
|
// Creates an internal Budibase DB table
|
||||||
if (!initialTable) {
|
if (!initialTable) {
|
||||||
cy.navigateToDataSection()
|
cy.navigateToDataSection()
|
||||||
cy.get(`[data-cy="new-table"]`, { timeout: 2000 }).click()
|
cy.get(`[data-cy="new-table"]`, { timeout: 2000 }).click()
|
||||||
|
@ -445,6 +448,7 @@ Cypress.Commands.add("createTable", (tableName, initialTable) => {
|
||||||
.contains("Continue")
|
.contains("Continue")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
})
|
})
|
||||||
|
cy.get(".spectrum-Modal").contains("Create Table", { timeout: 10000 })
|
||||||
cy.get(".spectrum-Modal", { timeout: 2000 }).within(() => {
|
cy.get(".spectrum-Modal", { timeout: 2000 }).within(() => {
|
||||||
cy.get("input", { timeout: 2000 }).first().type(tableName).blur()
|
cy.get("input", { timeout: 2000 }).first().type(tableName).blur()
|
||||||
cy.get(".spectrum-ButtonGroup").contains("Create").click()
|
cy.get(".spectrum-ButtonGroup").contains("Create").click()
|
||||||
|
@ -735,8 +739,15 @@ Cypress.Commands.add("deleteAllScreens", () => {
|
||||||
Cypress.Commands.add("navigateToFrontend", () => {
|
Cypress.Commands.add("navigateToFrontend", () => {
|
||||||
// Clicks on Design tab and then the Home nav item
|
// Clicks on Design tab and then the Home nav item
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
|
cy.intercept("**/preview").as("preview")
|
||||||
cy.contains("Design").click()
|
cy.contains("Design").click()
|
||||||
cy.get(".spectrum-Search", { timeout: 2000 }).type("/")
|
cy.wait("@preview")
|
||||||
|
cy.get("@preview").then(res => {
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
cy.reload()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
cy.get(".spectrum-Search", { timeout: 20000 }).type("/")
|
||||||
cy.get(".nav-item", { timeout: 2000 }).contains("home").click({ force: true })
|
cy.get(".nav-item", { timeout: 2000 }).contains("home").click({ force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "1.2.47",
|
"version": "1.2.57",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -13,11 +13,11 @@
|
||||||
"cy:setup:ci": "node ./cypress/setup.js",
|
"cy:setup:ci": "node ./cypress/setup.js",
|
||||||
"cy:open": "cypress open",
|
"cy:open": "cypress open",
|
||||||
"cy:run": "cypress run",
|
"cy:run": "cypress run",
|
||||||
"cy:run:ci": "cypress run --headed --browser chrome --spec cypress/integration/createApp.spec.js",
|
"cy:run:ci": "cypress run --headed --browser chrome --spec cypress/integration/createApp.spec.js",
|
||||||
"cy:run:ci:record": "xvfb-run cypress run --headed --browser chrome --record",
|
"cy:run:ci:record": "xvfb-run cypress run --headed --browser chrome --record",
|
||||||
"cy:test": "start-server-and-test cy:setup http://localhost:4100/builder cy:run",
|
"cy:test": "start-server-and-test cy:setup http://localhost:4100/builder cy:run",
|
||||||
"cy:ci": "start-server-and-test cy:setup:ci http://localhost:4100/builder cy:run:ci",
|
"cy:ci": "start-server-and-test cy:setup:ci http://localhost:4100/builder cy:run:ci",
|
||||||
"cy:ci:record": "start-server-and-test cy:setup:ci http://localhost:4100/builder cy:run:ci:record && npm run cy:ci:report",
|
"cy:ci:record": "start-server-and-test cy:setup:ci http://localhost:4100/builder cy:run:ci:record; npm run cy:ci:report",
|
||||||
"cy:ci:report": "mochawesome-merge cypress/reports/*.json > cypress/reports/testReport.json && marge cypress/reports/testReport.json --reportDir cypress/reports --inline",
|
"cy:ci:report": "mochawesome-merge cypress/reports/*.json > cypress/reports/testReport.json && marge cypress/reports/testReport.json --reportDir cypress/reports --inline",
|
||||||
"cy:ci:notify": "node scripts/cypressResultsWebhook",
|
"cy:ci:notify": "node scripts/cypressResultsWebhook",
|
||||||
"cy:debug": "start-server-and-test cy:setup http://localhost:4100/builder cy:open",
|
"cy:debug": "start-server-and-test cy:setup http://localhost:4100/builder cy:open",
|
||||||
|
@ -69,10 +69,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.2.47",
|
"@budibase/bbui": "^1.2.57",
|
||||||
"@budibase/client": "^1.2.47",
|
"@budibase/client": "^1.2.57",
|
||||||
"@budibase/frontend-core": "^1.2.47",
|
"@budibase/frontend-core": "^1.2.57",
|
||||||
"@budibase/string-templates": "^1.2.47",
|
"@budibase/string-templates": "^1.2.57",
|
||||||
"@sentry/browser": "5.19.1",
|
"@sentry/browser": "5.19.1",
|
||||||
"@spectrum-css/page": "^3.0.1",
|
"@spectrum-css/page": "^3.0.1",
|
||||||
"@spectrum-css/vars": "^3.0.1",
|
"@spectrum-css/vars": "^3.0.1",
|
||||||
|
|
|
@ -5,7 +5,6 @@ const path = require("path")
|
||||||
const fs = require("fs")
|
const fs = require("fs")
|
||||||
|
|
||||||
const WEBHOOK_URL = process.env.CYPRESS_WEBHOOK_URL
|
const WEBHOOK_URL = process.env.CYPRESS_WEBHOOK_URL
|
||||||
const OUTCOME = process.env.CYPRESS_OUTCOME
|
|
||||||
const DASHBOARD_URL = process.env.CYPRESS_DASHBOARD_URL
|
const DASHBOARD_URL = process.env.CYPRESS_DASHBOARD_URL
|
||||||
const GIT_SHA = process.env.GITHUB_SHA
|
const GIT_SHA = process.env.GITHUB_SHA
|
||||||
const GITHUB_ACTIONS_RUN_URL = process.env.GITHUB_ACTIONS_RUN_URL
|
const GITHUB_ACTIONS_RUN_URL = process.env.GITHUB_ACTIONS_RUN_URL
|
||||||
|
@ -35,6 +34,8 @@ async function discordCypressResultsNotification(report) {
|
||||||
skipped,
|
skipped,
|
||||||
} = report.stats
|
} = report.stats
|
||||||
|
|
||||||
|
const OUTCOME = failures > 0 ? "failure" : "success"
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -114,7 +115,7 @@ async function discordCypressResultsNotification(report) {
|
||||||
}
|
}
|
||||||
const response = await fetch(WEBHOOK_URL, options)
|
const response = await fetch(WEBHOOK_URL, options)
|
||||||
|
|
||||||
if (response.status >= 400) {
|
if (response.status >= 201) {
|
||||||
const text = await response.text()
|
const text = await response.text()
|
||||||
console.error(
|
console.error(
|
||||||
`Error sending discord webhook. \nStatus: ${response.status}. \nResponse Body: ${text}. \nRequest Body: ${options.body}`
|
`Error sending discord webhook. \nStatus: ${response.status}. \nResponse Body: ${text}. \nRequest Body: ${options.body}`
|
||||||
|
|
|
@ -40,6 +40,7 @@ const INITIAL_FRONTEND_STATE = {
|
||||||
devicePreview: false,
|
devicePreview: false,
|
||||||
messagePassing: false,
|
messagePassing: false,
|
||||||
continueIfAction: false,
|
continueIfAction: false,
|
||||||
|
showNotificationAction: false,
|
||||||
},
|
},
|
||||||
errors: [],
|
errors: [],
|
||||||
hasAppPackage: false,
|
hasAppPackage: false,
|
||||||
|
@ -534,7 +535,16 @@ export const getFrontendStore = () => {
|
||||||
if (!component) {
|
if (!component) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let parentId
|
|
||||||
|
// Determine the next component to select after deletion
|
||||||
|
const state = get(store)
|
||||||
|
let nextSelectedComponentId
|
||||||
|
if (state.selectedComponentId === component._id) {
|
||||||
|
nextSelectedComponentId = store.actions.components.getNext()
|
||||||
|
if (!nextSelectedComponentId) {
|
||||||
|
nextSelectedComponentId = store.actions.components.getPrevious()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Patch screen
|
// Patch screen
|
||||||
await store.actions.screens.patch(screen => {
|
await store.actions.screens.patch(screen => {
|
||||||
|
@ -549,17 +559,18 @@ export const getFrontendStore = () => {
|
||||||
if (!parent) {
|
if (!parent) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
parentId = parent._id
|
|
||||||
parent._children = parent._children.filter(
|
parent._children = parent._children.filter(
|
||||||
child => child._id !== component._id
|
child => child._id !== component._id
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Select the deleted component's parent
|
// Update selected component if required
|
||||||
store.update(state => {
|
if (nextSelectedComponentId) {
|
||||||
state.selectedComponentId = parentId
|
store.update(state => {
|
||||||
return state
|
state.selectedComponentId = nextSelectedComponentId
|
||||||
})
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
copy: (component, cut = false, selectParent = true) => {
|
copy: (component, cut = false, selectParent = true) => {
|
||||||
// Update store with copied component
|
// Update store with copied component
|
||||||
|
@ -618,6 +629,16 @@ export const getFrontendStore = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check inside is valid
|
||||||
|
if (mode === "inside") {
|
||||||
|
const definition = store.actions.components.getDefinition(
|
||||||
|
targetComponent._component
|
||||||
|
)
|
||||||
|
if (!definition.hasChildren) {
|
||||||
|
mode = "below"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Paste new component
|
// Paste new component
|
||||||
if (mode === "inside") {
|
if (mode === "inside") {
|
||||||
// Paste inside target component if chosen
|
// Paste inside target component if chosen
|
||||||
|
@ -654,46 +675,193 @@ export const getFrontendStore = () => {
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
getPrevious: () => {
|
||||||
|
const state = get(store)
|
||||||
|
const componentId = state.selectedComponentId
|
||||||
|
const screen = get(selectedScreen)
|
||||||
|
const parent = findComponentParent(screen.props, componentId)
|
||||||
|
|
||||||
|
// Check we aren't right at the top of the tree
|
||||||
|
const index = parent?._children.findIndex(x => x._id === componentId)
|
||||||
|
if (!parent || componentId === screen.props._id) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have siblings above us, choose the sibling or a descendant
|
||||||
|
if (index > 0) {
|
||||||
|
// If sibling before us accepts children, select a descendant
|
||||||
|
const previousSibling = parent._children[index - 1]
|
||||||
|
if (previousSibling._children?.length) {
|
||||||
|
let target = previousSibling
|
||||||
|
while (target._children?.length) {
|
||||||
|
target = target._children[target._children.length - 1]
|
||||||
|
}
|
||||||
|
return target._id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise just select sibling
|
||||||
|
return previousSibling._id
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no siblings above us, select the parent
|
||||||
|
return parent._id
|
||||||
|
},
|
||||||
|
getNext: () => {
|
||||||
|
const component = get(selectedComponent)
|
||||||
|
const componentId = component?._id
|
||||||
|
const screen = get(selectedScreen)
|
||||||
|
const parent = findComponentParent(screen.props, componentId)
|
||||||
|
const index = parent?._children.findIndex(x => x._id === componentId)
|
||||||
|
|
||||||
|
// If we have children, select first child
|
||||||
|
if (component._children?.length) {
|
||||||
|
return component._children[0]._id
|
||||||
|
} else if (!parent) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise select the next sibling if we have one
|
||||||
|
if (index < parent._children.length - 1) {
|
||||||
|
const nextSibling = parent._children[index + 1]
|
||||||
|
return nextSibling._id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last child, select our parents next sibling
|
||||||
|
let target = parent
|
||||||
|
let targetParent = findComponentParent(screen.props, target._id)
|
||||||
|
let targetIndex = targetParent?._children.findIndex(
|
||||||
|
child => child._id === target._id
|
||||||
|
)
|
||||||
|
while (
|
||||||
|
targetParent != null &&
|
||||||
|
targetIndex === targetParent._children?.length - 1
|
||||||
|
) {
|
||||||
|
target = targetParent
|
||||||
|
targetParent = findComponentParent(screen.props, target._id)
|
||||||
|
targetIndex = targetParent?._children.findIndex(
|
||||||
|
child => child._id === target._id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (targetParent) {
|
||||||
|
return targetParent._children[targetIndex + 1]._id
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectPrevious: () => {
|
||||||
|
const previousId = store.actions.components.getPrevious()
|
||||||
|
if (previousId) {
|
||||||
|
store.update(state => {
|
||||||
|
state.selectedComponentId = previousId
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectNext: () => {
|
||||||
|
const nextId = store.actions.components.getNext()
|
||||||
|
if (nextId) {
|
||||||
|
store.update(state => {
|
||||||
|
state.selectedComponentId = nextId
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
moveUp: async component => {
|
moveUp: async component => {
|
||||||
await store.actions.screens.patch(screen => {
|
await store.actions.screens.patch(screen => {
|
||||||
const componentId = component?._id
|
const componentId = component?._id
|
||||||
const parent = findComponentParent(screen.props, componentId)
|
const parent = findComponentParent(screen.props, componentId)
|
||||||
if (!parent?._children?.length) {
|
|
||||||
return false
|
// Check we aren't right at the top of the tree
|
||||||
|
const index = parent?._children.findIndex(x => x._id === componentId)
|
||||||
|
if (!parent || (index === 0 && parent._id === screen.props._id)) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
const currentIndex = parent._children.findIndex(
|
|
||||||
child => child._id === componentId
|
// Copy original component and remove it from the parent
|
||||||
)
|
const originalComponent = cloneDeep(parent._children[index])
|
||||||
if (currentIndex === 0) {
|
parent._children = parent._children.filter(
|
||||||
return false
|
|
||||||
}
|
|
||||||
const originalComponent = cloneDeep(parent._children[currentIndex])
|
|
||||||
const newChildren = parent._children.filter(
|
|
||||||
component => component._id !== componentId
|
component => component._id !== componentId
|
||||||
)
|
)
|
||||||
newChildren.splice(currentIndex - 1, 0, originalComponent)
|
|
||||||
parent._children = newChildren
|
// If we have siblings above us, move up
|
||||||
|
if (index > 0) {
|
||||||
|
// If sibling before us accepts children, move to last child of
|
||||||
|
// sibling
|
||||||
|
const previousSibling = parent._children[index - 1]
|
||||||
|
const definition = store.actions.components.getDefinition(
|
||||||
|
previousSibling._component
|
||||||
|
)
|
||||||
|
if (definition.hasChildren) {
|
||||||
|
previousSibling._children.push(originalComponent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise just move component above sibling
|
||||||
|
else {
|
||||||
|
parent._children.splice(index - 1, 0, originalComponent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no siblings above us, go above the parent as long as it isn't
|
||||||
|
// the screen
|
||||||
|
else if (parent._id !== screen.props._id) {
|
||||||
|
const grandParent = findComponentParent(screen.props, parent._id)
|
||||||
|
const parentIndex = grandParent._children.findIndex(
|
||||||
|
child => child._id === parent._id
|
||||||
|
)
|
||||||
|
grandParent._children.splice(parentIndex, 0, originalComponent)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
moveDown: async component => {
|
moveDown: async component => {
|
||||||
await store.actions.screens.patch(screen => {
|
await store.actions.screens.patch(screen => {
|
||||||
const componentId = component?._id
|
const componentId = component?._id
|
||||||
const parent = findComponentParent(screen.props, componentId)
|
const parent = findComponentParent(screen.props, componentId)
|
||||||
|
|
||||||
|
// Sanity check parent is found
|
||||||
if (!parent?._children?.length) {
|
if (!parent?._children?.length) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const currentIndex = parent._children.findIndex(
|
|
||||||
child => child._id === componentId
|
// Check we aren't right at the bottom of the tree
|
||||||
)
|
const index = parent._children.findIndex(x => x._id === componentId)
|
||||||
if (currentIndex === parent._children.length - 1) {
|
if (
|
||||||
return false
|
index === parent._children.length - 1 &&
|
||||||
|
parent._id === screen.props._id
|
||||||
|
) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
const originalComponent = cloneDeep(parent._children[currentIndex])
|
|
||||||
const newChildren = parent._children.filter(
|
// Copy the original component and remove from parent
|
||||||
|
const originalComponent = cloneDeep(parent._children[index])
|
||||||
|
parent._children = parent._children.filter(
|
||||||
component => component._id !== componentId
|
component => component._id !== componentId
|
||||||
)
|
)
|
||||||
newChildren.splice(currentIndex + 1, 0, originalComponent)
|
|
||||||
parent._children = newChildren
|
// Move below the next sibling if we are not the last sibling
|
||||||
|
if (index < parent._children.length) {
|
||||||
|
// If the next sibling has children, become the first child
|
||||||
|
const nextSibling = parent._children[index]
|
||||||
|
const definition = store.actions.components.getDefinition(
|
||||||
|
nextSibling._component
|
||||||
|
)
|
||||||
|
if (definition.hasChildren) {
|
||||||
|
nextSibling._children.splice(0, 0, originalComponent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise move below next sibling
|
||||||
|
else {
|
||||||
|
parent._children.splice(index + 1, 0, originalComponent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last child, so move below our parent
|
||||||
|
else {
|
||||||
|
const grandParent = findComponentParent(screen.props, parent._id)
|
||||||
|
const parentIndex = grandParent._children.findIndex(
|
||||||
|
child => child._id === parent._id
|
||||||
|
)
|
||||||
|
grandParent._children.splice(parentIndex + 1, 0, originalComponent)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
updateStyle: async (name, value) => {
|
updateStyle: async (name, value) => {
|
||||||
|
|
|
@ -162,7 +162,7 @@
|
||||||
width="28px"
|
width="28px"
|
||||||
height="28px"
|
height="28px"
|
||||||
class="spectrum-Icon"
|
class="spectrum-Icon"
|
||||||
style="color:grey;"
|
style="color:var(--spectrum-global-color-gray-700);"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
>
|
>
|
||||||
<use xlink:href="#spectrum-icon-18-Reuse" />
|
<use xlink:href="#spectrum-icon-18-Reuse" />
|
||||||
|
|
|
@ -64,7 +64,7 @@
|
||||||
width="28px"
|
width="28px"
|
||||||
height="28px"
|
height="28px"
|
||||||
class="spectrum-Icon"
|
class="spectrum-Icon"
|
||||||
style="color:grey;"
|
style="color:var(--spectrum-global-color-gray-700);"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
>
|
>
|
||||||
<use xlink:href="#spectrum-icon-18-{block.icon}" />
|
<use xlink:href="#spectrum-icon-18-{block.icon}" />
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
$: {
|
$: {
|
||||||
let fields = {}
|
let fields = {}
|
||||||
|
|
||||||
for (const [key, type] of Object.entries(block?.inputs?.fields)) {
|
for (const [key, type] of Object.entries(block?.inputs?.fields ?? {})) {
|
||||||
fields = {
|
fields = {
|
||||||
...fields,
|
...fields,
|
||||||
[key]: {
|
[key]: {
|
||||||
|
|
|
@ -167,6 +167,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
<HideAutocolumnButton bind:hideAutocolumns />
|
<HideAutocolumnButton bind:hideAutocolumns />
|
||||||
<ImportButton
|
<ImportButton
|
||||||
|
disabled={$tables.selected?._id === "ta_users"}
|
||||||
tableId={$tables.selected?._id}
|
tableId={$tables.selected?._id}
|
||||||
on:updaterows={onUpdateRows}
|
on:updaterows={onUpdateRows}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -3,11 +3,12 @@
|
||||||
import ImportModal from "../modals/ImportModal.svelte"
|
import ImportModal from "../modals/ImportModal.svelte"
|
||||||
|
|
||||||
export let tableId
|
export let tableId
|
||||||
|
export let disabled
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionButton icon="DataUpload" size="S" quiet on:click={modal.show}>
|
<ActionButton icon="DataUpload" size="S" quiet on:click={modal.show} {disabled}>
|
||||||
Import
|
Import
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
Modal,
|
Modal,
|
||||||
notifications,
|
notifications,
|
||||||
ProgressCircle,
|
ProgressCircle,
|
||||||
|
Layout,
|
||||||
|
Body,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { auth, apps } from "stores/portal"
|
import { auth, apps } from "stores/portal"
|
||||||
import { processStringSync } from "@budibase/string-templates"
|
import { processStringSync } from "@budibase/string-templates"
|
||||||
|
@ -72,62 +74,67 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Modal bind:this={appLockModal}>
|
{#key app}
|
||||||
<ModalContent
|
<div>
|
||||||
title={lockedByHeading}
|
<Modal bind:this={appLockModal}>
|
||||||
dataCy={"app-lock-modal"}
|
<ModalContent
|
||||||
showConfirmButton={false}
|
title={lockedByHeading}
|
||||||
showCancelButton={false}
|
dataCy={"app-lock-modal"}
|
||||||
>
|
showConfirmButton={false}
|
||||||
<p>
|
showCancelButton={false}
|
||||||
Apps are locked to prevent work from being lost from overlapping changes
|
>
|
||||||
between your team.
|
<Layout noPadding>
|
||||||
</p>
|
<Body size="S">
|
||||||
|
Apps are locked to prevent work from being lost from overlapping
|
||||||
{#if lockedByYou && getExpiryDuration(app) > 0}
|
changes between your team.
|
||||||
<span class="lock-expiry-body">
|
</Body>
|
||||||
{processStringSync(
|
{#if lockedByYou && getExpiryDuration(app) > 0}
|
||||||
"This lock will expire in {{ duration time 'millisecond' }} from now.",
|
<span class="lock-expiry-body">
|
||||||
{
|
{processStringSync(
|
||||||
time: getExpiryDuration(app),
|
"This lock will expire in {{ duration time 'millisecond' }} from now. This lock will expire in This lock will expire in ",
|
||||||
}
|
{
|
||||||
)}
|
time: getExpiryDuration(app),
|
||||||
</span>
|
}
|
||||||
{/if}
|
)}
|
||||||
<div class="lock-modal-actions">
|
</span>
|
||||||
<ButtonGroup>
|
{/if}
|
||||||
<Button
|
<div class="lock-modal-actions">
|
||||||
secondary
|
<ButtonGroup>
|
||||||
quiet={lockedBy && lockedByYou}
|
<Button
|
||||||
disabled={processing}
|
secondary
|
||||||
on:click={() => {
|
quiet={lockedBy && lockedByYou}
|
||||||
appLockModal.hide()
|
disabled={processing}
|
||||||
}}
|
on:click={() => {
|
||||||
>
|
appLockModal.hide()
|
||||||
<span class="cancel"
|
}}
|
||||||
>{lockedBy && !lockedByYou ? "Done" : "Cancel"}</span
|
>
|
||||||
>
|
<span class="cancel"
|
||||||
</Button>
|
>{lockedBy && !lockedByYou ? "Done" : "Cancel"}</span
|
||||||
{#if lockedByYou}
|
>
|
||||||
<Button
|
</Button>
|
||||||
secondary
|
{#if lockedByYou}
|
||||||
disabled={processing}
|
<Button
|
||||||
on:click={() => {
|
secondary
|
||||||
releaseLock()
|
disabled={processing}
|
||||||
appLockModal.hide()
|
on:click={() => {
|
||||||
}}
|
releaseLock()
|
||||||
>
|
appLockModal.hide()
|
||||||
{#if processing}
|
}}
|
||||||
<ProgressCircle overBackground={true} size="S" />
|
>
|
||||||
{:else}
|
{#if processing}
|
||||||
<span class="unlock">Release Lock</span>
|
<ProgressCircle overBackground={true} size="S" />
|
||||||
{/if}
|
{:else}
|
||||||
</Button>
|
<span class="unlock">Release Lock</span>
|
||||||
{/if}
|
{/if}
|
||||||
</ButtonGroup>
|
</Button>
|
||||||
</div>
|
{/if}
|
||||||
</ModalContent>
|
</ButtonGroup>
|
||||||
</Modal>
|
</div>
|
||||||
|
</Layout>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
{/key}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.lock-modal-actions {
|
.lock-modal-actions {
|
||||||
|
|
|
@ -27,6 +27,14 @@
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteAttachments(fileList) {
|
||||||
|
try {
|
||||||
|
return await API.deleteBuilderAttachments(fileList)
|
||||||
|
} catch (error) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Dropzone
|
<Dropzone
|
||||||
|
@ -34,5 +42,6 @@
|
||||||
{label}
|
{label}
|
||||||
{...$$restProps}
|
{...$$restProps}
|
||||||
{processFiles}
|
{processFiles}
|
||||||
|
{deleteAttachments}
|
||||||
{handleFileTooLarge}
|
{handleFileTooLarge}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
Tab,
|
Tab,
|
||||||
Body,
|
Body,
|
||||||
Layout,
|
Layout,
|
||||||
|
Button,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { createEventDispatcher, onMount } from "svelte"
|
import { createEventDispatcher, onMount } from "svelte"
|
||||||
import {
|
import {
|
||||||
|
@ -15,10 +16,15 @@
|
||||||
decodeJSBinding,
|
decodeJSBinding,
|
||||||
encodeJSBinding,
|
encodeJSBinding,
|
||||||
} from "@budibase/string-templates"
|
} from "@budibase/string-templates"
|
||||||
import { readableToRuntimeBinding } from "builderStore/dataBinding"
|
import {
|
||||||
|
readableToRuntimeBinding,
|
||||||
|
runtimeToReadableBinding,
|
||||||
|
} from "builderStore/dataBinding"
|
||||||
import { handlebarsCompletions } from "constants/completions"
|
import { handlebarsCompletions } from "constants/completions"
|
||||||
import { addHBSBinding, addJSBinding } from "./utils"
|
import { addHBSBinding, addJSBinding } from "./utils"
|
||||||
import CodeMirrorEditor from "components/common/CodeMirrorEditor.svelte"
|
import CodeMirrorEditor from "components/common/CodeMirrorEditor.svelte"
|
||||||
|
import { convertToJS } from "@budibase/string-templates"
|
||||||
|
import { admin } from "stores/portal"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -62,15 +68,24 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a HBS helper to the expression
|
// Adds a JS/HBS helper to the expression
|
||||||
const addHelper = helper => {
|
const addHelper = (helper, js) => {
|
||||||
hbsValue = addHBSBinding(hbsValue, getCaretPosition(), helper.text)
|
let tempVal
|
||||||
updateValue(hbsValue)
|
const pos = getCaretPosition()
|
||||||
|
if (js) {
|
||||||
|
const decoded = decodeJSBinding(jsValue)
|
||||||
|
tempVal = jsValue = encodeJSBinding(
|
||||||
|
addJSBinding(decoded, pos, helper.text, { helper: true })
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
tempVal = hbsValue = addHBSBinding(hbsValue, pos, helper.text)
|
||||||
|
}
|
||||||
|
updateValue(tempVal)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a data binding to the expression
|
// Adds a data binding to the expression
|
||||||
const addBinding = binding => {
|
const addBinding = (binding, { forceJS } = {}) => {
|
||||||
if (usingJS) {
|
if (usingJS || forceJS) {
|
||||||
let js = decodeJSBinding(jsValue)
|
let js = decodeJSBinding(jsValue)
|
||||||
js = addJSBinding(js, getCaretPosition(), binding.readableBinding)
|
js = addJSBinding(js, getCaretPosition(), binding.readableBinding)
|
||||||
jsValue = encodeJSBinding(js)
|
jsValue = encodeJSBinding(js)
|
||||||
|
@ -100,6 +115,26 @@
|
||||||
updateValue(jsValue)
|
updateValue(jsValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const convert = () => {
|
||||||
|
const runtime = readableToRuntimeBinding(bindings, hbsValue)
|
||||||
|
const runtimeJs = encodeJSBinding(convertToJS(runtime))
|
||||||
|
jsValue = runtimeToReadableBinding(bindings, runtimeJs)
|
||||||
|
hbsValue = null
|
||||||
|
mode = "JavaScript"
|
||||||
|
addBinding("", { forceJS: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
const getHelperExample = (helper, js) => {
|
||||||
|
let example = helper.example || ""
|
||||||
|
if (js) {
|
||||||
|
example = convertToJS(example).split("\n")[0].split("= ")[1]
|
||||||
|
if (example === "null;") {
|
||||||
|
example = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return example || ""
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
valid = isValid(readableToRuntimeBinding(bindings, value))
|
valid = isValid(readableToRuntimeBinding(bindings, value))
|
||||||
})
|
})
|
||||||
|
@ -135,18 +170,21 @@
|
||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
{#if filteredHelpers?.length && !usingJS}
|
{#if filteredHelpers?.length}
|
||||||
<section>
|
<section>
|
||||||
<div class="heading">Helpers</div>
|
<div class="heading">Helpers</div>
|
||||||
<ul>
|
<ul>
|
||||||
{#each filteredHelpers as helper}
|
{#each filteredHelpers as helper}
|
||||||
<li on:click={() => addHelper(helper)}>
|
<li on:click={() => addHelper(helper, usingJS)}>
|
||||||
<div class="helper">
|
<div class="helper">
|
||||||
<div class="helper__name">{helper.displayText}</div>
|
<div class="helper__name">{helper.displayText}</div>
|
||||||
<div class="helper__description">
|
<div class="helper__description">
|
||||||
{@html helper.description}
|
{@html helper.description}
|
||||||
</div>
|
</div>
|
||||||
<pre class="helper__example">{helper.example || ""}</pre>
|
<pre class="helper__example">{getHelperExample(
|
||||||
|
helper,
|
||||||
|
usingJS
|
||||||
|
)}</pre>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -172,6 +210,11 @@
|
||||||
for more details.
|
for more details.
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if $admin.isDev}
|
||||||
|
<div class="convert">
|
||||||
|
<Button secondary on:click={convert}>Convert to JS</Button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</Tab>
|
</Tab>
|
||||||
{#if allowJS}
|
{#if allowJS}
|
||||||
|
@ -306,4 +349,8 @@
|
||||||
color: var(--red);
|
color: var(--red);
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.convert {
|
||||||
|
padding-top: var(--spacing-m);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -18,10 +18,14 @@ export function addHBSBinding(value, caretPos, binding) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addJSBinding(value, caretPos, binding) {
|
export function addJSBinding(value, caretPos, binding, { helper } = {}) {
|
||||||
binding = typeof binding === "string" ? binding : binding.path
|
binding = typeof binding === "string" ? binding : binding.path
|
||||||
value = value == null ? "" : value
|
value = value == null ? "" : value
|
||||||
binding = `$("${binding}")`
|
if (!helper) {
|
||||||
|
binding = `$("${binding}")`
|
||||||
|
} else {
|
||||||
|
binding = `helper.${binding}()`
|
||||||
|
}
|
||||||
if (caretPos.start) {
|
if (caretPos.start) {
|
||||||
value =
|
value =
|
||||||
value.substring(0, caretPos.start) +
|
value.substring(0, caretPos.start) +
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
<script>
|
||||||
|
import { Select, Label, Checkbox } from "@budibase/bbui"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
|
|
||||||
|
export let parameters
|
||||||
|
export let bindings = []
|
||||||
|
|
||||||
|
const types = [
|
||||||
|
{
|
||||||
|
label: "Success",
|
||||||
|
value: "success",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Warning",
|
||||||
|
value: "warning",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Error",
|
||||||
|
value: "error",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Info",
|
||||||
|
value: "info",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (!parameters.type) {
|
||||||
|
parameters.type = "success"
|
||||||
|
}
|
||||||
|
if (parameters.autoDismiss == null) {
|
||||||
|
parameters.autoDismiss = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="root">
|
||||||
|
<Label>Type</Label>
|
||||||
|
<Select bind:value={parameters.type} options={types} placeholder={null} />
|
||||||
|
<Label>Message</Label>
|
||||||
|
<DrawerBindableInput
|
||||||
|
{bindings}
|
||||||
|
value={parameters.message}
|
||||||
|
on:change={e => (parameters.message = e.detail)}
|
||||||
|
/>
|
||||||
|
<Label />
|
||||||
|
<Checkbox text="Auto dismiss" bind:value={parameters.autoDismiss} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.root {
|
||||||
|
display: grid;
|
||||||
|
column-gap: var(--spacing-l);
|
||||||
|
row-gap: var(--spacing-s);
|
||||||
|
grid-template-columns: 60px 1fr;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -15,3 +15,4 @@ export { default as S3Upload } from "./S3Upload.svelte"
|
||||||
export { default as ExportData } from "./ExportData.svelte"
|
export { default as ExportData } from "./ExportData.svelte"
|
||||||
export { default as ContinueIf } from "./ContinueIf.svelte"
|
export { default as ContinueIf } from "./ContinueIf.svelte"
|
||||||
export { default as UpdateFieldValue } from "./UpdateFieldValue.svelte"
|
export { default as UpdateFieldValue } from "./UpdateFieldValue.svelte"
|
||||||
|
export { default as ShowNotification } from "./ShowNotification.svelte"
|
||||||
|
|
|
@ -110,6 +110,12 @@
|
||||||
"type": "logic",
|
"type": "logic",
|
||||||
"component": "ContinueIf",
|
"component": "ContinueIf",
|
||||||
"dependsOnFeature": "continueIfAction"
|
"dependsOnFeature": "continueIfAction"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Show Notification",
|
||||||
|
"type": "application",
|
||||||
|
"component": "ShowNotification",
|
||||||
|
"dependsOnFeature": "showNotificationAction"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -3,29 +3,41 @@
|
||||||
Body,
|
Body,
|
||||||
Button,
|
Button,
|
||||||
Combobox,
|
Combobox,
|
||||||
|
Multiselect,
|
||||||
DatePicker,
|
DatePicker,
|
||||||
DrawerContent,
|
DrawerContent,
|
||||||
Icon,
|
Icon,
|
||||||
Input,
|
Input,
|
||||||
Layout,
|
Layout,
|
||||||
Select,
|
Select,
|
||||||
|
Label,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
import ClientBindingPanel from "components/common/bindings/ClientBindingPanel.svelte"
|
import ClientBindingPanel from "components/common/bindings/ClientBindingPanel.svelte"
|
||||||
import { generate } from "shortid"
|
import { generate } from "shortid"
|
||||||
import { LuceneUtils, Constants } from "@budibase/frontend-core"
|
import { LuceneUtils, Constants } from "@budibase/frontend-core"
|
||||||
import { getFields } from "helpers/searchFields"
|
import { getFields } from "helpers/searchFields"
|
||||||
|
import { createEventDispatcher, onMount } from "svelte"
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
export let schemaFields
|
export let schemaFields
|
||||||
export let filters = []
|
export let filters = []
|
||||||
export let bindings = []
|
export let bindings = []
|
||||||
export let panel = ClientBindingPanel
|
export let panel = ClientBindingPanel
|
||||||
export let allowBindings = true
|
export let allowBindings = true
|
||||||
|
export let allOr = false
|
||||||
|
|
||||||
|
$: dispatch("change", filters)
|
||||||
$: enrichedSchemaFields = getFields(schemaFields || [])
|
$: enrichedSchemaFields = getFields(schemaFields || [])
|
||||||
$: fieldOptions = enrichedSchemaFields.map(field => field.name) || []
|
$: fieldOptions = enrichedSchemaFields.map(field => field.name) || []
|
||||||
$: valueTypeOptions = allowBindings ? ["Value", "Binding"] : ["Value"]
|
$: valueTypeOptions = allowBindings ? ["Value", "Binding"] : ["Value"]
|
||||||
|
|
||||||
|
let behaviourValue
|
||||||
|
const behaviourOptions = [
|
||||||
|
{ value: "and", label: "Match all of the following filters" },
|
||||||
|
{ value: "or", label: "Match any of the following filters" },
|
||||||
|
]
|
||||||
const addFilter = () => {
|
const addFilter = () => {
|
||||||
filters = [
|
filters = [
|
||||||
...filters,
|
...filters,
|
||||||
|
@ -69,7 +81,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// if changed to an array, change default value to empty array
|
// if changed to an array, change default value to empty array
|
||||||
const idx = filters.findIndex(x => x.field === field)
|
const idx = filters.findIndex(x => x.id === expression.id)
|
||||||
if (expression.type === "array") {
|
if (expression.type === "array") {
|
||||||
filters[idx].value = []
|
filters[idx].value = []
|
||||||
} else {
|
} else {
|
||||||
|
@ -86,12 +98,26 @@
|
||||||
if (expression.noValue) {
|
if (expression.noValue) {
|
||||||
expression.value = null
|
expression.value = null
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
operator === Constants.OperatorOptions.In.value &&
|
||||||
|
!Array.isArray(expression.value)
|
||||||
|
) {
|
||||||
|
if (expression.value) {
|
||||||
|
expression.value = [expression.value]
|
||||||
|
} else {
|
||||||
|
expression.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFieldOptions = field => {
|
const getFieldOptions = field => {
|
||||||
const schema = enrichedSchemaFields.find(x => x.name === field)
|
const schema = enrichedSchemaFields.find(x => x.name === field)
|
||||||
return schema?.constraints?.inclusion || []
|
return schema?.constraints?.inclusion || []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
behaviourValue = allOr ? "or" : "and"
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DrawerContent>
|
<DrawerContent>
|
||||||
|
@ -107,79 +133,101 @@
|
||||||
</Body>
|
</Body>
|
||||||
{#if filters?.length}
|
{#if filters?.length}
|
||||||
<div class="fields">
|
<div class="fields">
|
||||||
{#each filters as filter, idx}
|
<Select
|
||||||
<Select
|
label="Behaviour"
|
||||||
bind:value={filter.field}
|
value={behaviourValue}
|
||||||
options={fieldOptions}
|
options={behaviourOptions}
|
||||||
on:change={e => onFieldChange(filter, e.detail)}
|
getOptionLabel={opt => opt.label}
|
||||||
placeholder="Column"
|
getOptionValue={opt => opt.value}
|
||||||
/>
|
on:change={e => (allOr = e.detail === "or")}
|
||||||
<Select
|
placeholder={null}
|
||||||
disabled={!filter.field}
|
/>
|
||||||
options={LuceneUtils.getValidOperatorsForType(filter.type)}
|
</div>
|
||||||
bind:value={filter.operator}
|
<div>
|
||||||
on:change={e => onOperatorChange(filter, e.detail)}
|
<div class="filter-label">
|
||||||
placeholder={null}
|
<Label>Filters</Label>
|
||||||
/>
|
</div>
|
||||||
<Select
|
<div class="fields">
|
||||||
disabled={filter.noValue || !filter.field}
|
{#each filters as filter, idx}
|
||||||
options={valueTypeOptions}
|
<Select
|
||||||
bind:value={filter.valueType}
|
bind:value={filter.field}
|
||||||
placeholder={null}
|
options={fieldOptions}
|
||||||
/>
|
on:change={e => onFieldChange(filter, e.detail)}
|
||||||
{#if filter.valueType === "Binding"}
|
placeholder="Column"
|
||||||
<DrawerBindableInput
|
|
||||||
disabled={filter.noValue}
|
|
||||||
title={`Value for "${filter.field}"`}
|
|
||||||
value={filter.value}
|
|
||||||
placeholder="Value"
|
|
||||||
{panel}
|
|
||||||
{bindings}
|
|
||||||
on:change={event => (filter.value = event.detail)}
|
|
||||||
/>
|
/>
|
||||||
{:else if ["string", "longform", "number", "formula"].includes(filter.type)}
|
<Select
|
||||||
<Input disabled={filter.noValue} bind:value={filter.value} />
|
disabled={!filter.field}
|
||||||
{:else if ["options", "array"].includes(filter.type)}
|
options={LuceneUtils.getValidOperatorsForType(filter.type)}
|
||||||
<Combobox
|
bind:value={filter.operator}
|
||||||
disabled={filter.noValue}
|
on:change={e => onOperatorChange(filter, e.detail)}
|
||||||
options={getFieldOptions(filter.field)}
|
placeholder={null}
|
||||||
bind:value={filter.value}
|
|
||||||
/>
|
/>
|
||||||
{:else if filter.type === "boolean"}
|
<Select
|
||||||
<Combobox
|
disabled={filter.noValue || !filter.field}
|
||||||
disabled={filter.noValue}
|
options={valueTypeOptions}
|
||||||
options={[
|
bind:value={filter.valueType}
|
||||||
{ label: "True", value: "true" },
|
placeholder={null}
|
||||||
{ label: "False", value: "false" },
|
|
||||||
]}
|
|
||||||
bind:value={filter.value}
|
|
||||||
/>
|
/>
|
||||||
{:else if filter.type === "datetime"}
|
{#if filter.valueType === "Binding"}
|
||||||
<DatePicker
|
<DrawerBindableInput
|
||||||
disabled={filter.noValue}
|
disabled={filter.noValue}
|
||||||
enableTime={!getSchema(filter).dateOnly}
|
title={`Value for "${filter.field}"`}
|
||||||
timeOnly={getSchema(filter).timeOnly}
|
value={filter.value}
|
||||||
bind:value={filter.value}
|
placeholder="Value"
|
||||||
|
{panel}
|
||||||
|
{bindings}
|
||||||
|
on:change={event => (filter.value = event.detail)}
|
||||||
|
/>
|
||||||
|
{:else if ["string", "longform", "number", "formula"].includes(filter.type)}
|
||||||
|
<Input disabled={filter.noValue} bind:value={filter.value} />
|
||||||
|
{:else if filter.type === "array" || (filter.type === "options" && filter.operator === "oneOf")}
|
||||||
|
<Multiselect
|
||||||
|
disabled={filter.noValue}
|
||||||
|
options={getFieldOptions(filter.field)}
|
||||||
|
bind:value={filter.value}
|
||||||
|
/>
|
||||||
|
{:else if filter.type === "options"}
|
||||||
|
<Combobox
|
||||||
|
disabled={filter.noValue}
|
||||||
|
options={getFieldOptions(filter.field)}
|
||||||
|
bind:value={filter.value}
|
||||||
|
/>
|
||||||
|
{:else if filter.type === "boolean"}
|
||||||
|
<Combobox
|
||||||
|
disabled={filter.noValue}
|
||||||
|
options={[
|
||||||
|
{ label: "True", value: "true" },
|
||||||
|
{ label: "False", value: "false" },
|
||||||
|
]}
|
||||||
|
bind:value={filter.value}
|
||||||
|
/>
|
||||||
|
{:else if filter.type === "datetime"}
|
||||||
|
<DatePicker
|
||||||
|
disabled={filter.noValue}
|
||||||
|
enableTime={!getSchema(filter).dateOnly}
|
||||||
|
timeOnly={getSchema(filter).timeOnly}
|
||||||
|
bind:value={filter.value}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<DrawerBindableInput disabled />
|
||||||
|
{/if}
|
||||||
|
<Icon
|
||||||
|
name="Duplicate"
|
||||||
|
hoverable
|
||||||
|
size="S"
|
||||||
|
on:click={() => duplicateFilter(filter.id)}
|
||||||
/>
|
/>
|
||||||
{:else}
|
<Icon
|
||||||
<DrawerBindableInput disabled />
|
name="Close"
|
||||||
{/if}
|
hoverable
|
||||||
<Icon
|
size="S"
|
||||||
name="Duplicate"
|
on:click={() => removeFilter(filter.id)}
|
||||||
hoverable
|
/>
|
||||||
size="S"
|
{/each}
|
||||||
on:click={() => duplicateFilter(filter.id)}
|
</div>
|
||||||
/>
|
|
||||||
<Icon
|
|
||||||
name="Close"
|
|
||||||
hoverable
|
|
||||||
size="S"
|
|
||||||
on:click={() => removeFilter(filter.id)}
|
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div>
|
<div class="bottom">
|
||||||
<Button icon="AddCircle" size="M" secondary on:click={addFilter}>
|
<Button icon="AddCircle" size="M" secondary on:click={addFilter}>
|
||||||
Add filter
|
Add filter
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -202,4 +250,14 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
grid-template-columns: 1fr 120px 120px 1fr auto auto;
|
grid-template-columns: 1fr 120px 120px 1fr auto auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filter-label {
|
||||||
|
margin-bottom: var(--spacing-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -8,21 +8,73 @@
|
||||||
import FilterDrawer from "./FilterDrawer.svelte"
|
import FilterDrawer from "./FilterDrawer.svelte"
|
||||||
import { currentAsset } from "builderStore"
|
import { currentAsset } from "builderStore"
|
||||||
|
|
||||||
|
const QUERY_START_REGEX = /\d[0-9]*:/g
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
export let value = []
|
export let value = []
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
export let bindings = []
|
export let bindings = []
|
||||||
|
|
||||||
let drawer
|
let drawer,
|
||||||
let tempValue = value || []
|
toSaveFilters = null,
|
||||||
|
allOr,
|
||||||
|
initialAllOr
|
||||||
|
|
||||||
|
$: initialFilters = correctFilters(value || [])
|
||||||
$: dataSource = getDatasourceForProvider($currentAsset, componentInstance)
|
$: dataSource = getDatasourceForProvider($currentAsset, componentInstance)
|
||||||
$: schema = getSchemaForDatasource($currentAsset, dataSource)?.schema
|
$: schema = getSchemaForDatasource($currentAsset, dataSource)?.schema
|
||||||
$: schemaFields = Object.values(schema || {})
|
$: schemaFields = Object.values(schema || {})
|
||||||
|
|
||||||
const saveFilter = async () => {
|
function addNumbering(filters) {
|
||||||
dispatch("change", tempValue)
|
let count = 1
|
||||||
|
for (let value of filters) {
|
||||||
|
if (value.field && value.field?.match(QUERY_START_REGEX) == null) {
|
||||||
|
value.field = `${count++}:${value.field}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filters
|
||||||
|
}
|
||||||
|
|
||||||
|
function correctFilters(filters) {
|
||||||
|
const corrected = []
|
||||||
|
for (let filter of filters) {
|
||||||
|
let field = filter.field
|
||||||
|
if (filter.operator === "allOr") {
|
||||||
|
initialAllOr = allOr = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
typeof filter.field === "string" &&
|
||||||
|
filter.field.match(QUERY_START_REGEX) != null
|
||||||
|
) {
|
||||||
|
const parts = field.split(":")
|
||||||
|
const number = parts[0]
|
||||||
|
// it's the new format, remove number
|
||||||
|
if (!isNaN(parseInt(number))) {
|
||||||
|
parts.shift()
|
||||||
|
field = parts.join(":")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
corrected.push({
|
||||||
|
...filter,
|
||||||
|
field,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return corrected
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveFilter() {
|
||||||
|
if (!toSaveFilters && allOr !== initialAllOr) {
|
||||||
|
toSaveFilters = initialFilters
|
||||||
|
}
|
||||||
|
const filters = toSaveFilters?.filter(filter => filter.operator !== "allOr")
|
||||||
|
if (allOr && filters) {
|
||||||
|
filters.push({ operator: "allOr" })
|
||||||
|
}
|
||||||
|
// only save if anything was updated
|
||||||
|
if (filters) {
|
||||||
|
dispatch("change", addNumbering(filters))
|
||||||
|
}
|
||||||
notifications.success("Filters saved.")
|
notifications.success("Filters saved.")
|
||||||
drawer.hide()
|
drawer.hide()
|
||||||
}
|
}
|
||||||
|
@ -33,8 +85,12 @@
|
||||||
<Button cta slot="buttons" on:click={saveFilter}>Save</Button>
|
<Button cta slot="buttons" on:click={saveFilter}>Save</Button>
|
||||||
<FilterDrawer
|
<FilterDrawer
|
||||||
slot="body"
|
slot="body"
|
||||||
bind:filters={tempValue}
|
filters={initialFilters}
|
||||||
{bindings}
|
{bindings}
|
||||||
{schemaFields}
|
{schemaFields}
|
||||||
|
bind:allOr
|
||||||
|
on:change={event => {
|
||||||
|
toSaveFilters = event.detail
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="desktop">
|
<div class="desktop">
|
||||||
<AppLockModal {app} buttonSize="M" />
|
<span><AppLockModal {app} buttonSize="M" /></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="desktop">
|
<div class="desktop">
|
||||||
<div class="app-status">
|
<div class="app-status">
|
||||||
|
|
|
@ -55,13 +55,16 @@
|
||||||
let saveId, url
|
let saveId, url
|
||||||
let response, schema, enabledHeaders
|
let response, schema, enabledHeaders
|
||||||
let authConfigId
|
let authConfigId
|
||||||
let dynamicVariables, addVariableModal, varBinding
|
let dynamicVariables, addVariableModal, varBinding, globalDynamicBindings
|
||||||
let restBindings = getRestBindings()
|
let restBindings = getRestBindings()
|
||||||
|
|
||||||
$: staticVariables = datasource?.config?.staticVariables || {}
|
$: staticVariables = datasource?.config?.staticVariables || {}
|
||||||
|
|
||||||
$: customRequestBindings = toBindingsArray(requestBindings, "Binding")
|
$: customRequestBindings = toBindingsArray(requestBindings, "Binding")
|
||||||
$: dynamicRequestBindings = toBindingsArray(dynamicVariables, "Dynamic")
|
$: globalDynamicRequestBindings = toBindingsArray(
|
||||||
|
globalDynamicBindings,
|
||||||
|
"Dynamic"
|
||||||
|
)
|
||||||
$: dataSourceStaticBindings = toBindingsArray(
|
$: dataSourceStaticBindings = toBindingsArray(
|
||||||
staticVariables,
|
staticVariables,
|
||||||
"Datasource.Static"
|
"Datasource.Static"
|
||||||
|
@ -70,7 +73,7 @@
|
||||||
$: mergedBindings = [
|
$: mergedBindings = [
|
||||||
...restBindings,
|
...restBindings,
|
||||||
...customRequestBindings,
|
...customRequestBindings,
|
||||||
...dynamicRequestBindings,
|
...globalDynamicRequestBindings,
|
||||||
...dataSourceStaticBindings,
|
...dataSourceStaticBindings,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -231,11 +234,11 @@
|
||||||
]
|
]
|
||||||
|
|
||||||
// convert dynamic variables list to simple key/val object
|
// convert dynamic variables list to simple key/val object
|
||||||
const getDynamicVariables = (datasource, queryId) => {
|
const getDynamicVariables = (datasource, queryId, matchFn) => {
|
||||||
const variablesList = datasource?.config?.dynamicVariables
|
const variablesList = datasource?.config?.dynamicVariables
|
||||||
if (variablesList && variablesList.length > 0) {
|
if (variablesList && variablesList.length > 0) {
|
||||||
const filtered = queryId
|
const filtered = queryId
|
||||||
? variablesList.filter(variable => variable.queryId === queryId)
|
? variablesList.filter(variable => matchFn(variable, queryId))
|
||||||
: variablesList
|
: variablesList
|
||||||
return filtered.reduce(
|
return filtered.reduce(
|
||||||
(acc, next) => ({ ...acc, [next.name]: next.value }),
|
(acc, next) => ({ ...acc, [next.name]: next.value }),
|
||||||
|
@ -367,12 +370,21 @@
|
||||||
if (query && !query.fields.pagination) {
|
if (query && !query.fields.pagination) {
|
||||||
query.fields.pagination = {}
|
query.fields.pagination = {}
|
||||||
}
|
}
|
||||||
dynamicVariables = getDynamicVariables(datasource, query._id)
|
dynamicVariables = getDynamicVariables(
|
||||||
|
datasource,
|
||||||
|
query._id,
|
||||||
|
(variable, queryId) => variable.queryId === queryId
|
||||||
|
)
|
||||||
|
globalDynamicBindings = getDynamicVariables(
|
||||||
|
datasource,
|
||||||
|
query._id,
|
||||||
|
(variable, queryId) => variable.queryId !== queryId
|
||||||
|
)
|
||||||
|
|
||||||
prettifyQueryRequestBody(
|
prettifyQueryRequestBody(
|
||||||
query,
|
query,
|
||||||
requestBindings,
|
requestBindings,
|
||||||
dynamicVariables,
|
globalDynamicBindings,
|
||||||
staticVariables,
|
staticVariables,
|
||||||
restBindings
|
restBindings
|
||||||
)
|
)
|
||||||
|
@ -437,7 +449,7 @@
|
||||||
valuePlaceholder="Default"
|
valuePlaceholder="Default"
|
||||||
bindings={[
|
bindings={[
|
||||||
...restBindings,
|
...restBindings,
|
||||||
...dynamicRequestBindings,
|
...globalDynamicRequestBindings,
|
||||||
...dataSourceStaticBindings,
|
...dataSourceStaticBindings,
|
||||||
]}
|
]}
|
||||||
bindingDrawerLeft="260px"
|
bindingDrawerLeft="260px"
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
selectedLayout,
|
selectedLayout,
|
||||||
currentAsset,
|
currentAsset,
|
||||||
} from "builderStore"
|
} from "builderStore"
|
||||||
import iframeTemplate from "./iframeTemplate"
|
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
import {
|
import {
|
||||||
ProgressCircle,
|
ProgressCircle,
|
||||||
|
@ -40,12 +39,6 @@
|
||||||
BUDIBASE: "type",
|
BUDIBASE: "type",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct iframe template
|
|
||||||
$: template = iframeTemplate.replace(
|
|
||||||
/\{\{ CLIENT_LIB_PATH }}/,
|
|
||||||
$store.clientLibPath
|
|
||||||
)
|
|
||||||
|
|
||||||
const placeholderScreen = new Screen()
|
const placeholderScreen = new Screen()
|
||||||
.name("Screen Placeholder")
|
.name("Screen Placeholder")
|
||||||
.route("/")
|
.route("/")
|
||||||
|
@ -151,7 +144,11 @@
|
||||||
} else if (type === "update-prop") {
|
} else if (type === "update-prop") {
|
||||||
await store.actions.components.updateSetting(data.prop, data.value)
|
await store.actions.components.updateSetting(data.prop, data.value)
|
||||||
} else if (type === "delete-component" && data.id) {
|
} else if (type === "delete-component" && data.id) {
|
||||||
|
// Legacy type, can be deleted in future
|
||||||
confirmDeleteComponent(data.id)
|
confirmDeleteComponent(data.id)
|
||||||
|
} else if (type === "key-down") {
|
||||||
|
const { key, ctrlKey } = data
|
||||||
|
document.dispatchEvent(new KeyboardEvent("keydown", { key, ctrlKey }))
|
||||||
} else if (type === "duplicate-component" && data.id) {
|
} else if (type === "duplicate-component" && data.id) {
|
||||||
const rootComponent = get(currentAsset).props
|
const rootComponent = get(currentAsset).props
|
||||||
const component = findComponent(rootComponent, data.id)
|
const component = findComponent(rootComponent, data.id)
|
||||||
|
@ -293,7 +290,7 @@
|
||||||
<iframe
|
<iframe
|
||||||
title="componentPreview"
|
title="componentPreview"
|
||||||
bind:this={iframe}
|
bind:this={iframe}
|
||||||
srcdoc={template}
|
src="/preview"
|
||||||
class:hidden={loading || error}
|
class:hidden={loading || error}
|
||||||
class:tablet={$store.previewDevice === "tablet"}
|
class:tablet={$store.previewDevice === "tablet"}
|
||||||
class:mobile={$store.previewDevice === "mobile"}
|
class:mobile={$store.previewDevice === "mobile"}
|
||||||
|
|
|
@ -1,104 +0,0 @@
|
||||||
export default `
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" />
|
|
||||||
<link
|
|
||||||
href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600;700&display=swap"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
href="https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
<style>
|
|
||||||
html, body {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
html {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
*,
|
|
||||||
*:before,
|
|
||||||
*:after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script src='{{ CLIENT_LIB_PATH }}'></script>
|
|
||||||
<script>
|
|
||||||
function receiveMessage(event) {
|
|
||||||
if (!event.data) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse received message
|
|
||||||
// If parsing fails, just ignore and wait for the next message
|
|
||||||
let parsed
|
|
||||||
try {
|
|
||||||
parsed = JSON.parse(event.data)
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Client received invalid JSON")
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
if (!parsed || !parsed.isBudibaseEvent) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract data from message
|
|
||||||
const {
|
|
||||||
selectedComponentId,
|
|
||||||
layout,
|
|
||||||
screen,
|
|
||||||
appId,
|
|
||||||
theme,
|
|
||||||
customTheme,
|
|
||||||
previewDevice,
|
|
||||||
navigation,
|
|
||||||
hiddenComponentIds
|
|
||||||
} = parsed
|
|
||||||
|
|
||||||
// Set some flags so the app knows we're in the builder
|
|
||||||
window["##BUDIBASE_IN_BUILDER##"] = true
|
|
||||||
window["##BUDIBASE_APP_ID##"] = appId
|
|
||||||
window["##BUDIBASE_PREVIEW_LAYOUT##"] = layout
|
|
||||||
window["##BUDIBASE_PREVIEW_SCREEN##"] = screen
|
|
||||||
window["##BUDIBASE_SELECTED_COMPONENT_ID##"] = selectedComponentId
|
|
||||||
window["##BUDIBASE_PREVIEW_ID##"] = Math.random()
|
|
||||||
window["##BUDIBASE_PREVIEW_THEME##"] = theme
|
|
||||||
window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"] = customTheme
|
|
||||||
window["##BUDIBASE_PREVIEW_DEVICE##"] = previewDevice
|
|
||||||
window["##BUDIBASE_PREVIEW_NAVIGATION##"] = navigation
|
|
||||||
window["##BUDIBASE_HIDDEN_COMPONENT_IDS##"] = hiddenComponentIds
|
|
||||||
|
|
||||||
// Initialise app
|
|
||||||
try {
|
|
||||||
if (window.loadBudibase) {
|
|
||||||
window.loadBudibase()
|
|
||||||
document.documentElement.classList.add("loaded")
|
|
||||||
} else {
|
|
||||||
throw "The client library couldn't be loaded"
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
window.parent.postMessage({ type: "error", error })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("message", receiveMessage)
|
|
||||||
window.parent.postMessage({ type: "ready" })
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body/>
|
|
||||||
</html>
|
|
||||||
`
|
|
|
@ -1,117 +1,78 @@
|
||||||
<script>
|
<script>
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import { ActionMenu, MenuItem, Icon } from "@budibase/bbui"
|
||||||
import { ActionMenu, MenuItem, Icon, notifications } from "@budibase/bbui"
|
|
||||||
|
|
||||||
export let component
|
export let component
|
||||||
|
|
||||||
let confirmDeleteDialog
|
|
||||||
|
|
||||||
$: definition = store.actions.components.getDefinition(component?._component)
|
|
||||||
$: noChildrenAllowed = !component || !definition?.hasChildren
|
|
||||||
$: noPaste = !$store.componentToPaste
|
$: noPaste = !$store.componentToPaste
|
||||||
|
|
||||||
// "editable" has been repurposed for inline text editing.
|
const keyboardEvent = (key, ctrlKey = false) => {
|
||||||
// It remains here for legacy compatibility.
|
// Ensure this component is selected first
|
||||||
// Future components should define "static": true for indicate they should
|
if (component._id !== $store.selectedComponentId) {
|
||||||
// not show a context menu.
|
store.update(state => {
|
||||||
$: showMenu = definition?.editable !== false && definition?.static !== true
|
state.selectedComponentId = component._id
|
||||||
|
return state
|
||||||
const moveUpComponent = async () => {
|
})
|
||||||
try {
|
|
||||||
await store.actions.components.moveUp(component)
|
|
||||||
} catch (error) {
|
|
||||||
notifications.error("Error moving component up")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const moveDownComponent = async () => {
|
|
||||||
try {
|
|
||||||
await store.actions.components.moveDown(component)
|
|
||||||
} catch (error) {
|
|
||||||
notifications.error("Error moving component down")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const duplicateComponent = () => {
|
|
||||||
storeComponentForCopy(false)
|
|
||||||
pasteComponent("below")
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteComponent = async () => {
|
|
||||||
try {
|
|
||||||
await store.actions.components.delete(component)
|
|
||||||
} catch (error) {
|
|
||||||
notifications.error("Error deleting component")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const storeComponentForCopy = (cut = false) => {
|
|
||||||
store.actions.components.copy(component, cut)
|
|
||||||
}
|
|
||||||
|
|
||||||
const pasteComponent = mode => {
|
|
||||||
try {
|
|
||||||
store.actions.components.paste(component, mode)
|
|
||||||
} catch (error) {
|
|
||||||
notifications.error("Error saving component")
|
|
||||||
}
|
}
|
||||||
|
document.dispatchEvent(new KeyboardEvent("keydown", { key, ctrlKey }))
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if showMenu}
|
<ActionMenu>
|
||||||
<ActionMenu>
|
<div slot="control" class="icon">
|
||||||
<div slot="control" class="icon">
|
<Icon size="S" hoverable name="MoreSmallList" />
|
||||||
<Icon size="S" hoverable name="MoreSmallList" />
|
</div>
|
||||||
</div>
|
<MenuItem
|
||||||
<MenuItem icon="Delete" on:click={confirmDeleteDialog.show}>
|
icon="Delete"
|
||||||
Delete
|
keyBind="!BackAndroid"
|
||||||
</MenuItem>
|
on:click={() => keyboardEvent("Delete")}
|
||||||
<MenuItem noClose icon="ChevronUp" on:click={moveUpComponent}>
|
>
|
||||||
Move up
|
Delete
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem noClose icon="ChevronDown" on:click={moveDownComponent}>
|
<MenuItem
|
||||||
Move down
|
icon="ChevronUp"
|
||||||
</MenuItem>
|
keyBind="Ctrl+!ArrowUp"
|
||||||
<MenuItem noClose icon="Duplicate" on:click={duplicateComponent}>
|
on:click={() => keyboardEvent("ArrowUp", true)}
|
||||||
Duplicate
|
>
|
||||||
</MenuItem>
|
Move up
|
||||||
<MenuItem icon="Cut" on:click={() => storeComponentForCopy(true)}>
|
</MenuItem>
|
||||||
Cut
|
<MenuItem
|
||||||
</MenuItem>
|
icon="ChevronDown"
|
||||||
<MenuItem icon="Copy" on:click={() => storeComponentForCopy(false)}>
|
keyBind="Ctrl+!ArrowDown"
|
||||||
Copy
|
on:click={() => keyboardEvent("ArrowDown", true)}
|
||||||
</MenuItem>
|
>
|
||||||
<MenuItem
|
Move down
|
||||||
icon="LayersBringToFront"
|
</MenuItem>
|
||||||
on:click={() => pasteComponent("above")}
|
<MenuItem
|
||||||
disabled={noPaste}
|
icon="Duplicate"
|
||||||
>
|
keyBind="Ctrl+D"
|
||||||
Paste above
|
on:click={() => keyboardEvent("d", true)}
|
||||||
</MenuItem>
|
>
|
||||||
<MenuItem
|
Duplicate
|
||||||
icon="LayersSendToBack"
|
</MenuItem>
|
||||||
on:click={() => pasteComponent("below")}
|
<MenuItem
|
||||||
disabled={noPaste}
|
icon="Cut"
|
||||||
>
|
keyBind="Ctrl+X"
|
||||||
Paste below
|
on:click={() => keyboardEvent("x", true)}
|
||||||
</MenuItem>
|
>
|
||||||
<MenuItem
|
Cut
|
||||||
icon="ShowOneLayer"
|
</MenuItem>
|
||||||
on:click={() => pasteComponent("inside")}
|
<MenuItem
|
||||||
disabled={noPaste || noChildrenAllowed}
|
icon="Copy"
|
||||||
>
|
keyBind="Ctrl+C"
|
||||||
Paste inside
|
on:click={() => keyboardEvent("c", true)}
|
||||||
</MenuItem>
|
>
|
||||||
</ActionMenu>
|
Copy
|
||||||
<ConfirmDialog
|
</MenuItem>
|
||||||
bind:this={confirmDeleteDialog}
|
<MenuItem
|
||||||
title="Confirm Deletion"
|
icon="LayersSendToBack"
|
||||||
body={`Are you sure you wish to delete this '${definition?.name}' component?`}
|
keyBind="Ctrl+V"
|
||||||
okText="Delete Component"
|
on:click={() => keyboardEvent("v", true)}
|
||||||
onOk={deleteComponent}
|
disabled={noPaste}
|
||||||
/>
|
>
|
||||||
{/if}
|
Paste
|
||||||
|
</MenuItem>
|
||||||
|
</ActionMenu>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.icon {
|
.icon {
|
||||||
|
|
|
@ -2,16 +2,19 @@
|
||||||
import Panel from "components/design/Panel.svelte"
|
import Panel from "components/design/Panel.svelte"
|
||||||
import ComponentTree from "./ComponentTree.svelte"
|
import ComponentTree from "./ComponentTree.svelte"
|
||||||
import { dndStore } from "./dndStore.js"
|
import { dndStore } from "./dndStore.js"
|
||||||
import { goto } from "@roxi/routify"
|
import { goto, isActive } from "@roxi/routify"
|
||||||
import { store, selectedScreen } from "builderStore"
|
import { store, selectedScreen, selectedComponent } from "builderStore"
|
||||||
import NavItem from "components/common/NavItem.svelte"
|
import NavItem from "components/common/NavItem.svelte"
|
||||||
import ScreenslotDropdownMenu from "./ScreenslotDropdownMenu.svelte"
|
import ScreenslotDropdownMenu from "./ScreenslotDropdownMenu.svelte"
|
||||||
import { setContext } from "svelte"
|
import { setContext, onMount } from "svelte"
|
||||||
|
import { get } from "svelte/store"
|
||||||
import DNDPositionIndicator from "./DNDPositionIndicator.svelte"
|
import DNDPositionIndicator from "./DNDPositionIndicator.svelte"
|
||||||
import { DropPosition } from "./dndStore"
|
import { DropPosition } from "./dndStore"
|
||||||
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
import { notifications, Button } from "@budibase/bbui"
|
import { notifications, Button } from "@budibase/bbui"
|
||||||
|
|
||||||
let scrollRef
|
let scrollRef
|
||||||
|
let confirmDeleteDialog
|
||||||
|
|
||||||
const scrollTo = bounds => {
|
const scrollTo = bounds => {
|
||||||
if (!bounds) {
|
if (!bounds) {
|
||||||
|
@ -69,6 +72,76 @@
|
||||||
setContext("scroll", {
|
setContext("scroll", {
|
||||||
scrollTo,
|
scrollTo,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const deleteComponent = async () => {
|
||||||
|
await store.actions.components.delete(get(selectedComponent))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleKeyPress = async e => {
|
||||||
|
// Ignore repeating events
|
||||||
|
if (e.repeat) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Ignore events when typing
|
||||||
|
const activeTag = document.activeElement?.tagName.toLowerCase()
|
||||||
|
if (["input", "textarea"].indexOf(activeTag) !== -1 && e.key !== "Escape") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const component = get(selectedComponent)
|
||||||
|
try {
|
||||||
|
if (e.ctrlKey || e.metaKey) {
|
||||||
|
if (e.key === "ArrowUp") {
|
||||||
|
e.preventDefault()
|
||||||
|
await store.actions.components.moveUp(component)
|
||||||
|
} else if (e.key === "ArrowDown") {
|
||||||
|
e.preventDefault()
|
||||||
|
await store.actions.components.moveDown(component)
|
||||||
|
} else if (e.key === "c") {
|
||||||
|
e.preventDefault()
|
||||||
|
await store.actions.components.copy(component, false)
|
||||||
|
} else if (e.key === "x") {
|
||||||
|
e.preventDefault()
|
||||||
|
store.actions.components.copy(component, true)
|
||||||
|
} else if (e.key === "v") {
|
||||||
|
e.preventDefault()
|
||||||
|
await store.actions.components.paste(component, "inside")
|
||||||
|
} else if (e.key === "d") {
|
||||||
|
e.preventDefault()
|
||||||
|
await store.actions.components.copy(component)
|
||||||
|
await store.actions.components.paste(component, "below")
|
||||||
|
} else if (e.key === "Enter") {
|
||||||
|
e.preventDefault()
|
||||||
|
$goto("./new")
|
||||||
|
}
|
||||||
|
} else if (e.key === "Backspace" || e.key === "Delete") {
|
||||||
|
// Don't show confirmation for the screen itself
|
||||||
|
if (component._id === get(selectedScreen).props._id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e.preventDefault()
|
||||||
|
confirmDeleteDialog.show()
|
||||||
|
} else if (e.key === "ArrowUp") {
|
||||||
|
e.preventDefault()
|
||||||
|
await store.actions.components.selectPrevious()
|
||||||
|
} else if (e.key === "ArrowDown") {
|
||||||
|
e.preventDefault()
|
||||||
|
await store.actions.components.selectNext()
|
||||||
|
} else if (e.key === "Escape" && $isActive("./new")) {
|
||||||
|
e.preventDefault()
|
||||||
|
$goto("./")
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
notifications.error("Error handling key press")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
document.addEventListener("keydown", handleKeyPress)
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("keydown", handleKeyPress)
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Panel title="Components" showExpandIcon borderRight>
|
<Panel title="Components" showExpandIcon borderRight>
|
||||||
|
@ -116,6 +189,13 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
<ConfirmDialog
|
||||||
|
bind:this={confirmDeleteDialog}
|
||||||
|
title="Confirm Deletion"
|
||||||
|
body={`Are you sure you want to delete "${$selectedComponent?._instanceName}"?`}
|
||||||
|
okText="Delete Component"
|
||||||
|
onOk={deleteComponent}
|
||||||
|
/>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.add-component {
|
.add-component {
|
||||||
|
|
|
@ -31,15 +31,20 @@
|
||||||
<div slot="control" class="icon">
|
<div slot="control" class="icon">
|
||||||
<Icon size="S" hoverable name="MoreSmallList" />
|
<Icon size="S" hoverable name="MoreSmallList" />
|
||||||
</div>
|
</div>
|
||||||
<MenuItem icon="Copy" on:click={() => storeComponentForCopy(false)}>
|
<MenuItem
|
||||||
|
icon="Copy"
|
||||||
|
keyBind="Ctrl+C"
|
||||||
|
on:click={() => storeComponentForCopy(false)}
|
||||||
|
>
|
||||||
Copy
|
Copy
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon="ShowOneLayer"
|
icon="LayersSendToBack"
|
||||||
|
keyBind="Ctrl+V"
|
||||||
on:click={() => pasteComponent("inside")}
|
on:click={() => pasteComponent("inside")}
|
||||||
disabled={noPaste}
|
disabled={noPaste}
|
||||||
>
|
>
|
||||||
Paste inside
|
Paste
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</ActionMenu>
|
</ActionMenu>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -36,7 +36,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const canRenderControl = setting => {
|
const canRenderControl = (setting, isScreen) => {
|
||||||
|
// Prevent rendering on click setting for screens
|
||||||
|
if (setting?.type === "event" && isScreen) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
const control = getComponentForSetting(setting)
|
const control = getComponentForSetting(setting)
|
||||||
if (!control) {
|
if (!control) {
|
||||||
return false
|
return false
|
||||||
|
@ -87,7 +92,7 @@
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#each section.settings as setting (setting.key)}
|
{#each section.settings as setting (setting.key)}
|
||||||
{#if canRenderControl(setting)}
|
{#if canRenderControl(setting, isScreen)}
|
||||||
<PropertyControl
|
<PropertyControl
|
||||||
type={setting.type}
|
type={setting.type}
|
||||||
control={getComponentForSetting(setting)}
|
control={getComponentForSetting(setting)}
|
||||||
|
|
|
@ -1,20 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { notifications, Slider, Icon } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
|
import { Constants } from "@budibase/frontend-core"
|
||||||
|
|
||||||
const ThemeOptions = [
|
const onChangeTheme = async theme => {
|
||||||
"spectrum--darkest",
|
|
||||||
"spectrum--dark",
|
|
||||||
"spectrum--light",
|
|
||||||
"spectrum--lightest",
|
|
||||||
]
|
|
||||||
|
|
||||||
$: themeIndex = ThemeOptions.indexOf($store.theme) ?? 2
|
|
||||||
|
|
||||||
const onChangeTheme = async e => {
|
|
||||||
try {
|
try {
|
||||||
const theme = ThemeOptions[e.detail] ?? ThemeOptions[2]
|
await store.actions.theme.save(`spectrum--${theme}`)
|
||||||
await store.actions.theme.save(theme)
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error updating theme")
|
notifications.error("Error updating theme")
|
||||||
}
|
}
|
||||||
|
@ -22,26 +13,52 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<Icon name="Moon" />
|
{#each Constants.Themes as theme}
|
||||||
<Slider
|
<div
|
||||||
min={0}
|
class="theme"
|
||||||
max={3}
|
class:selected={`spectrum--${theme.class}` === $store.theme}
|
||||||
step={1}
|
on:click={() => onChangeTheme(theme.class)}
|
||||||
value={themeIndex}
|
>
|
||||||
on:change={onChangeTheme}
|
<div
|
||||||
/>
|
style="background: {theme.preview}"
|
||||||
<Icon name="Light" />
|
class="color spectrum--{theme.class}"
|
||||||
|
/>
|
||||||
|
{theme.name}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
div {
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
.color {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 50px;
|
||||||
|
background: var(--spectrum-global-color-gray-200);
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.theme {
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: var(--spacing-s) var(--spacing-m);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-m);
|
gap: var(--spacing-xl);
|
||||||
|
transition: background 130ms ease-out;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--spectrum-global-color-gray-900);
|
||||||
}
|
}
|
||||||
div :global(.spectrum-Form-item) {
|
.theme:hover {
|
||||||
flex: 1 1 auto;
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.theme.selected,
|
||||||
|
.theme:hover {
|
||||||
|
background: var(--spectrum-global-color-gray-50);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
<script>
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import { Slider, Button } from "@budibase/bbui"
|
||||||
|
|
||||||
|
export let customTheme
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
const options = ["0", "4px", "8px", "16px"]
|
||||||
|
|
||||||
|
$: index = options.indexOf(customTheme.buttonBorderRadius) ?? 2
|
||||||
|
|
||||||
|
const onChange = async e => {
|
||||||
|
dispatch("change", options[e.detail])
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<Slider min={0} max={3} step={1} value={index} on:change={onChange} />
|
||||||
|
<div class="button" style="--radius: {customTheme.buttonBorderRadius};">
|
||||||
|
<Button primary newStyles>Button</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
.container :global(.spectrum-Form-item) {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
.button :global(.spectrum-Button) {
|
||||||
|
border-radius: var(--radius) !important;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,35 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import Panel from "components/design/Panel.svelte"
|
import Panel from "components/design/Panel.svelte"
|
||||||
import {
|
import { Layout, Label, ColorPicker, notifications } from "@budibase/bbui"
|
||||||
Layout,
|
|
||||||
Label,
|
|
||||||
ColorPicker,
|
|
||||||
Button,
|
|
||||||
notifications,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { DefaultAppTheme } from "constants"
|
import { DefaultAppTheme } from "constants"
|
||||||
import AppThemeSelect from "./AppThemeSelect.svelte"
|
import AppThemeSelect from "./AppThemeSelect.svelte"
|
||||||
|
import ButtonRoundnessSelect from "./ButtonRoundnessSelect.svelte"
|
||||||
const ButtonBorderRadiusOptions = [
|
|
||||||
{
|
|
||||||
label: "Square",
|
|
||||||
value: "0",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Soft edge",
|
|
||||||
value: "4px",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Curved",
|
|
||||||
value: "8px",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Round",
|
|
||||||
value: "16px",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
$: customTheme = $store.customTheme || {}
|
$: customTheme = $store.customTheme || {}
|
||||||
|
|
||||||
|
@ -52,22 +28,11 @@
|
||||||
<AppThemeSelect />
|
<AppThemeSelect />
|
||||||
</Layout>
|
</Layout>
|
||||||
<Layout noPadding gap="XS">
|
<Layout noPadding gap="XS">
|
||||||
<Label>Buttons</Label>
|
<Label>Button roundness</Label>
|
||||||
<div class="buttons">
|
<ButtonRoundnessSelect
|
||||||
{#each ButtonBorderRadiusOptions as option}
|
{customTheme}
|
||||||
<div
|
on:change={e => update("buttonBorderRadius", e.detail)}
|
||||||
class:active={customTheme.buttonBorderRadius === option.value}
|
/>
|
||||||
style={`--radius: ${option.value}`}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
secondary
|
|
||||||
on:click={() => update("buttonBorderRadius", option.value)}
|
|
||||||
>
|
|
||||||
{option.label}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
<Layout noPadding gap="XS">
|
<Layout noPadding gap="XS">
|
||||||
<Label>Accent color</Label>
|
<Label>Accent color</Label>
|
||||||
|
@ -88,29 +53,3 @@
|
||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
<style>
|
|
||||||
.buttons {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 100px 100px;
|
|
||||||
gap: var(--spacing-m);
|
|
||||||
}
|
|
||||||
.buttons > div {
|
|
||||||
display: contents;
|
|
||||||
}
|
|
||||||
.buttons > div :global(.spectrum-Button) {
|
|
||||||
border-radius: var(--radius) !important;
|
|
||||||
border-width: 1px;
|
|
||||||
border-color: var(--spectrum-global-color-gray-400);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.buttons > div:hover :global(.spectrum-Button) {
|
|
||||||
background: var(--spectrum-global-color-gray-700);
|
|
||||||
border-color: var(--spectrum-global-color-gray-700);
|
|
||||||
}
|
|
||||||
.buttons > div.active :global(.spectrum-Button) {
|
|
||||||
background: var(--spectrum-global-color-gray-200);
|
|
||||||
color: var(--spectrum-global-color-gray-800);
|
|
||||||
border-color: var(--spectrum-global-color-gray-600);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
<script>
|
<script>
|
||||||
import { Layout, Heading, Body, Button, notifications } from "@budibase/bbui"
|
import { Layout, Heading, Body, Button, notifications } from "@budibase/bbui"
|
||||||
import { goto, params } from "@roxi/routify"
|
import { goto, params } from "@roxi/routify"
|
||||||
import { users } from "stores/portal"
|
import { users, organisation } from "stores/portal"
|
||||||
import PasswordRepeatInput from "components/common/users/PasswordRepeatInput.svelte"
|
import PasswordRepeatInput from "components/common/users/PasswordRepeatInput.svelte"
|
||||||
import Logo from "assets/bb-emblem.svg"
|
import Logo from "assets/bb-emblem.svg"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
const inviteCode = $params["?code"]
|
const inviteCode = $params["?code"]
|
||||||
let password, error
|
let password, error
|
||||||
|
|
||||||
|
$: company = $organisation.company || "Budibase"
|
||||||
|
|
||||||
async function acceptInvite() {
|
async function acceptInvite() {
|
||||||
try {
|
try {
|
||||||
await users.acceptInvite(inviteCode, password)
|
await users.acceptInvite(inviteCode, password)
|
||||||
|
@ -17,16 +20,24 @@
|
||||||
notifications.error(error.message)
|
notifications.error(error.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
try {
|
||||||
|
await organisation.init()
|
||||||
|
} catch (error) {
|
||||||
|
notifications.error("Error getting org config")
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<Layout>
|
<Layout>
|
||||||
<img src={Logo} alt="logo" />
|
<img alt="logo" src={$organisation.logoUrl || Logo} />
|
||||||
<Layout gap="XS" justifyItems="center" noPadding>
|
<Layout gap="XS" justifyItems="center" noPadding>
|
||||||
<Heading size="M">Accept Invitation</Heading>
|
<Heading size="M">Invitation to {company}</Heading>
|
||||||
<Body textAlign="center" size="M">
|
<Body textAlign="center" size="M">
|
||||||
Please enter a password to set up your user.
|
Please enter a password to get started.
|
||||||
</Body>
|
</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
<PasswordRepeatInput bind:error bind:password />
|
<PasswordRepeatInput bind:error bind:password />
|
||||||
|
@ -46,7 +57,7 @@
|
||||||
}
|
}
|
||||||
.container {
|
.container {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 260px;
|
width: 300px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
Body,
|
Body,
|
||||||
Select,
|
Select,
|
||||||
Toggle,
|
Toggle,
|
||||||
|
Tag,
|
||||||
|
Tags,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
|
@ -29,6 +31,8 @@
|
||||||
OIDC: "oidc",
|
OIDC: "oidc",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const HasSpacesRegex = /[\\"\s]/
|
||||||
|
|
||||||
// Some older google configs contain a manually specified value - retain the functionality to edit the field
|
// Some older google configs contain a manually specified value - retain the functionality to edit the field
|
||||||
// When there is no value or we are in the cloud - prohibit editing the field, must use platform url to change
|
// When there is no value or we are in the cloud - prohibit editing the field, must use platform url to change
|
||||||
$: googleCallbackUrl = undefined
|
$: googleCallbackUrl = undefined
|
||||||
|
@ -145,7 +149,6 @@
|
||||||
|
|
||||||
async function save(docs) {
|
async function save(docs) {
|
||||||
let calls = []
|
let calls = []
|
||||||
|
|
||||||
// Only if the user has provided an image, upload it
|
// Only if the user has provided an image, upload it
|
||||||
if (image) {
|
if (image) {
|
||||||
let data = new FormData()
|
let data = new FormData()
|
||||||
|
@ -157,7 +160,6 @@
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
docs.forEach(element => {
|
docs.forEach(element => {
|
||||||
// Delete unsupported fields
|
// Delete unsupported fields
|
||||||
delete element.createdAt
|
delete element.createdAt
|
||||||
|
@ -199,7 +201,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (calls.length) {
|
if (calls.length) {
|
||||||
Promise.all(calls)
|
Promise.all(calls)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
@ -215,6 +216,21 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let defaultScopes = ["profile", "email", "offline_access"]
|
||||||
|
|
||||||
|
const refreshScopes = idx => {
|
||||||
|
providers.oidc.config.configs[idx]["scopes"] =
|
||||||
|
providers.oidc.config.configs[idx]["scopes"]
|
||||||
|
}
|
||||||
|
|
||||||
|
let scopesFields = [
|
||||||
|
{
|
||||||
|
editing: true,
|
||||||
|
inputText: null,
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
try {
|
try {
|
||||||
await organisation.init()
|
await organisation.init()
|
||||||
|
@ -276,7 +292,7 @@
|
||||||
if (!oidcDoc?._id) {
|
if (!oidcDoc?._id) {
|
||||||
providers.oidc = {
|
providers.oidc = {
|
||||||
type: ConfigTypes.OIDC,
|
type: ConfigTypes.OIDC,
|
||||||
config: { configs: [{ activated: true }] },
|
config: { configs: [{ activated: true, scopes: defaultScopes }] },
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
originalOidcDoc = cloneDeep(oidcDoc)
|
originalOidcDoc = cloneDeep(oidcDoc)
|
||||||
|
@ -345,6 +361,7 @@
|
||||||
size="s"
|
size="s"
|
||||||
cta
|
cta
|
||||||
on:click={() => save([providers.oidc])}
|
on:click={() => save([providers.oidc])}
|
||||||
|
dataCy={"oidc-save"}
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -362,6 +379,7 @@
|
||||||
bind:value={providers.oidc.config.configs[0][field.name]}
|
bind:value={providers.oidc.config.configs[0][field.name]}
|
||||||
readonly={field.readonly}
|
readonly={field.readonly}
|
||||||
placeholder={field.placeholder}
|
placeholder={field.placeholder}
|
||||||
|
dataCy={field.name}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -392,15 +410,132 @@
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<Label size="L">Activated</Label>
|
<Label size="L">Activated</Label>
|
||||||
<Toggle
|
<Toggle
|
||||||
|
dataCy={"oidc-active"}
|
||||||
text=""
|
text=""
|
||||||
bind:value={providers.oidc.config.configs[0].activated}
|
bind:value={providers.oidc.config.configs[0].activated}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
<span class="advanced-config">
|
||||||
|
<Layout gap="XS" noPadding>
|
||||||
|
<Heading size="XS">
|
||||||
|
<div class="auth-scopes">
|
||||||
|
<div>Advanced</div>
|
||||||
|
<Button
|
||||||
|
secondary
|
||||||
|
newStyles
|
||||||
|
size="S"
|
||||||
|
on:click={() => {
|
||||||
|
providers.oidc.config.configs[0]["scopes"] = [...defaultScopes]
|
||||||
|
}}
|
||||||
|
dataCy={"restore-oidc-default-scopes"}
|
||||||
|
>
|
||||||
|
Restore Defaults
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Heading>
|
||||||
|
<Body size="S">
|
||||||
|
Changes to your authentication scopes will only take effect when you
|
||||||
|
next log in. Please refer to your vendor documentation before
|
||||||
|
modification.
|
||||||
|
</Body>
|
||||||
|
|
||||||
|
<div class="auth-form">
|
||||||
|
<span class="add-new">
|
||||||
|
<Label size="L">{"Auth Scopes"}</Label>
|
||||||
|
<Input
|
||||||
|
dataCy={"new-scope-input"}
|
||||||
|
error={scopesFields[0].error}
|
||||||
|
placeholder={"New Scope"}
|
||||||
|
bind:value={scopesFields[0].inputText}
|
||||||
|
on:keyup={e => {
|
||||||
|
if (!scopesFields[0].inputText) {
|
||||||
|
scopesFields[0].error = null
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
e.key === "Enter" ||
|
||||||
|
e.keyCode === 13 ||
|
||||||
|
e.code == "Space" ||
|
||||||
|
e.keyCode == 32
|
||||||
|
) {
|
||||||
|
let scopes = providers.oidc.config.configs[0]["scopes"]
|
||||||
|
? providers.oidc.config.configs[0]["scopes"]
|
||||||
|
: [...defaultScopes]
|
||||||
|
|
||||||
|
let update = scopesFields[0].inputText.trim()
|
||||||
|
|
||||||
|
if (HasSpacesRegex.test(update)) {
|
||||||
|
scopesFields[0].error =
|
||||||
|
"Auth scopes cannot contain spaces, double quotes or backslashes"
|
||||||
|
return
|
||||||
|
} else if (scopes.indexOf(update) > -1) {
|
||||||
|
scopesFields[0].error = "Auth scope already exists"
|
||||||
|
return
|
||||||
|
} else if (!update.length) {
|
||||||
|
scopesFields[0].inputText = null
|
||||||
|
scopesFields[0].error = null
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
scopesFields[0].error = null
|
||||||
|
scopes.push(update)
|
||||||
|
providers.oidc.config.configs[0]["scopes"] = scopes
|
||||||
|
scopesFields[0].inputText = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<div class="tag-wrap">
|
||||||
|
<span />
|
||||||
|
<Tags>
|
||||||
|
<Tag closable={false}>openid</Tag>
|
||||||
|
{#each providers.oidc.config.configs[0]["scopes"] || [...defaultScopes] as tag, idx}
|
||||||
|
<Tag
|
||||||
|
closable={scopesFields[0].editing}
|
||||||
|
on:click={() => {
|
||||||
|
let idxScopes = providers.oidc.config.configs[0]["scopes"]
|
||||||
|
if (idxScopes.length == 1) {
|
||||||
|
idxScopes.pop()
|
||||||
|
} else {
|
||||||
|
idxScopes.splice(idx, 1)
|
||||||
|
refreshScopes(0)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
</Tag>
|
||||||
|
{/each}
|
||||||
|
</Tags>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.auth-scopes {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.advanced-config :global(.spectrum-Tags-item) {
|
||||||
|
margin-left: 0px;
|
||||||
|
margin-top: var(--spacing-m);
|
||||||
|
margin-right: var(--spacing-m);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-form > * {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: var(--spacing-l);
|
||||||
|
grid-template-columns: 100px 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.advanced-config .auth-form .tag-wrap {
|
||||||
|
padding: 0px 5px 5px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.form-row {
|
.form-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 100px 1fr;
|
grid-template-columns: 100px 1fr;
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
csvString = e.target.result
|
csvString = e.target.result
|
||||||
files = fileArray
|
files = fileArray
|
||||||
|
|
||||||
userEmails = csvString.split("\n")
|
userEmails = csvString.split(/\r?\n/)
|
||||||
})
|
})
|
||||||
reader.readAsText(fileArray[0])
|
reader.readAsText(fileArray[0])
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "1.2.47",
|
"version": "1.2.57",
|
||||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
"outputPath": "build"
|
"outputPath": "build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/backend-core": "1.2.47",
|
"@budibase/backend-core": "1.2.57",
|
||||||
"axios": "0.21.2",
|
"axios": "0.21.2",
|
||||||
"chalk": "4.1.0",
|
"chalk": "4.1.0",
|
||||||
"cli-progress": "3.11.2",
|
"cli-progress": "3.11.2",
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -8,7 +8,8 @@
|
||||||
"devicePreview": true,
|
"devicePreview": true,
|
||||||
"messagePassing": true,
|
"messagePassing": true,
|
||||||
"rowSelection": true,
|
"rowSelection": true,
|
||||||
"continueIfAction": true
|
"continueIfAction": true,
|
||||||
|
"showNotificationAction": true
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
"name": "Layout",
|
"name": "Layout",
|
||||||
|
@ -237,6 +238,11 @@
|
||||||
"showInBar": true,
|
"showInBar": true,
|
||||||
"barIcon": "ModernGridView",
|
"barIcon": "ModernGridView",
|
||||||
"barTitle": "Wrap"
|
"barTitle": "Wrap"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"label": "On Click",
|
||||||
|
"key": "onClick"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1466,10 +1472,11 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"label": "Colours",
|
"label": "Colors",
|
||||||
"key": "palette",
|
"key": "palette",
|
||||||
"defaultValue": "Palette 1",
|
"defaultValue": "Palette 1",
|
||||||
"options": [
|
"options": [
|
||||||
|
"Custom",
|
||||||
"Palette 1",
|
"Palette 1",
|
||||||
"Palette 2",
|
"Palette 2",
|
||||||
"Palette 3",
|
"Palette 3",
|
||||||
|
@ -1482,6 +1489,51 @@
|
||||||
"Palette 10"
|
"Palette 10"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C1",
|
||||||
|
"key": "c1",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C2",
|
||||||
|
"key": "c2",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C3",
|
||||||
|
"key": "c3",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C4",
|
||||||
|
"key": "c4",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C5",
|
||||||
|
"key": "c5",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Stacked",
|
"label": "Stacked",
|
||||||
|
@ -1581,10 +1633,11 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"label": "Colours",
|
"label": "Colors",
|
||||||
"key": "palette",
|
"key": "palette",
|
||||||
"defaultValue": "Palette 1",
|
"defaultValue": "Palette 1",
|
||||||
"options": [
|
"options": [
|
||||||
|
"Custom",
|
||||||
"Palette 1",
|
"Palette 1",
|
||||||
"Palette 2",
|
"Palette 2",
|
||||||
"Palette 3",
|
"Palette 3",
|
||||||
|
@ -1597,6 +1650,51 @@
|
||||||
"Palette 10"
|
"Palette 10"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C1",
|
||||||
|
"key": "c1",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C2",
|
||||||
|
"key": "c2",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C3",
|
||||||
|
"key": "c3",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C4",
|
||||||
|
"key": "c4",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C5",
|
||||||
|
"key": "c5",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"label": "Curve",
|
"label": "Curve",
|
||||||
|
@ -1695,10 +1793,11 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"label": "Colours",
|
"label": "Colors",
|
||||||
"key": "palette",
|
"key": "palette",
|
||||||
"defaultValue": "Palette 1",
|
"defaultValue": "Palette 1",
|
||||||
"options": [
|
"options": [
|
||||||
|
"Custom",
|
||||||
"Palette 1",
|
"Palette 1",
|
||||||
"Palette 2",
|
"Palette 2",
|
||||||
"Palette 3",
|
"Palette 3",
|
||||||
|
@ -1711,6 +1810,51 @@
|
||||||
"Palette 10"
|
"Palette 10"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C1",
|
||||||
|
"key": "c1",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C2",
|
||||||
|
"key": "c2",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C3",
|
||||||
|
"key": "c3",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C4",
|
||||||
|
"key": "c4",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C5",
|
||||||
|
"key": "c5",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"label": "Curve",
|
"label": "Curve",
|
||||||
|
@ -1800,10 +1944,11 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"label": "Colours",
|
"label": "Colors",
|
||||||
"key": "palette",
|
"key": "palette",
|
||||||
"defaultValue": "Palette 1",
|
"defaultValue": "Palette 1",
|
||||||
"options": [
|
"options": [
|
||||||
|
"Custom",
|
||||||
"Palette 1",
|
"Palette 1",
|
||||||
"Palette 2",
|
"Palette 2",
|
||||||
"Palette 3",
|
"Palette 3",
|
||||||
|
@ -1816,6 +1961,51 @@
|
||||||
"Palette 10"
|
"Palette 10"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C1",
|
||||||
|
"key": "c1",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C2",
|
||||||
|
"key": "c2",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C3",
|
||||||
|
"key": "c3",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C4",
|
||||||
|
"key": "c4",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C5",
|
||||||
|
"key": "c5",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Data Labels",
|
"label": "Data Labels",
|
||||||
|
@ -1882,10 +2072,11 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"label": "Colours",
|
"label": "Colors",
|
||||||
"key": "palette",
|
"key": "palette",
|
||||||
"defaultValue": "Palette 1",
|
"defaultValue": "Palette 1",
|
||||||
"options": [
|
"options": [
|
||||||
|
"Custom",
|
||||||
"Palette 1",
|
"Palette 1",
|
||||||
"Palette 2",
|
"Palette 2",
|
||||||
"Palette 3",
|
"Palette 3",
|
||||||
|
@ -1898,6 +2089,51 @@
|
||||||
"Palette 10"
|
"Palette 10"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C1",
|
||||||
|
"key": "c1",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C2",
|
||||||
|
"key": "c2",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C3",
|
||||||
|
"key": "c3",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C4",
|
||||||
|
"key": "c4",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C5",
|
||||||
|
"key": "c5",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Data Labels",
|
"label": "Data Labels",
|
||||||
|
@ -2875,6 +3111,12 @@
|
||||||
"key": "timeOnly",
|
"key": "timeOnly",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "24-Hour time",
|
||||||
|
"key": "time24hr",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Ignore time zones",
|
"label": "Ignore time zones",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "1.2.47",
|
"version": "1.2.57",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"module": "dist/budibase-client.js",
|
"module": "dist/budibase-client.js",
|
||||||
"main": "dist/budibase-client.js",
|
"main": "dist/budibase-client.js",
|
||||||
|
@ -19,9 +19,9 @@
|
||||||
"dev:builder": "rollup -cw"
|
"dev:builder": "rollup -cw"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.2.47",
|
"@budibase/bbui": "^1.2.57",
|
||||||
"@budibase/frontend-core": "^1.2.47",
|
"@budibase/frontend-core": "^1.2.57",
|
||||||
"@budibase/string-templates": "^1.2.47",
|
"@budibase/string-templates": "^1.2.57",
|
||||||
"@spectrum-css/button": "^3.0.3",
|
"@spectrum-css/button": "^3.0.3",
|
||||||
"@spectrum-css/card": "^3.0.3",
|
"@spectrum-css/card": "^3.0.3",
|
||||||
"@spectrum-css/divider": "^1.0.3",
|
"@spectrum-css/divider": "^1.0.3",
|
||||||
|
|
|
@ -94,7 +94,7 @@
|
||||||
id="spectrum-root"
|
id="spectrum-root"
|
||||||
lang="en"
|
lang="en"
|
||||||
dir="ltr"
|
dir="ltr"
|
||||||
class="spectrum spectrum--medium spectrum--darkest {$themeStore.theme}"
|
class="spectrum spectrum--medium {$themeStore.baseTheme} {$themeStore.theme}"
|
||||||
>
|
>
|
||||||
<DeviceBindingsProvider>
|
<DeviceBindingsProvider>
|
||||||
<UserBindingsProvider>
|
<UserBindingsProvider>
|
||||||
|
|
|
@ -17,10 +17,16 @@
|
||||||
--spectrum-semantic-cta-color-background-default: var(--primaryColor);
|
--spectrum-semantic-cta-color-background-default: var(--primaryColor);
|
||||||
--spectrum-semantic-cta-color-background-hover: var(--primaryColorHover);
|
--spectrum-semantic-cta-color-background-hover: var(--primaryColorHover);
|
||||||
--spectrum-semantic-cta-color-background-down: var(--primaryColorHover);
|
--spectrum-semantic-cta-color-background-down: var(--primaryColorHover);
|
||||||
--spectrum-button-primary-s-border-radius: var(--buttonBorderRadius);
|
--spectrum-button-primary-s-border-radius: calc(
|
||||||
|
var(--buttonBorderRadius) * 0.9
|
||||||
|
);
|
||||||
--spectrum-button-primary-m-border-radius: var(--buttonBorderRadius);
|
--spectrum-button-primary-m-border-radius: var(--buttonBorderRadius);
|
||||||
--spectrum-button-primary-l-border-radius: var(--buttonBorderRadius);
|
--spectrum-button-primary-l-border-radius: calc(
|
||||||
--spectrum-button-primary-xl-border-radius: var(--buttonBorderRadius);
|
var(--buttonBorderRadius) * 1.25
|
||||||
|
);
|
||||||
|
--spectrum-button-primary-xl-border-radius: calc(
|
||||||
|
var(--buttonBorderRadius) * 1.5
|
||||||
|
);
|
||||||
|
|
||||||
/* Loading spinners */
|
/* Loading spinners */
|
||||||
--spectrum-progresscircle-medium-track-fill-color: var(--primaryColor);
|
--spectrum-progresscircle-medium-track-fill-color: var(--primaryColor);
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
export let size
|
export let size
|
||||||
export let gap
|
export let gap
|
||||||
export let wrap
|
export let wrap
|
||||||
|
export let onClick
|
||||||
|
|
||||||
$: directionClass = direction ? `valid-container direction-${direction}` : ""
|
$: directionClass = direction ? `valid-container direction-${direction}` : ""
|
||||||
$: hAlignClass = hAlign ? `hAlign-${hAlign}` : ""
|
$: hAlignClass = hAlign ? `hAlign-${hAlign}` : ""
|
||||||
|
@ -25,7 +26,13 @@
|
||||||
].join(" ")
|
].join(" ")
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class={classNames} use:styleable={$component.styles} class:wrap>
|
<div
|
||||||
|
class={classNames}
|
||||||
|
class:clickable={!!onClick}
|
||||||
|
use:styleable={$component.styles}
|
||||||
|
class:wrap
|
||||||
|
on:click={onClick}
|
||||||
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -104,4 +111,10 @@
|
||||||
.wrap {
|
.wrap {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
.clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.clickable :global(*) {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -10,7 +10,9 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if options}
|
{#if options}
|
||||||
<div use:chart={options} use:styleable={$component.styles} />
|
{#key options.customColor}
|
||||||
|
<div use:chart={options} use:styleable={$component.styles} />
|
||||||
|
{/key}
|
||||||
{:else if $builderStore.inBuilder}
|
{:else if $builderStore.inBuilder}
|
||||||
<div use:styleable={$component.styles}>
|
<div use:styleable={$component.styles}>
|
||||||
<Placeholder />
|
<Placeholder />
|
||||||
|
|
|
@ -62,8 +62,14 @@ export class ApexOptionsBuilder {
|
||||||
return this.setOption(["title", "text"], title)
|
return this.setOption(["title", "text"], title)
|
||||||
}
|
}
|
||||||
|
|
||||||
color(color) {
|
colors(colors) {
|
||||||
return this.setOption(["colors"], [color])
|
if (!colors) {
|
||||||
|
delete this.options.colors
|
||||||
|
this.options["customColor"] = false
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
this.options["customColor"] = true
|
||||||
|
return this.setOption(["colors"], colors)
|
||||||
}
|
}
|
||||||
|
|
||||||
width(width) {
|
width(width) {
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
export let stacked
|
export let stacked
|
||||||
export let yAxisUnits
|
export let yAxisUnits
|
||||||
export let palette
|
export let palette
|
||||||
|
export let c1, c2, c3, c4, c5
|
||||||
export let horizontal
|
export let horizontal
|
||||||
|
|
||||||
$: options = setUpChart(
|
$: options = setUpChart(
|
||||||
|
@ -33,9 +34,13 @@
|
||||||
stacked,
|
stacked,
|
||||||
yAxisUnits,
|
yAxisUnits,
|
||||||
palette,
|
palette,
|
||||||
horizontal
|
horizontal,
|
||||||
|
c1 && c2 && c3 && c4 && c5 ? [c1, c2, c3, c4, c5] : null,
|
||||||
|
customColor
|
||||||
)
|
)
|
||||||
|
|
||||||
|
$: customColor = palette === "Custom"
|
||||||
|
|
||||||
const setUpChart = (
|
const setUpChart = (
|
||||||
title,
|
title,
|
||||||
dataProvider,
|
dataProvider,
|
||||||
|
@ -51,7 +56,9 @@
|
||||||
stacked,
|
stacked,
|
||||||
yAxisUnits,
|
yAxisUnits,
|
||||||
palette,
|
palette,
|
||||||
horizontal
|
horizontal,
|
||||||
|
colors,
|
||||||
|
customColor
|
||||||
) => {
|
) => {
|
||||||
const allCols = [labelColumn, ...(valueColumns || [null])]
|
const allCols = [labelColumn, ...(valueColumns || [null])]
|
||||||
if (
|
if (
|
||||||
|
@ -85,6 +92,7 @@
|
||||||
.stacked(stacked)
|
.stacked(stacked)
|
||||||
.palette(palette)
|
.palette(palette)
|
||||||
.horizontal(horizontal)
|
.horizontal(horizontal)
|
||||||
|
.colors(customColor ? colors : null)
|
||||||
|
|
||||||
// Add data
|
// Add data
|
||||||
let useDates = false
|
let useDates = false
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
export let legend
|
export let legend
|
||||||
export let yAxisUnits
|
export let yAxisUnits
|
||||||
export let palette
|
export let palette
|
||||||
|
export let c1, c2, c3, c4, c5
|
||||||
|
|
||||||
// Area specific props
|
// Area specific props
|
||||||
export let area
|
export let area
|
||||||
|
@ -40,9 +41,13 @@
|
||||||
palette,
|
palette,
|
||||||
area,
|
area,
|
||||||
stacked,
|
stacked,
|
||||||
gradient
|
gradient,
|
||||||
|
c1 && c2 && c3 && c4 && c5 ? [c1, c2, c3, c4, c5] : null,
|
||||||
|
customColor
|
||||||
)
|
)
|
||||||
|
|
||||||
|
$: customColor = palette === "Custom"
|
||||||
|
|
||||||
const setUpChart = (
|
const setUpChart = (
|
||||||
title,
|
title,
|
||||||
dataProvider,
|
dataProvider,
|
||||||
|
@ -60,7 +65,9 @@
|
||||||
palette,
|
palette,
|
||||||
area,
|
area,
|
||||||
stacked,
|
stacked,
|
||||||
gradient
|
gradient,
|
||||||
|
colors,
|
||||||
|
customColor
|
||||||
) => {
|
) => {
|
||||||
const allCols = [labelColumn, ...(valueColumns || [null])]
|
const allCols = [labelColumn, ...(valueColumns || [null])]
|
||||||
if (
|
if (
|
||||||
|
@ -96,6 +103,7 @@
|
||||||
.legend(legend)
|
.legend(legend)
|
||||||
.yUnits(yAxisUnits)
|
.yUnits(yAxisUnits)
|
||||||
.palette(palette)
|
.palette(palette)
|
||||||
|
.colors(customColor ? colors : null)
|
||||||
|
|
||||||
// Add data
|
// Add data
|
||||||
let useDates = false
|
let useDates = false
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
export let legend
|
export let legend
|
||||||
export let donut
|
export let donut
|
||||||
export let palette
|
export let palette
|
||||||
|
export let c1, c2, c3, c4, c5
|
||||||
|
|
||||||
$: options = setUpChart(
|
$: options = setUpChart(
|
||||||
title,
|
title,
|
||||||
|
@ -25,9 +26,13 @@
|
||||||
animate,
|
animate,
|
||||||
legend,
|
legend,
|
||||||
donut,
|
donut,
|
||||||
palette
|
palette,
|
||||||
|
c1 && c2 && c3 && c4 && c5 ? [c1, c2, c3, c4, c5] : null,
|
||||||
|
customColor
|
||||||
)
|
)
|
||||||
|
|
||||||
|
$: customColor = palette === "Custom"
|
||||||
|
|
||||||
const setUpChart = (
|
const setUpChart = (
|
||||||
title,
|
title,
|
||||||
dataProvider,
|
dataProvider,
|
||||||
|
@ -39,7 +44,9 @@
|
||||||
animate,
|
animate,
|
||||||
legend,
|
legend,
|
||||||
donut,
|
donut,
|
||||||
palette
|
palette,
|
||||||
|
colors,
|
||||||
|
customColor
|
||||||
) => {
|
) => {
|
||||||
if (
|
if (
|
||||||
!dataProvider ||
|
!dataProvider ||
|
||||||
|
@ -70,6 +77,7 @@
|
||||||
.legend(legend)
|
.legend(legend)
|
||||||
.legendPosition("right")
|
.legendPosition("right")
|
||||||
.palette(palette)
|
.palette(palette)
|
||||||
|
.colors(customColor ? colors : null)
|
||||||
|
|
||||||
// Add data if valid datasource
|
// Add data if valid datasource
|
||||||
const series = data.map(row => parseFloat(row[valueColumn]))
|
const series = data.map(row => parseFloat(row[valueColumn]))
|
||||||
|
|
|
@ -47,6 +47,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deleteAttachments = async fileList => {
|
||||||
|
try {
|
||||||
|
return await API.deleteAttachments({
|
||||||
|
keys: fileList,
|
||||||
|
tableId: formContext?.dataSource?.tableId,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleChange = e => {
|
const handleChange = e => {
|
||||||
fieldApi.setValue(e.detail)
|
fieldApi.setValue(e.detail)
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
|
@ -72,6 +83,7 @@
|
||||||
error={fieldState.error}
|
error={fieldState.error}
|
||||||
on:change={handleChange}
|
on:change={handleChange}
|
||||||
{processFiles}
|
{processFiles}
|
||||||
|
{deleteAttachments}
|
||||||
{handleFileTooLarge}
|
{handleFileTooLarge}
|
||||||
{handleTooManyFiles}
|
{handleTooManyFiles}
|
||||||
{maximum}
|
{maximum}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let enableTime = false
|
export let enableTime = false
|
||||||
export let timeOnly = false
|
export let timeOnly = false
|
||||||
|
export let time24hr = false
|
||||||
export let ignoreTimezones = false
|
export let ignoreTimezones = false
|
||||||
export let validation
|
export let validation
|
||||||
export let defaultValue
|
export let defaultValue
|
||||||
|
@ -44,6 +45,7 @@
|
||||||
appendTo={document.getElementById("flatpickr-root")}
|
appendTo={document.getElementById("flatpickr-root")}
|
||||||
{enableTime}
|
{enableTime}
|
||||||
{timeOnly}
|
{timeOnly}
|
||||||
|
{time24hr}
|
||||||
{ignoreTimezones}
|
{ignoreTimezones}
|
||||||
{placeholder}
|
{placeholder}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -5,6 +5,8 @@ import "@spectrum-css/vars/dist/spectrum-darkest.css"
|
||||||
import "@spectrum-css/vars/dist/spectrum-dark.css"
|
import "@spectrum-css/vars/dist/spectrum-dark.css"
|
||||||
import "@spectrum-css/vars/dist/spectrum-light.css"
|
import "@spectrum-css/vars/dist/spectrum-light.css"
|
||||||
import "@spectrum-css/vars/dist/spectrum-lightest.css"
|
import "@spectrum-css/vars/dist/spectrum-lightest.css"
|
||||||
|
import "@budibase/frontend-core/src/themes/nord.css"
|
||||||
|
import "@budibase/frontend-core/src/themes/midnight.css"
|
||||||
import "@spectrum-css/page/dist/index-vars.css"
|
import "@spectrum-css/page/dist/index-vars.css"
|
||||||
|
|
||||||
// Non user-facing components
|
// Non user-facing components
|
||||||
|
|
|
@ -16,20 +16,20 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
const onKeyDown = e => {
|
const onKeyDown = e => {
|
||||||
if (e.key === "Delete" || e.key === "Backspace") {
|
|
||||||
deleteSelectedComponent()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteSelectedComponent = () => {
|
|
||||||
const state = get(builderStore)
|
const state = get(builderStore)
|
||||||
if (!state.inBuilder || !state.selectedComponentId || state.editMode) {
|
if (!state.inBuilder || state.editMode) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const activeTag = document.activeElement?.tagName.toLowerCase()
|
const activeTag = document.activeElement?.tagName.toLowerCase()
|
||||||
if (["input", "textarea"].indexOf(activeTag) !== -1) {
|
if (["input", "textarea"].indexOf(activeTag) !== -1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
builderStore.actions.deleteComponent(state.selectedComponentId)
|
|
||||||
|
// Need to manually block certain keys from propagating to the browser
|
||||||
|
if (e.ctrlKey && e.key === "d") {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
builderStore.actions.keyDown(e.key, e.ctrlKey)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -40,8 +40,8 @@ const createBuilderStore = () => {
|
||||||
updateProp: (prop, value) => {
|
updateProp: (prop, value) => {
|
||||||
dispatchEvent("update-prop", { prop, value })
|
dispatchEvent("update-prop", { prop, value })
|
||||||
},
|
},
|
||||||
deleteComponent: id => {
|
keyDown: (key, ctrlKey) => {
|
||||||
dispatchEvent("delete-component", { id })
|
dispatchEvent("key-down", { key, ctrlKey })
|
||||||
},
|
},
|
||||||
duplicateComponent: id => {
|
duplicateComponent: id => {
|
||||||
dispatchEvent("duplicate-component", { id })
|
dispatchEvent("duplicate-component", { id })
|
||||||
|
|
|
@ -62,10 +62,14 @@ const createNotificationStore = () => {
|
||||||
subscribe: store.subscribe,
|
subscribe: store.subscribe,
|
||||||
actions: {
|
actions: {
|
||||||
send,
|
send,
|
||||||
info: msg => send(msg, "info", "Info"),
|
info: (msg, autoDismiss) =>
|
||||||
success: msg => send(msg, "success", "CheckmarkCircle"),
|
send(msg, "info", "Info", autoDismiss ?? true),
|
||||||
warning: msg => send(msg, "warning", "Alert"),
|
success: (msg, autoDismiss) =>
|
||||||
error: msg => send(msg, "error", "Alert", false),
|
send(msg, "success", "CheckmarkCircle", autoDismiss ?? true),
|
||||||
|
warning: (msg, autoDismiss) =>
|
||||||
|
send(msg, "warning", "Alert", autoDismiss ?? true),
|
||||||
|
error: (msg, autoDismiss) =>
|
||||||
|
send(msg, "error", "Alert", autoDismiss ?? false),
|
||||||
blockNotifications,
|
blockNotifications,
|
||||||
dismiss,
|
dismiss,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { derived } from "svelte/store"
|
import { derived } from "svelte/store"
|
||||||
import { appStore } from "./app"
|
import { appStore } from "./app"
|
||||||
import { builderStore } from "./builder"
|
import { builderStore } from "./builder"
|
||||||
|
import { Constants } from "@budibase/frontend-core"
|
||||||
|
|
||||||
// This is the good old acorn bug where having the word "g l o b a l" makes it
|
// This is the good old acorn bug where having the word "g l o b a l" makes it
|
||||||
// think that this is not ES6 compatible and starts throwing errors when using
|
// think that this is not ES6 compatible and starts throwing errors when using
|
||||||
|
@ -28,6 +29,13 @@ const createThemeStore = () => {
|
||||||
// Ensure theme is set
|
// Ensure theme is set
|
||||||
theme = theme || defaultTheme
|
theme = theme || defaultTheme
|
||||||
|
|
||||||
|
// Get base theme
|
||||||
|
let base =
|
||||||
|
Constants.Themes.find(x => `spectrum--${x.class}` === theme)?.base || ""
|
||||||
|
if (base) {
|
||||||
|
base = `spectrum--${base}`
|
||||||
|
}
|
||||||
|
|
||||||
// Delete and nullish keys from the custom theme
|
// Delete and nullish keys from the custom theme
|
||||||
if (customTheme) {
|
if (customTheme) {
|
||||||
Object.entries(customTheme).forEach(([key, value]) => {
|
Object.entries(customTheme).forEach(([key, value]) => {
|
||||||
|
@ -51,6 +59,7 @@ const createThemeStore = () => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
theme,
|
theme,
|
||||||
|
baseTheme: base,
|
||||||
customTheme,
|
customTheme,
|
||||||
customThemeCss,
|
customThemeCss,
|
||||||
}
|
}
|
||||||
|
|
|
@ -300,6 +300,14 @@ const continueIfHandler = action => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showNotificationHandler = action => {
|
||||||
|
const { message, type, autoDismiss } = action.parameters
|
||||||
|
if (!message || !type) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
notificationStore.actions[type]?.(message, autoDismiss)
|
||||||
|
}
|
||||||
|
|
||||||
const handlerMap = {
|
const handlerMap = {
|
||||||
["Save Row"]: saveRowHandler,
|
["Save Row"]: saveRowHandler,
|
||||||
["Duplicate Row"]: duplicateRowHandler,
|
["Duplicate Row"]: duplicateRowHandler,
|
||||||
|
@ -318,6 +326,7 @@ const handlerMap = {
|
||||||
["Upload File to S3"]: s3UploadHandler,
|
["Upload File to S3"]: s3UploadHandler,
|
||||||
["Export Data"]: exportDataHandler,
|
["Export Data"]: exportDataHandler,
|
||||||
["Continue if / Stop if"]: continueIfHandler,
|
["Continue if / Stop if"]: continueIfHandler,
|
||||||
|
["Show Notification"]: showNotificationHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirmTextMap = {
|
const confirmTextMap = {
|
||||||
|
@ -334,8 +343,8 @@ const confirmTextMap = {
|
||||||
*/
|
*/
|
||||||
export const enrichButtonActions = (actions, context) => {
|
export const enrichButtonActions = (actions, context) => {
|
||||||
// Prevent button actions in the builder preview
|
// Prevent button actions in the builder preview
|
||||||
if (!actions || get(builderStore).inBuilder) {
|
if (!actions?.length || get(builderStore).inBuilder) {
|
||||||
return () => {}
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this is a function then it has already been enriched
|
// If this is a function then it has already been enriched
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/frontend-core",
|
"name": "@budibase/frontend-core",
|
||||||
"version": "1.2.47",
|
"version": "1.2.57",
|
||||||
"description": "Budibase frontend core libraries used in builder and client",
|
"description": "Budibase frontend core libraries used in builder and client",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.2.47",
|
"@budibase/bbui": "^1.2.57",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"svelte": "^3.46.2"
|
"svelte": "^3.46.2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,5 +61,32 @@ export const buildAttachmentEndpoints = API => {
|
||||||
})
|
})
|
||||||
return { publicUrl }
|
return { publicUrl }
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes attachments from the bucket.
|
||||||
|
* @param keys the attachments to delete
|
||||||
|
* @param tableId the associated table ID
|
||||||
|
*/
|
||||||
|
deleteAttachments: async ({ keys, tableId }) => {
|
||||||
|
return await API.post({
|
||||||
|
url: `/api/attachments/${tableId}/delete`,
|
||||||
|
body: {
|
||||||
|
keys,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes attachments from the builder bucket.
|
||||||
|
* @param keys the attachments to delete
|
||||||
|
*/
|
||||||
|
deleteBuilderAttachments: async keys => {
|
||||||
|
return await API.post({
|
||||||
|
url: `/api/attachments/delete`,
|
||||||
|
body: {
|
||||||
|
keys,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,13 +39,17 @@ export const OperatorOptions = {
|
||||||
label: "Contains",
|
label: "Contains",
|
||||||
},
|
},
|
||||||
NotContains: {
|
NotContains: {
|
||||||
value: "notEqual",
|
value: "notContains",
|
||||||
label: "Does Not Contain",
|
label: "Does Not Contain",
|
||||||
},
|
},
|
||||||
In: {
|
In: {
|
||||||
value: "oneOf",
|
value: "oneOf",
|
||||||
label: "Is in",
|
label: "Is in",
|
||||||
},
|
},
|
||||||
|
ContainsAny: {
|
||||||
|
value: "containsAny",
|
||||||
|
label: "Has any",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cookie names
|
// Cookie names
|
||||||
|
|
|
@ -12,5 +12,7 @@
|
||||||
--spectrum-global-color-gray-700: hsl(var(--hue), var(--sat), 70%);
|
--spectrum-global-color-gray-700: hsl(var(--hue), var(--sat), 70%);
|
||||||
--spectrum-global-color-gray-800: hsl(var(--hue), var(--sat), 80%);
|
--spectrum-global-color-gray-800: hsl(var(--hue), var(--sat), 80%);
|
||||||
--spectrum-global-color-gray-900: hsl(var(--hue), var(--sat), 95%);
|
--spectrum-global-color-gray-900: hsl(var(--hue), var(--sat), 95%);
|
||||||
|
|
||||||
|
--modal-background: var(--spectrum-global-color-gray-50);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,4 +43,7 @@
|
||||||
|
|
||||||
--spectrum-alias-highlight-hover: rgba(169, 177, 193, 0.1);
|
--spectrum-alias-highlight-hover: rgba(169, 177, 193, 0.1);
|
||||||
--spectrum-alias-highlight-active: rgba(169, 177, 193, 0.1);
|
--spectrum-alias-highlight-active: rgba(169, 177, 193, 0.1);
|
||||||
|
--spectrum-alias-background-color-hover-overlay: rgba(169, 177, 193, 0.1);
|
||||||
|
|
||||||
|
--modal-background: var(--spectrum-global-color-gray-50);
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue