Merge branch 'develop' into backmerge-20230717
This commit is contained in:
commit
13ef7b8858
|
@ -5,7 +5,7 @@
|
||||||
"jest": true,
|
"jest": true,
|
||||||
"node": true
|
"node": true
|
||||||
},
|
},
|
||||||
"parser": "babel-eslint",
|
"parser": "@babel/eslint-parser",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 2019,
|
"ecmaVersion": 2019,
|
||||||
"sourceType": "module",
|
"sourceType": "module",
|
||||||
|
@ -18,17 +18,23 @@
|
||||||
"*.spec.js",
|
"*.spec.js",
|
||||||
"bundle.js"
|
"bundle.js"
|
||||||
],
|
],
|
||||||
"plugins": ["svelte3"],
|
|
||||||
"extends": ["eslint:recommended"],
|
"extends": ["eslint:recommended"],
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": ["*.svelte"],
|
"files": ["**/*.svelte"],
|
||||||
"processor": "svelte3/svelte3"
|
"extends": "plugin:svelte/recommended",
|
||||||
|
"parser": "svelte-eslint-parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"parser": "@babel/eslint-parser",
|
||||||
|
"ecmaVersion": 2019,
|
||||||
|
"sourceType": "module",
|
||||||
|
"allowImportExportEverywhere": true
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"files": ["**/*.ts"],
|
"files": ["**/*.ts"],
|
||||||
"parser": "@typescript-eslint/parser",
|
"parser": "@typescript-eslint/parser",
|
||||||
"plugins": [],
|
|
||||||
"extends": ["eslint:recommended"],
|
"extends": ["eslint:recommended"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
|
@ -41,7 +47,8 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-self-assign": "off"
|
"no-self-assign": "off",
|
||||||
|
"no-unused-vars": ["error", { "varsIgnorePattern": "^_", "argsIgnorePattern": "^_", "destructuredArrayIgnorePattern": "^_" }]
|
||||||
},
|
},
|
||||||
"globals": {
|
"globals": {
|
||||||
"GeolocationPositionError": true
|
"GeolocationPositionError": true
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
# Configuration for probot-stale - https://github.com/probot/stale
|
|
||||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
|
||||||
daysUntilStale: 60
|
|
||||||
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
|
||||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
|
||||||
daysUntilClose: false
|
|
||||||
# Issues with these labels will never be considered stale
|
|
||||||
exemptLabels:
|
|
||||||
- pinned
|
|
||||||
- security
|
|
||||||
- roadmap
|
|
||||||
# Label to use when marking an issue as stale
|
|
||||||
staleLabel: stale
|
|
||||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
|
||||||
markComment: >
|
|
||||||
This issue has been automatically marked as stale because it has not had
|
|
||||||
recent activity.
|
|
||||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
|
||||||
closeComment: false
|
|
|
@ -185,7 +185,7 @@ jobs:
|
||||||
pro_commit=$(git rev-parse HEAD)
|
pro_commit=$(git rev-parse HEAD)
|
||||||
|
|
||||||
branch="${{ github.base_ref || github.ref_name }}"
|
branch="${{ github.base_ref || github.ref_name }}"
|
||||||
echo "Running on branch `$branch` (base_ref=${{ github.base_ref }}, ref_name=${{ github.head_ref }})"
|
echo "Running on branch '$branch' (base_ref=${{ github.base_ref }}, ref_name=${{ github.head_ref }})"
|
||||||
|
|
||||||
if [[ $branch == "master" ]]; then
|
if [[ $branch == "master" ]]; then
|
||||||
base_commit=$(git rev-parse origin/master)
|
base_commit=$(git rev-parse origin/master)
|
||||||
|
|
|
@ -34,7 +34,6 @@ jobs:
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
|
@ -58,9 +57,12 @@ jobs:
|
||||||
echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} >> .npmrc
|
echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} >> .npmrc
|
||||||
yarn release
|
yarn release
|
||||||
|
|
||||||
- name: "Get Previous tag"
|
- name: "Get Current tag"
|
||||||
id: previoustag
|
id: currenttag
|
||||||
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
run: |
|
||||||
|
version=v$(./scripts/getCurrentVersion.sh)
|
||||||
|
echo 'Using tag $version'
|
||||||
|
echo "::set-output name=tag::$resversionult"
|
||||||
|
|
||||||
- name: Build/release Docker images
|
- name: Build/release Docker images
|
||||||
run: |
|
run: |
|
||||||
|
@ -69,7 +71,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
|
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
|
||||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}
|
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}
|
||||||
BUDIBASE_RELEASE_VERSION: ${{ steps.previoustag.outputs.tag }}
|
BUDIBASE_RELEASE_VERSION: ${{ steps.currenttag.outputs.tag }}
|
||||||
|
|
||||||
release-helm-chart:
|
release-helm-chart:
|
||||||
needs: [release-images]
|
needs: [release-images]
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
name: Close stale issues and PRs # https://github.com/actions/stale
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: '30 1 * * *' # 1:30 every morning
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v8
|
||||||
|
with:
|
||||||
|
# stale rules
|
||||||
|
days-before-stale: 60
|
||||||
|
days-before-pr-stale: 7
|
||||||
|
stale-issue-label: stale
|
||||||
|
stale-issue-message: "This issue has been automatically marked as stale because it has not had any activity for 60 days."
|
||||||
|
|
||||||
|
# close rules
|
||||||
|
# days after being marked as stale to close
|
||||||
|
days-before-close: 30
|
||||||
|
close-issue-label: closed-stale
|
||||||
|
close-issue-message: This issue has been automatically closed it has not had any activity in 90 days."
|
||||||
|
days-before-pr-close: 7
|
||||||
|
|
||||||
|
# exemptions
|
||||||
|
exempt-pr-labels: pinned,security,roadmap
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"presets": [["@babel/preset-env", { "targets": { "node": "current" } }]]
|
||||||
|
}
|
|
@ -28,3 +28,4 @@ BB_ADMIN_USER_PASSWORD=
|
||||||
|
|
||||||
# A path that is watched for plugin bundles. Any bundles found are imported automatically/
|
# A path that is watched for plugin bundles. Any bundles found are imported automatically/
|
||||||
PLUGINS_DIR=
|
PLUGINS_DIR=
|
||||||
|
ROLLING_LOG_MAX_SIZE=
|
12
nx.json
12
nx.json
|
@ -1,9 +1,13 @@
|
||||||
{
|
{
|
||||||
"tasksRunnerOptions": {
|
"tasksRunnerOptions": {
|
||||||
"default": {
|
"default": {
|
||||||
"runner": "nx/tasks-runners/default",
|
"runner": "nx-cloud",
|
||||||
"options": {
|
"options": {
|
||||||
"cacheableOperations": ["build", "test"]
|
"cacheableOperations": [
|
||||||
|
"build",
|
||||||
|
"test"
|
||||||
|
],
|
||||||
|
"accessToken": "YWNiYzc5NTEtMzMzZC00NDhjLTgyNjktZTllMjI1MzM4OGQxfHJlYWQtd3JpdGU="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -11,7 +15,9 @@
|
||||||
"dev:builder": {
|
"dev:builder": {
|
||||||
"dependsOn": [
|
"dependsOn": [
|
||||||
{
|
{
|
||||||
"projects": ["@budibase/string-templates"],
|
"projects": [
|
||||||
|
"@budibase/string-templates"
|
||||||
|
],
|
||||||
"target": "build"
|
"target": "build"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
31
package.json
31
package.json
|
@ -3,27 +3,32 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@esbuild-plugins/tsconfig-paths": "^0.1.2",
|
"@esbuild-plugins/tsconfig-paths": "^0.1.2",
|
||||||
"@nx/js": "16.2.1",
|
"@nx/js": "16.4.3",
|
||||||
"@rollup/plugin-json": "^4.0.2",
|
"@rollup/plugin-json": "^4.0.2",
|
||||||
"@typescript-eslint/parser": "5.45.0",
|
"@typescript-eslint/parser": "5.45.0",
|
||||||
"babel-eslint": "^10.0.3",
|
|
||||||
"esbuild": "^0.17.18",
|
"esbuild": "^0.17.18",
|
||||||
"esbuild-node-externals": "^1.7.0",
|
"esbuild-node-externals": "^1.7.0",
|
||||||
"eslint": "^7.28.0",
|
"eslint": "^8.44.0",
|
||||||
"eslint-plugin-cypress": "^2.11.3",
|
"eslint-plugin-cypress": "^2.11.3",
|
||||||
"eslint-plugin-svelte3": "^3.2.0",
|
|
||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"kill-port": "^1.6.1",
|
"kill-port": "^1.6.1",
|
||||||
"lerna": "7.0.2",
|
"lerna": "7.1.1",
|
||||||
"madge": "^6.0.0",
|
"madge": "^6.0.0",
|
||||||
"minimist": "^1.2.8",
|
"minimist": "^1.2.8",
|
||||||
|
"nx": "16.4.3",
|
||||||
|
"nx-cloud": "16.0.5",
|
||||||
"prettier": "2.8.8",
|
"prettier": "2.8.8",
|
||||||
"prettier-plugin-svelte": "^2.3.0",
|
"prettier-plugin-svelte": "^2.3.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rollup-plugin-replace": "^2.2.0",
|
"rollup-plugin-replace": "^2.2.0",
|
||||||
"svelte": "^3.38.2",
|
"svelte": "^3.38.2",
|
||||||
"typescript": "4.7.3"
|
"typescript": "4.7.3",
|
||||||
|
"@babel/core": "^7.22.5",
|
||||||
|
"@babel/eslint-parser": "^7.22.5",
|
||||||
|
"@babel/preset-env": "^7.22.5",
|
||||||
|
"eslint-plugin-svelte": "^2.32.2",
|
||||||
|
"svelte-eslint-parser": "^0.32.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "node scripts/syncProPackage.js",
|
"preinstall": "node scripts/syncProPackage.js",
|
||||||
|
@ -41,21 +46,21 @@
|
||||||
"restore": "yarn run clean && yarn run bootstrap && yarn run build",
|
"restore": "yarn run clean && yarn run bootstrap && yarn run build",
|
||||||
"nuke": "yarn run nuke:packages && yarn run nuke:docker",
|
"nuke": "yarn run nuke:packages && yarn run nuke:docker",
|
||||||
"nuke:packages": "yarn run restore",
|
"nuke:packages": "yarn run restore",
|
||||||
"nuke:docker": "lerna run --stream --parallel dev:stack:nuke",
|
"nuke:docker": "lerna run --stream dev:stack:nuke",
|
||||||
"clean": "lerna clean",
|
"clean": "lerna clean",
|
||||||
"kill-builder": "kill-port 3000",
|
"kill-builder": "kill-port 3000",
|
||||||
"kill-server": "kill-port 4001 4002",
|
"kill-server": "kill-port 4001 4002",
|
||||||
"kill-all": "yarn run kill-builder && yarn run kill-server",
|
"kill-all": "yarn run kill-builder && yarn run kill-server",
|
||||||
"dev": "yarn run kill-all && lerna run --stream --parallel dev:builder --stream",
|
"dev": "yarn run kill-all && lerna run --stream dev:builder --stream",
|
||||||
"dev:noserver": "yarn run kill-builder && lerna run --stream dev:stack:up && lerna run --stream --parallel dev:builder --ignore @budibase/backend-core --ignore @budibase/server --ignore @budibase/worker",
|
"dev:noserver": "yarn run kill-builder && lerna run --stream dev:stack:up && lerna run --stream dev:builder --ignore @budibase/backend-core --ignore @budibase/server --ignore @budibase/worker",
|
||||||
"dev:server": "yarn run kill-server && yarn build --projects=@budibase/client && lerna run --stream --parallel dev:builder --scope @budibase/worker --scope @budibase/server",
|
"dev:server": "yarn run kill-server && yarn build --projects=@budibase/client && lerna run --stream dev:builder --scope @budibase/worker --scope @budibase/server",
|
||||||
"dev:built": "yarn run kill-all && cd packages/server && yarn dev:stack:up && cd ../../ && lerna run --stream --parallel dev:built",
|
"dev:built": "yarn run kill-all && cd packages/server && yarn dev:stack:up && cd ../../ && lerna run --stream dev:built",
|
||||||
"dev:docker": "yarn build:docker:pre && docker-compose -f hosting/docker-compose.build.yaml -f hosting/docker-compose.dev.yaml --env-file hosting/.env up --build --scale proxy-service=0",
|
"dev:docker": "yarn build:docker:pre && docker-compose -f hosting/docker-compose.build.yaml -f hosting/docker-compose.dev.yaml --env-file hosting/.env up --build --scale proxy-service=0",
|
||||||
"test": "lerna run --stream test --stream",
|
"test": "lerna run --stream test --stream",
|
||||||
"lint:eslint": "eslint packages && eslint qa-core",
|
"lint:eslint": "eslint packages qa-core --max-warnings=0",
|
||||||
"lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --check \"qa-core/**/*.{js,ts,svelte}\"",
|
"lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --check \"qa-core/**/*.{js,ts,svelte}\"",
|
||||||
"lint": "yarn run lint:eslint && yarn run lint:prettier",
|
"lint": "yarn run lint:eslint && yarn run lint:prettier",
|
||||||
"lint:fix:eslint": "eslint --fix packages qa-core",
|
"lint:fix:eslint": "eslint --fix --max-warnings=0 packages qa-core",
|
||||||
"lint:fix:prettier": "prettier --write \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --write \"qa-core/**/*.{js,ts,svelte}\"",
|
"lint:fix:prettier": "prettier --write \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --write \"qa-core/**/*.{js,ts,svelte}\"",
|
||||||
"lint:fix": "yarn run lint:fix:prettier && yarn run lint:fix:eslint",
|
"lint:fix": "yarn run lint:fix:prettier && yarn run lint:fix:eslint",
|
||||||
"build:specs": "lerna run --stream specs",
|
"build:specs": "lerna run --stream specs",
|
||||||
|
|
|
@ -51,6 +51,7 @@
|
||||||
"pouchdb": "7.3.0",
|
"pouchdb": "7.3.0",
|
||||||
"pouchdb-find": "7.2.2",
|
"pouchdb-find": "7.2.2",
|
||||||
"redlock": "4.2.0",
|
"redlock": "4.2.0",
|
||||||
|
"rotating-file-stream": "3.1.0",
|
||||||
"sanitize-s3-objectkey": "0.0.1",
|
"sanitize-s3-objectkey": "0.0.1",
|
||||||
"semver": "7.3.7",
|
"semver": "7.3.7",
|
||||||
"tar-fs": "2.1.1",
|
"tar-fs": "2.1.1",
|
||||||
|
|
|
@ -47,7 +47,10 @@ function httpLogging() {
|
||||||
return process.env.HTTP_LOGGING
|
return process.env.HTTP_LOGGING
|
||||||
}
|
}
|
||||||
|
|
||||||
function findVersion() {
|
function getPackageJsonFields(): {
|
||||||
|
VERSION: string
|
||||||
|
SERVICE_NAME: string
|
||||||
|
} {
|
||||||
function findFileInAncestors(
|
function findFileInAncestors(
|
||||||
fileName: string,
|
fileName: string,
|
||||||
currentDir: string
|
currentDir: string
|
||||||
|
@ -69,10 +72,14 @@ function findVersion() {
|
||||||
try {
|
try {
|
||||||
const packageJsonFile = findFileInAncestors("package.json", process.cwd())
|
const packageJsonFile = findFileInAncestors("package.json", process.cwd())
|
||||||
const content = readFileSync(packageJsonFile!, "utf-8")
|
const content = readFileSync(packageJsonFile!, "utf-8")
|
||||||
return JSON.parse(content).version
|
const parsedContent = JSON.parse(content)
|
||||||
|
return {
|
||||||
|
VERSION: parsedContent.version,
|
||||||
|
SERVICE_NAME: parsedContent.name,
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// throwing an error here is confusing/causes backend-core to be hard to import
|
// throwing an error here is confusing/causes backend-core to be hard to import
|
||||||
return undefined
|
return { VERSION: "", SERVICE_NAME: "" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,13 +161,14 @@ const environment = {
|
||||||
ENABLE_SSO_MAINTENANCE_MODE: selfHosted
|
ENABLE_SSO_MAINTENANCE_MODE: selfHosted
|
||||||
? process.env.ENABLE_SSO_MAINTENANCE_MODE
|
? process.env.ENABLE_SSO_MAINTENANCE_MODE
|
||||||
: false,
|
: false,
|
||||||
VERSION: findVersion(),
|
...getPackageJsonFields(),
|
||||||
DISABLE_PINO_LOGGER: process.env.DISABLE_PINO_LOGGER,
|
DISABLE_PINO_LOGGER: process.env.DISABLE_PINO_LOGGER,
|
||||||
_set(key: any, value: any) {
|
_set(key: any, value: any) {
|
||||||
process.env[key] = value
|
process.env[key] = value
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
environment[key] = value
|
environment[key] = value
|
||||||
},
|
},
|
||||||
|
ROLLING_LOG_MAX_SIZE: process.env.ROLLING_LOG_MAX_SIZE || "10M",
|
||||||
}
|
}
|
||||||
|
|
||||||
// clean up any environment variable edge cases
|
// clean up any environment variable edge cases
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
export * as correlation from "./correlation/correlation"
|
export * as correlation from "./correlation/correlation"
|
||||||
export { logger } from "./pino/logger"
|
export { logger } from "./pino/logger"
|
||||||
export * from "./alerts"
|
export * from "./alerts"
|
||||||
|
export * as system from "./system"
|
||||||
|
|
||||||
// turn off or on context logging i.e. tenantId, appId etc
|
// turn off or on context logging i.e. tenantId, appId etc
|
||||||
export let LOG_CONTEXT = true
|
export let LOG_CONTEXT = true
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
import env from "../../environment"
|
|
||||||
import pino, { LoggerOptions } from "pino"
|
import pino, { LoggerOptions } from "pino"
|
||||||
|
import pinoPretty from "pino-pretty"
|
||||||
|
|
||||||
|
import { IdentityType } from "@budibase/types"
|
||||||
|
|
||||||
|
import env from "../../environment"
|
||||||
import * as context from "../../context"
|
import * as context from "../../context"
|
||||||
import * as correlation from "../correlation"
|
import * as correlation from "../correlation"
|
||||||
import { IdentityType } from "@budibase/types"
|
|
||||||
import { LOG_CONTEXT } from "../index"
|
import { LOG_CONTEXT } from "../index"
|
||||||
|
|
||||||
|
import { localFileDestination } from "../system"
|
||||||
|
|
||||||
// LOGGER
|
// LOGGER
|
||||||
|
|
||||||
let pinoInstance: pino.Logger | undefined
|
let pinoInstance: pino.Logger | undefined
|
||||||
|
@ -16,22 +21,27 @@ if (!env.DISABLE_PINO_LOGGER) {
|
||||||
return { level: label.toUpperCase() }
|
return { level: label.toUpperCase() }
|
||||||
},
|
},
|
||||||
bindings: () => {
|
bindings: () => {
|
||||||
return {}
|
return {
|
||||||
|
service: env.SERVICE_NAME,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
timestamp: () => `,"timestamp":"${new Date(Date.now()).toISOString()}"`,
|
timestamp: () => `,"timestamp":"${new Date(Date.now()).toISOString()}"`,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const destinations: pino.DestinationStream[] = []
|
||||||
|
|
||||||
if (env.isDev()) {
|
if (env.isDev()) {
|
||||||
pinoOptions.transport = {
|
destinations.push(pinoPretty({ singleLine: true }))
|
||||||
target: "pino-pretty",
|
|
||||||
options: {
|
|
||||||
singleLine: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pinoInstance = pino(pinoOptions)
|
if (env.SELF_HOSTED) {
|
||||||
|
destinations.push(localFileDestination())
|
||||||
|
}
|
||||||
|
|
||||||
|
pinoInstance = destinations.length
|
||||||
|
? pino(pinoOptions, pino.multistream(destinations))
|
||||||
|
: pino(pinoOptions)
|
||||||
|
|
||||||
// CONSOLE OVERRIDES
|
// CONSOLE OVERRIDES
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
import fs from "fs"
|
||||||
|
import path from "path"
|
||||||
|
import * as rfs from "rotating-file-stream"
|
||||||
|
|
||||||
|
import env from "../environment"
|
||||||
|
import { budibaseTempDir } from "../objectStore"
|
||||||
|
|
||||||
|
const logsFileName = `budibase.log`
|
||||||
|
const budibaseLogsHistoryFileName = "budibase-logs-history.txt"
|
||||||
|
|
||||||
|
const logsPath = path.join(budibaseTempDir(), "systemlogs")
|
||||||
|
|
||||||
|
function getFullPath(fileName: string) {
|
||||||
|
return path.join(logsPath, fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSingleFileMaxSizeInfo(totalMaxSize: string) {
|
||||||
|
const regex = /(\d+)([A-Za-z])/
|
||||||
|
const match = totalMaxSize?.match(regex)
|
||||||
|
if (!match) {
|
||||||
|
console.warn(`totalMaxSize does not have a valid value`, {
|
||||||
|
totalMaxSize,
|
||||||
|
})
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const size = +match[1]
|
||||||
|
const unit = match[2]
|
||||||
|
if (size === 1) {
|
||||||
|
switch (unit) {
|
||||||
|
case "B":
|
||||||
|
return { size: `${size}B`, totalHistoryFiles: 1 }
|
||||||
|
case "K":
|
||||||
|
return { size: `${(size * 1000) / 2}B`, totalHistoryFiles: 1 }
|
||||||
|
case "M":
|
||||||
|
return { size: `${(size * 1000) / 2}K`, totalHistoryFiles: 1 }
|
||||||
|
case "G":
|
||||||
|
return { size: `${(size * 1000) / 2}M`, totalHistoryFiles: 1 }
|
||||||
|
default:
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size % 2 === 0) {
|
||||||
|
return { size: `${size / 2}${unit}`, totalHistoryFiles: 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
return { size: `1${unit}`, totalHistoryFiles: size - 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function localFileDestination() {
|
||||||
|
const fileInfo = getSingleFileMaxSizeInfo(env.ROLLING_LOG_MAX_SIZE)
|
||||||
|
const outFile = rfs.createStream(logsFileName, {
|
||||||
|
// As we have a rolling size, we want to half the max size
|
||||||
|
size: fileInfo?.size,
|
||||||
|
path: logsPath,
|
||||||
|
maxFiles: fileInfo?.totalHistoryFiles || 1,
|
||||||
|
immutable: true,
|
||||||
|
history: budibaseLogsHistoryFileName,
|
||||||
|
initialRotation: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
return outFile
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLogReadStream() {
|
||||||
|
const streams = []
|
||||||
|
const historyFile = getFullPath(budibaseLogsHistoryFileName)
|
||||||
|
if (fs.existsSync(historyFile)) {
|
||||||
|
const fileContent = fs.readFileSync(historyFile, "utf-8")
|
||||||
|
const historyFiles = fileContent.split("\n")
|
||||||
|
for (const historyFile of historyFiles.filter(x => x)) {
|
||||||
|
streams.push(fs.readFileSync(historyFile))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
streams.push(fs.readFileSync(getFullPath(logsFileName)))
|
||||||
|
|
||||||
|
const combinedContent = Buffer.concat(streams)
|
||||||
|
return combinedContent
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { getSingleFileMaxSizeInfo } from "../system"
|
||||||
|
|
||||||
|
describe("system", () => {
|
||||||
|
describe("getSingleFileMaxSizeInfo", () => {
|
||||||
|
it.each([
|
||||||
|
["100B", "50B"],
|
||||||
|
["200K", "100K"],
|
||||||
|
["20M", "10M"],
|
||||||
|
["4G", "2G"],
|
||||||
|
])(
|
||||||
|
"Halving even number (%s) returns halved size and 1 history file (%s)",
|
||||||
|
(totalValue, expectedMaxSize) => {
|
||||||
|
const result = getSingleFileMaxSizeInfo(totalValue)
|
||||||
|
expect(result).toEqual({
|
||||||
|
size: expectedMaxSize,
|
||||||
|
totalHistoryFiles: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
["5B", "1B", 4],
|
||||||
|
["17K", "1K", 16],
|
||||||
|
["21M", "1M", 20],
|
||||||
|
["3G", "1G", 2],
|
||||||
|
])(
|
||||||
|
"Halving an odd number (%s) returns as many files as size (-1) (%s)",
|
||||||
|
(totalValue, expectedMaxSize, totalHistoryFiles) => {
|
||||||
|
const result = getSingleFileMaxSizeInfo(totalValue)
|
||||||
|
expect(result).toEqual({
|
||||||
|
size: expectedMaxSize,
|
||||||
|
totalHistoryFiles,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
["1B", "1B"],
|
||||||
|
["1K", "500B"],
|
||||||
|
["1M", "500K"],
|
||||||
|
["1G", "500M"],
|
||||||
|
])(
|
||||||
|
"Halving '%s' returns halved unit (%s)",
|
||||||
|
(totalValue, expectedMaxSize) => {
|
||||||
|
const result = getSingleFileMaxSizeInfo(totalValue)
|
||||||
|
expect(result).toEqual({
|
||||||
|
size: expectedMaxSize,
|
||||||
|
totalHistoryFiles: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
it.each([[undefined], [""], ["50"], ["wrongvalue"]])(
|
||||||
|
"Halving wrongly formatted value ('%s') returns undefined",
|
||||||
|
totalValue => {
|
||||||
|
const result = getSingleFileMaxSizeInfo(totalValue!)
|
||||||
|
expect(result).toBeUndefined()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import "@spectrum-css/button/dist/index-vars.css"
|
import "@spectrum-css/button/dist/index-vars.css"
|
||||||
import Tooltip from "../Tooltip/Tooltip.svelte"
|
import AbsTooltip from "../Tooltip/AbsTooltip.svelte"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
export let type
|
export let type
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
|
@ -17,65 +18,52 @@
|
||||||
export let newStyles = true
|
export let newStyles = true
|
||||||
export let id
|
export let id
|
||||||
|
|
||||||
let showTooltip = false
|
const dispatch = createEventDispatcher()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<AbsTooltip text={tooltip}>
|
||||||
{id}
|
<button
|
||||||
{type}
|
{id}
|
||||||
class:spectrum-Button--cta={cta}
|
{type}
|
||||||
class:spectrum-Button--primary={primary}
|
class:spectrum-Button--cta={cta}
|
||||||
class:spectrum-Button--secondary={secondary}
|
class:spectrum-Button--primary={primary}
|
||||||
class:spectrum-Button--warning={warning}
|
class:spectrum-Button--secondary={secondary}
|
||||||
class:spectrum-Button--overBackground={overBackground}
|
class:spectrum-Button--warning={warning}
|
||||||
class:spectrum-Button--quiet={quiet}
|
class:spectrum-Button--overBackground={overBackground}
|
||||||
class:new-styles={newStyles}
|
class:spectrum-Button--quiet={quiet}
|
||||||
class:active
|
class:new-styles={newStyles}
|
||||||
class:disabled
|
class:active
|
||||||
class="spectrum-Button spectrum-Button--size{size.toUpperCase()}"
|
class:is-disabled={disabled}
|
||||||
{disabled}
|
class="spectrum-Button spectrum-Button--size{size.toUpperCase()}"
|
||||||
on:click|preventDefault
|
on:click|preventDefault={() => {
|
||||||
on:mouseover={() => (showTooltip = true)}
|
if (!disabled) {
|
||||||
on:focus={() => (showTooltip = true)}
|
dispatch("click")
|
||||||
on:mouseleave={() => (showTooltip = false)}
|
}
|
||||||
>
|
}}
|
||||||
{#if icon}
|
>
|
||||||
<svg
|
{#if icon}
|
||||||
class="spectrum-Icon spectrum-Icon--size{size.toUpperCase()}"
|
|
||||||
focusable="false"
|
|
||||||
aria-hidden="true"
|
|
||||||
aria-label={icon}
|
|
||||||
>
|
|
||||||
<use xlink:href="#spectrum-icon-18-{icon}" />
|
|
||||||
</svg>
|
|
||||||
{/if}
|
|
||||||
{#if $$slots}
|
|
||||||
<span class="spectrum-Button-label"><slot /></span>
|
|
||||||
{/if}
|
|
||||||
{#if !disabled && tooltip}
|
|
||||||
<div class="tooltip-icon">
|
|
||||||
<svg
|
<svg
|
||||||
class="spectrum-Icon spectrum-Icon--size{size.toUpperCase()}"
|
class="spectrum-Icon spectrum-Icon--size{size.toUpperCase()}"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
aria-label="Info"
|
aria-label={icon}
|
||||||
>
|
>
|
||||||
<use xlink:href="#spectrum-icon-18-InfoOutline" />
|
<use xlink:href="#spectrum-icon-18-{icon}" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
{/if}
|
||||||
{/if}
|
{#if $$slots}
|
||||||
{#if showTooltip && tooltip}
|
<span class="spectrum-Button-label"><slot /></span>
|
||||||
<div class="tooltip">
|
{/if}
|
||||||
<Tooltip textWrapping={true} direction={"bottom"} text={tooltip} />
|
</button>
|
||||||
</div>
|
</AbsTooltip>
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
button {
|
button {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
button.is-disabled {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
.spectrum-Button-label {
|
.spectrum-Button-label {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -84,21 +72,6 @@
|
||||||
.active {
|
.active {
|
||||||
color: var(--spectrum-global-color-blue-600) !important;
|
color: var(--spectrum-global-color-blue-600) !important;
|
||||||
}
|
}
|
||||||
.tooltip {
|
|
||||||
position: absolute;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 100;
|
|
||||||
width: 160px;
|
|
||||||
text-align: center;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
left: 50%;
|
|
||||||
top: calc(100% - 3px);
|
|
||||||
}
|
|
||||||
.tooltip-icon {
|
|
||||||
padding-left: var(--spacing-m);
|
|
||||||
line-height: 0;
|
|
||||||
}
|
|
||||||
.spectrum-Button--primary.new-styles {
|
.spectrum-Button--primary.new-styles {
|
||||||
background: var(--spectrum-global-color-gray-800);
|
background: var(--spectrum-global-color-gray-800);
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
|
@ -112,10 +85,10 @@
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
color: var(--spectrum-global-color-gray-900);
|
color: var(--spectrum-global-color-gray-900);
|
||||||
}
|
}
|
||||||
.spectrum-Button--secondary.new-styles:not(.disabled):hover {
|
.spectrum-Button--secondary.new-styles:not(.is-disabled):hover {
|
||||||
background: var(--spectrum-global-color-gray-300);
|
background: var(--spectrum-global-color-gray-300);
|
||||||
}
|
}
|
||||||
.spectrum-Button--secondary.new-styles.disabled {
|
.spectrum-Button--secondary.new-styles.is-disabled {
|
||||||
color: var(--spectrum-global-color-gray-500);
|
color: var(--spectrum-global-color-gray-500);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -15,8 +15,6 @@
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
$: placeholder = !value
|
|
||||||
|
|
||||||
const extractProperty = (value, property) => {
|
const extractProperty = (value, property) => {
|
||||||
if (value && typeof value === "object") {
|
if (value && typeof value === "object") {
|
||||||
return value[property]
|
return value[property]
|
||||||
|
|
|
@ -150,7 +150,7 @@
|
||||||
</div>
|
</div>
|
||||||
{:else if variables.length}
|
{:else if variables.length}
|
||||||
<div style="max-height: 100px">
|
<div style="max-height: 100px">
|
||||||
{#each variables as variable, idx}
|
{#each variables as variable}
|
||||||
<li
|
<li
|
||||||
class="spectrum-Menu-item"
|
class="spectrum-Menu-item"
|
||||||
role="option"
|
role="option"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import "@spectrum-css/link/dist/index-vars.css"
|
import "@spectrum-css/link/dist/index-vars.css"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import Tooltip from "../Tooltip/Tooltip.svelte"
|
||||||
|
|
||||||
export let href = "#"
|
export let href = "#"
|
||||||
export let size = "M"
|
export let size = "M"
|
||||||
|
@ -10,18 +11,61 @@
|
||||||
export let overBackground = false
|
export let overBackground = false
|
||||||
export let target
|
export let target
|
||||||
export let download
|
export let download
|
||||||
|
export let disabled = false
|
||||||
|
export let tooltip = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
const onClick = e => {
|
||||||
|
if (!disabled) {
|
||||||
|
dispatch("click")
|
||||||
|
e.stopPropagation()
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
on:click={e => dispatch("click") && e.stopPropagation()}
|
on:click={onClick}
|
||||||
{href}
|
{href}
|
||||||
{target}
|
{target}
|
||||||
{download}
|
{download}
|
||||||
|
class:disabled
|
||||||
class:spectrum-Link--primary={primary}
|
class:spectrum-Link--primary={primary}
|
||||||
class:spectrum-Link--secondary={secondary}
|
class:spectrum-Link--secondary={secondary}
|
||||||
class:spectrum-Link--overBackground={overBackground}
|
class:spectrum-Link--overBackground={overBackground}
|
||||||
class:spectrum-Link--quiet={quiet}
|
class:spectrum-Link--quiet={quiet}
|
||||||
class="spectrum-Link spectrum-Link--size{size}"><slot /></a
|
class="spectrum-Link spectrum-Link--size{size}"
|
||||||
>
|
>
|
||||||
|
<slot />
|
||||||
|
{#if tooltip}
|
||||||
|
<div class="tooltip">
|
||||||
|
<Tooltip textWrapping direction="bottom" text={tooltip} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
a {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
a.disabled {
|
||||||
|
color: var(--spectrum-global-color-gray-500);
|
||||||
|
}
|
||||||
|
a.disabled:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.tooltip {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 100%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
opacity: 0;
|
||||||
|
transition: 130ms ease-out;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
a:hover .tooltip {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
<script context="module">
|
||||||
|
export const keepOpen = Symbol("keepOpen")
|
||||||
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import "@spectrum-css/dialog/dist/index-vars.css"
|
import "@spectrum-css/dialog/dist/index-vars.css"
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
@ -30,7 +34,7 @@
|
||||||
|
|
||||||
async function secondary(e) {
|
async function secondary(e) {
|
||||||
loading = true
|
loading = true
|
||||||
if (!secondaryAction || (await secondaryAction(e)) !== false) {
|
if (!secondaryAction || (await secondaryAction(e)) !== keepOpen) {
|
||||||
hide()
|
hide()
|
||||||
}
|
}
|
||||||
loading = false
|
loading = false
|
||||||
|
@ -38,7 +42,7 @@
|
||||||
|
|
||||||
async function confirm() {
|
async function confirm() {
|
||||||
loading = true
|
loading = true
|
||||||
if (!onConfirm || (await onConfirm()) !== false) {
|
if (!onConfirm || (await onConfirm()) !== keepOpen) {
|
||||||
hide()
|
hide()
|
||||||
}
|
}
|
||||||
loading = false
|
loading = false
|
||||||
|
@ -46,7 +50,7 @@
|
||||||
|
|
||||||
async function close() {
|
async function close() {
|
||||||
loading = true
|
loading = true
|
||||||
if (!onCancel || (await onCancel()) !== false) {
|
if (!onCancel || (await onCancel()) !== keepOpen) {
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
loading = false
|
loading = false
|
||||||
|
|
|
@ -29,7 +29,6 @@
|
||||||
$: type = getType(schema)
|
$: type = getType(schema)
|
||||||
$: customRenderer = customRenderers?.find(x => x.column === schema?.name)
|
$: customRenderer = customRenderers?.find(x => x.column === schema?.name)
|
||||||
$: renderer = customRenderer?.component ?? typeMap[type] ?? StringRenderer
|
$: renderer = customRenderer?.component ?? typeMap[type] ?? StringRenderer
|
||||||
$: width = schema?.width || "150px"
|
|
||||||
$: cellValue = getCellValue(value, schema.template)
|
$: cellValue = getCellValue(value, schema.template)
|
||||||
|
|
||||||
const getType = schema => {
|
const getType = schema => {
|
||||||
|
|
|
@ -379,7 +379,7 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if sortedRows?.length}
|
{#if sortedRows?.length}
|
||||||
{#each sortedRows as row, idx}
|
{#each sortedRows as row}
|
||||||
<div class="spectrum-Table-row" class:clickable={allowClickRows}>
|
<div class="spectrum-Table-row" class:clickable={allowClickRows}>
|
||||||
{#if showEditColumn}
|
{#if showEditColumn}
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
<script context="module">
|
||||||
|
export const TooltipPosition = {
|
||||||
|
Top: "top",
|
||||||
|
Right: "right",
|
||||||
|
Bottom: "bottom",
|
||||||
|
Left: "left",
|
||||||
|
}
|
||||||
|
export const TooltipType = {
|
||||||
|
Default: "default",
|
||||||
|
Info: "info",
|
||||||
|
Positive: "positive",
|
||||||
|
Negative: "negative",
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Portal from "svelte-portal"
|
||||||
|
import { fade } from "svelte/transition"
|
||||||
|
import "@spectrum-css/tooltip/dist/index-vars.css"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
|
export let position = TooltipPosition.Top
|
||||||
|
export let type = TooltipType.Default
|
||||||
|
export let text = ""
|
||||||
|
export let fixed = false
|
||||||
|
export let color = null
|
||||||
|
|
||||||
|
let wrapper
|
||||||
|
let hovered = false
|
||||||
|
let left
|
||||||
|
let top
|
||||||
|
let visible = false
|
||||||
|
let timeout
|
||||||
|
let interval
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (hovered || fixed) {
|
||||||
|
// Debounce showing by 200ms to avoid flashing tooltip
|
||||||
|
timeout = setTimeout(show, 200)
|
||||||
|
} else {
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$: tooltipStyle = color ? `background:${color};` : null
|
||||||
|
$: tipStyle = color ? `border-top-color:${color};` : null
|
||||||
|
|
||||||
|
// Computes the position of the tooltip
|
||||||
|
const updateTooltipPosition = () => {
|
||||||
|
const node = wrapper?.children?.[0]
|
||||||
|
if (!node) {
|
||||||
|
left = null
|
||||||
|
top = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const bounds = node.getBoundingClientRect()
|
||||||
|
|
||||||
|
// Determine where to render tooltip based on position prop
|
||||||
|
if (position === TooltipPosition.Top) {
|
||||||
|
left = bounds.left + bounds.width / 2
|
||||||
|
top = bounds.top
|
||||||
|
} else if (position === TooltipPosition.Right) {
|
||||||
|
left = bounds.left + bounds.width
|
||||||
|
top = bounds.top + bounds.height / 2
|
||||||
|
} else if (position === TooltipPosition.Bottom) {
|
||||||
|
left = bounds.left + bounds.width / 2
|
||||||
|
top = bounds.top + bounds.height
|
||||||
|
} else if (position === TooltipPosition.Left) {
|
||||||
|
left = bounds.left
|
||||||
|
top = bounds.top + bounds.height / 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computes the position of the tooltip then shows it.
|
||||||
|
// We set up a poll to frequently update the position of the tooltip in case
|
||||||
|
// the target moves.
|
||||||
|
const show = () => {
|
||||||
|
updateTooltipPosition()
|
||||||
|
interval = setInterval(updateTooltipPosition, 100)
|
||||||
|
visible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hides the tooltip
|
||||||
|
const hide = () => {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
clearInterval(interval)
|
||||||
|
visible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we clean up interval and timeout
|
||||||
|
onDestroy(hide)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={wrapper}
|
||||||
|
class="abs-tooltip"
|
||||||
|
on:focus={null}
|
||||||
|
on:mouseover={() => (hovered = true)}
|
||||||
|
on:mouseleave={() => (hovered = false)}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if visible && text && left != null && top != null}
|
||||||
|
<Portal target=".spectrum">
|
||||||
|
<span
|
||||||
|
class="spectrum-Tooltip spectrum-Tooltip--{type} spectrum-Tooltip--{position} is-open"
|
||||||
|
style={`left:${left}px;top:${top}px;${tooltipStyle}`}
|
||||||
|
transition:fade|local={{ duration: 130 }}
|
||||||
|
>
|
||||||
|
<span class="spectrum-Tooltip-label">{text}</span>
|
||||||
|
<span class="spectrum-Tooltip-tip" style={tipStyle} />
|
||||||
|
</span>
|
||||||
|
</Portal>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.abs-tooltip {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
.spectrum-Tooltip {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 9999;
|
||||||
|
pointer-events: none;
|
||||||
|
margin: 0;
|
||||||
|
max-width: 280px;
|
||||||
|
transition: top 130ms ease-out, left 130ms ease-out;
|
||||||
|
}
|
||||||
|
.spectrum-Tooltip-label {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Colour overrides for default type */
|
||||||
|
.spectrum-Tooltip--default {
|
||||||
|
background: var(--spectrum-global-color-gray-500);
|
||||||
|
}
|
||||||
|
.spectrum-Tooltip--default .spectrum-Tooltip-tip {
|
||||||
|
border-top-color: var(--spectrum-global-color-gray-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Position styles */
|
||||||
|
.spectrum-Tooltip--top {
|
||||||
|
transform: translateX(-50%) translateY(calc(-100% - 8px));
|
||||||
|
}
|
||||||
|
.spectrum-Tooltip--right {
|
||||||
|
transform: translateX(8px) translateY(-50%);
|
||||||
|
}
|
||||||
|
.spectrum-Tooltip--bottom {
|
||||||
|
transform: translateX(-50%) translateY(8px);
|
||||||
|
}
|
||||||
|
.spectrum-Tooltip--left {
|
||||||
|
transform: translateX(calc(-100% - 8px)) translateY(-50%);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,39 @@
|
||||||
|
<script>
|
||||||
|
import AbsTooltip from "./AbsTooltip.svelte"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
|
export let text = null
|
||||||
|
export let condition = true
|
||||||
|
export let duration = 3000
|
||||||
|
export let position
|
||||||
|
export let type
|
||||||
|
|
||||||
|
let visible = false
|
||||||
|
let timeout
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (condition) {
|
||||||
|
showTooltip()
|
||||||
|
} else {
|
||||||
|
hideTooltip()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const showTooltip = () => {
|
||||||
|
visible = true
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
visible = false
|
||||||
|
}, duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
const hideTooltip = () => {
|
||||||
|
visible = false
|
||||||
|
clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
onDestroy(hideTooltip)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AbsTooltip {position} {type} text={visible ? text : null} fixed={visible}>
|
||||||
|
<slot />
|
||||||
|
</AbsTooltip>
|
|
@ -36,13 +36,19 @@ export { default as Layout } from "./Layout/Layout.svelte"
|
||||||
export { default as Page } from "./Layout/Page.svelte"
|
export { default as Page } from "./Layout/Page.svelte"
|
||||||
export { default as Link } from "./Link/Link.svelte"
|
export { default as Link } from "./Link/Link.svelte"
|
||||||
export { default as Tooltip } from "./Tooltip/Tooltip.svelte"
|
export { default as Tooltip } from "./Tooltip/Tooltip.svelte"
|
||||||
|
export { default as TempTooltip } from "./Tooltip/TempTooltip.svelte"
|
||||||
|
export {
|
||||||
|
default as AbsTooltip,
|
||||||
|
TooltipPosition,
|
||||||
|
TooltipType,
|
||||||
|
} from "./Tooltip/AbsTooltip.svelte"
|
||||||
export { default as TooltipWrapper } from "./Tooltip/TooltipWrapper.svelte"
|
export { default as TooltipWrapper } from "./Tooltip/TooltipWrapper.svelte"
|
||||||
export { default as Menu } from "./Menu/Menu.svelte"
|
export { default as Menu } from "./Menu/Menu.svelte"
|
||||||
export { default as MenuSection } from "./Menu/Section.svelte"
|
export { default as MenuSection } from "./Menu/Section.svelte"
|
||||||
export { default as MenuSeparator } from "./Menu/Separator.svelte"
|
export { default as MenuSeparator } from "./Menu/Separator.svelte"
|
||||||
export { default as MenuItem } from "./Menu/Item.svelte"
|
export { default as MenuItem } from "./Menu/Item.svelte"
|
||||||
export { default as Modal } from "./Modal/Modal.svelte"
|
export { default as Modal } from "./Modal/Modal.svelte"
|
||||||
export { default as ModalContent } from "./Modal/ModalContent.svelte"
|
export { default as ModalContent, keepOpen } from "./Modal/ModalContent.svelte"
|
||||||
export { default as NotificationDisplay } from "./Notification/NotificationDisplay.svelte"
|
export { default as NotificationDisplay } from "./Notification/NotificationDisplay.svelte"
|
||||||
export { default as Notification } from "./Notification/Notification.svelte"
|
export { default as Notification } from "./Notification/Notification.svelte"
|
||||||
export { default as SideNavigation } from "./SideNavigation/Navigation.svelte"
|
export { default as SideNavigation } from "./SideNavigation/Navigation.svelte"
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { getAutomationStore } from "./store/automation"
|
||||||
import { getTemporalStore } from "./store/temporal"
|
import { getTemporalStore } from "./store/temporal"
|
||||||
import { getThemeStore } from "./store/theme"
|
import { getThemeStore } from "./store/theme"
|
||||||
import { getUserStore } from "./store/users"
|
import { getUserStore } from "./store/users"
|
||||||
|
import { getDeploymentStore } from "./store/deployments"
|
||||||
import { derived } from "svelte/store"
|
import { derived } from "svelte/store"
|
||||||
import { findComponent, findComponentPath } from "./componentUtils"
|
import { findComponent, findComponentPath } from "./componentUtils"
|
||||||
import { RoleUtils } from "@budibase/frontend-core"
|
import { RoleUtils } from "@budibase/frontend-core"
|
||||||
|
@ -14,6 +15,7 @@ export const automationStore = getAutomationStore()
|
||||||
export const themeStore = getThemeStore()
|
export const themeStore = getThemeStore()
|
||||||
export const temporalStore = getTemporalStore()
|
export const temporalStore = getTemporalStore()
|
||||||
export const userStore = getUserStore()
|
export const userStore = getUserStore()
|
||||||
|
export const deploymentStore = getDeploymentStore()
|
||||||
|
|
||||||
// Setup history for screens
|
// Setup history for screens
|
||||||
export const screenHistoryStore = createHistoryStore({
|
export const screenHistoryStore = createHistoryStore({
|
||||||
|
@ -118,3 +120,24 @@ export const selectedAutomation = derived(automationStore, $automationStore => {
|
||||||
x => x._id === $automationStore.selectedAutomationId
|
x => x._id === $automationStore.selectedAutomationId
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Derive map of resource IDs to other users.
|
||||||
|
// We only ever care about a single user in each resource, so if multiple users
|
||||||
|
// share the same datasource we can just overwrite them.
|
||||||
|
export const userSelectedResourceMap = derived(userStore, $userStore => {
|
||||||
|
let map = {}
|
||||||
|
$userStore.forEach(user => {
|
||||||
|
const resource = user.builderMetadata?.selectedResourceId
|
||||||
|
if (resource) {
|
||||||
|
if (!map[resource]) {
|
||||||
|
map[resource] = []
|
||||||
|
}
|
||||||
|
map[resource].push(user)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return map
|
||||||
|
})
|
||||||
|
|
||||||
|
export const isOnlyUser = derived(userStore, $userStore => {
|
||||||
|
return $userStore.length < 2
|
||||||
|
})
|
||||||
|
|
|
@ -248,4 +248,36 @@ const automationActions = store => ({
|
||||||
}
|
}
|
||||||
await store.actions.save(newAutomation)
|
await store.actions.save(newAutomation)
|
||||||
},
|
},
|
||||||
|
replace: async (automationId, automation) => {
|
||||||
|
if (!automation) {
|
||||||
|
store.update(state => {
|
||||||
|
// Remove the automation
|
||||||
|
state.automations = state.automations.filter(
|
||||||
|
x => x._id !== automationId
|
||||||
|
)
|
||||||
|
// Select a new automation if required
|
||||||
|
if (automationId === state.selectedAutomationId) {
|
||||||
|
store.actions.select(state.automations[0]?._id)
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const index = get(store).automations.findIndex(
|
||||||
|
x => x._id === automation._id
|
||||||
|
)
|
||||||
|
if (index === -1) {
|
||||||
|
// Automation addition
|
||||||
|
store.update(state => ({
|
||||||
|
...state,
|
||||||
|
automations: [...state.automations, automation],
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
// Automation update
|
||||||
|
store.update(state => {
|
||||||
|
state.automations[index] = automation
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { writable } from "svelte/store"
|
||||||
|
import { API } from "api"
|
||||||
|
import { notifications } from "@budibase/bbui"
|
||||||
|
|
||||||
|
export const getDeploymentStore = () => {
|
||||||
|
let store = writable([])
|
||||||
|
|
||||||
|
const load = async () => {
|
||||||
|
try {
|
||||||
|
store.set(await API.getAppDeployments())
|
||||||
|
} catch (err) {
|
||||||
|
notifications.error("Error fetching deployments")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe: store.subscribe,
|
||||||
|
actions: {
|
||||||
|
load,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,6 +38,7 @@ import {
|
||||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
import { getComponentFieldOptions } from "helpers/formFields"
|
import { getComponentFieldOptions } from "helpers/formFields"
|
||||||
import { createBuilderWebsocket } from "builderStore/websocket"
|
import { createBuilderWebsocket } from "builderStore/websocket"
|
||||||
|
import { BuilderSocketEvent } from "@budibase/shared-core"
|
||||||
|
|
||||||
const INITIAL_FRONTEND_STATE = {
|
const INITIAL_FRONTEND_STATE = {
|
||||||
initialised: false,
|
initialised: false,
|
||||||
|
@ -353,6 +354,33 @@ export const getFrontendStore = () => {
|
||||||
}
|
}
|
||||||
return await sequentialScreenPatch(patchFn, screenId)
|
return await sequentialScreenPatch(patchFn, screenId)
|
||||||
},
|
},
|
||||||
|
replace: async (screenId, screen) => {
|
||||||
|
if (!screenId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!screen) {
|
||||||
|
// Screen deletion
|
||||||
|
store.update(state => ({
|
||||||
|
...state,
|
||||||
|
screens: state.screens.filter(x => x._id !== screenId),
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
const index = get(store).screens.findIndex(x => x._id === screen._id)
|
||||||
|
if (index === -1) {
|
||||||
|
// Screen addition
|
||||||
|
store.update(state => ({
|
||||||
|
...state,
|
||||||
|
screens: [...state.screens, screen],
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
// Screen update
|
||||||
|
store.update(state => {
|
||||||
|
state.screens[index] = screen
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
delete: async screens => {
|
delete: async screens => {
|
||||||
const screensToDelete = Array.isArray(screens) ? screens : [screens]
|
const screensToDelete = Array.isArray(screens) ? screens : [screens]
|
||||||
|
|
||||||
|
@ -1305,7 +1333,7 @@ export const getFrontendStore = () => {
|
||||||
links: {
|
links: {
|
||||||
save: async (url, title) => {
|
save: async (url, title) => {
|
||||||
const navigation = get(store).navigation
|
const navigation = get(store).navigation
|
||||||
let links = [...navigation?.links]
|
let links = [...(navigation?.links ?? [])]
|
||||||
|
|
||||||
// Skip if we have an identical link
|
// Skip if we have an identical link
|
||||||
if (links.find(link => link.url === url && link.text === title)) {
|
if (links.find(link => link.url === url && link.text === title)) {
|
||||||
|
@ -1365,6 +1393,21 @@ export const getFrontendStore = () => {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
websocket: {
|
||||||
|
selectResource: id => {
|
||||||
|
websocket.emit(BuilderSocketEvent.SelectResource, {
|
||||||
|
resourceId: id,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
replace: metadata => {
|
||||||
|
store.update(state => ({
|
||||||
|
...state,
|
||||||
|
...metadata,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return store
|
return store
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
import { createWebsocket } from "@budibase/frontend-core"
|
import { createWebsocket } from "@budibase/frontend-core"
|
||||||
import { userStore, store } from "builderStore"
|
import {
|
||||||
|
userStore,
|
||||||
|
store,
|
||||||
|
deploymentStore,
|
||||||
|
automationStore,
|
||||||
|
} from "builderStore"
|
||||||
import { datasources, tables } from "stores/backend"
|
import { datasources, tables } from "stores/backend"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { auth } from "stores/portal"
|
import { auth } from "stores/portal"
|
||||||
import { SocketEvent, BuilderSocketEvent } from "@budibase/shared-core"
|
import { SocketEvent, BuilderSocketEvent } from "@budibase/shared-core"
|
||||||
|
import { apps } from "stores/portal"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
|
import { helpers } from "@budibase/shared-core"
|
||||||
|
|
||||||
export const createBuilderWebsocket = appId => {
|
export const createBuilderWebsocket = appId => {
|
||||||
const socket = createWebsocket("/socket/builder")
|
const socket = createWebsocket("/socket/builder")
|
||||||
|
@ -31,7 +38,6 @@ export const createBuilderWebsocket = appId => {
|
||||||
})
|
})
|
||||||
socket.onOther(BuilderSocketEvent.LockTransfer, ({ userId }) => {
|
socket.onOther(BuilderSocketEvent.LockTransfer, ({ userId }) => {
|
||||||
if (userId === get(auth)?.user?._id) {
|
if (userId === get(auth)?.user?._id) {
|
||||||
notifications.success("You can now edit screens and automations")
|
|
||||||
store.update(state => ({
|
store.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
hasLock: true,
|
hasLock: true,
|
||||||
|
@ -39,15 +45,37 @@ export const createBuilderWebsocket = appId => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Table events
|
// Data section events
|
||||||
socket.onOther(BuilderSocketEvent.TableChange, ({ id, table }) => {
|
socket.onOther(BuilderSocketEvent.TableChange, ({ id, table }) => {
|
||||||
tables.replaceTable(id, table)
|
tables.replaceTable(id, table)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Datasource events
|
|
||||||
socket.onOther(BuilderSocketEvent.DatasourceChange, ({ id, datasource }) => {
|
socket.onOther(BuilderSocketEvent.DatasourceChange, ({ id, datasource }) => {
|
||||||
datasources.replaceDatasource(id, datasource)
|
datasources.replaceDatasource(id, datasource)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Design section events
|
||||||
|
socket.onOther(BuilderSocketEvent.ScreenChange, ({ id, screen }) => {
|
||||||
|
store.actions.screens.replace(id, screen)
|
||||||
|
})
|
||||||
|
socket.onOther(BuilderSocketEvent.AppMetadataChange, ({ metadata }) => {
|
||||||
|
store.actions.metadata.replace(metadata)
|
||||||
|
})
|
||||||
|
socket.onOther(
|
||||||
|
BuilderSocketEvent.AppPublishChange,
|
||||||
|
async ({ user, published }) => {
|
||||||
|
await apps.load()
|
||||||
|
if (published) {
|
||||||
|
await deploymentStore.actions.load()
|
||||||
|
}
|
||||||
|
const verb = published ? "published" : "unpublished"
|
||||||
|
notifications.success(`${helpers.getUserLabel(user)} ${verb} this app`)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Automations
|
||||||
|
socket.onOther(BuilderSocketEvent.AutomationChange, ({ id, automation }) => {
|
||||||
|
automationStore.actions.replace(id, automation)
|
||||||
|
})
|
||||||
|
|
||||||
return socket
|
return socket
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,7 +168,7 @@
|
||||||
<Layout noPadding gap="XS">
|
<Layout noPadding gap="XS">
|
||||||
<Detail size="S">Plugins</Detail>
|
<Detail size="S">Plugins</Detail>
|
||||||
<div class="item-list">
|
<div class="item-list">
|
||||||
{#each Object.entries(plugins) as [idx, action]}
|
{#each Object.entries(plugins) as [_, action]}
|
||||||
<div
|
<div
|
||||||
class="item"
|
class="item"
|
||||||
class:selected={selectedAction === action.name}
|
class:selected={selectedAction === action.name}
|
||||||
|
|
|
@ -60,6 +60,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags-->
|
||||||
{@html html}
|
{@html html}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { automationStore, selectedAutomation } from "builderStore"
|
import {
|
||||||
|
automationStore,
|
||||||
|
selectedAutomation,
|
||||||
|
userSelectedResourceMap,
|
||||||
|
} from "builderStore"
|
||||||
import NavItem from "components/common/NavItem.svelte"
|
import NavItem from "components/common/NavItem.svelte"
|
||||||
import EditAutomationPopover from "./EditAutomationPopover.svelte"
|
import EditAutomationPopover from "./EditAutomationPopover.svelte"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
|
@ -21,13 +25,13 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="automations-list">
|
<div class="automations-list">
|
||||||
{#each $automationStore.automations.sort(aut => aut.name) as automation, idx}
|
{#each $automationStore.automations.sort(aut => aut.name) as automation}
|
||||||
<NavItem
|
<NavItem
|
||||||
border={idx > 0}
|
|
||||||
icon="ShareAndroid"
|
icon="ShareAndroid"
|
||||||
text={automation.name}
|
text={automation.name}
|
||||||
selected={automation._id === selectedAutomationId}
|
selected={automation._id === selectedAutomationId}
|
||||||
on:click={() => selectAutomation(automation._id)}
|
on:click={() => selectAutomation(automation._id)}
|
||||||
|
selectedBy={$userSelectedResourceMap[automation._id]}
|
||||||
>
|
>
|
||||||
<EditAutomationPopover {automation} />
|
<EditAutomationPopover {automation} />
|
||||||
</NavItem>
|
</NavItem>
|
||||||
|
@ -40,6 +44,5 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
margin: 0 calc(-1 * var(--spacing-xl));
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -11,8 +11,8 @@
|
||||||
<Panel title="Automations" borderRight>
|
<Panel title="Automations" borderRight>
|
||||||
<Layout paddingX="L" paddingY="XL" gap="S">
|
<Layout paddingX="L" paddingY="XL" gap="S">
|
||||||
<Button cta on:click={modal.show}>Add automation</Button>
|
<Button cta on:click={modal.show}>Add automation</Button>
|
||||||
<AutomationList />
|
|
||||||
</Layout>
|
</Layout>
|
||||||
|
<AutomationList />
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
|
|
|
@ -71,7 +71,7 @@
|
||||||
<Layout noPadding gap="XS">
|
<Layout noPadding gap="XS">
|
||||||
<Label size="S">Trigger</Label>
|
<Label size="S">Trigger</Label>
|
||||||
<div class="item-list">
|
<div class="item-list">
|
||||||
{#each triggers as [idx, trigger]}
|
{#each triggers as [_, trigger]}
|
||||||
<div
|
<div
|
||||||
class="item"
|
class="item"
|
||||||
class:selected={selectedTrigger === trigger.name}
|
class:selected={selectedTrigger === trigger.name}
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
import { goto, params } from "@roxi/routify"
|
import { goto, params } from "@roxi/routify"
|
||||||
import { Table, Heading, Layout } from "@budibase/bbui"
|
import { Table, Heading, Layout } from "@budibase/bbui"
|
||||||
import Spinner from "components/common/Spinner.svelte"
|
import Spinner from "components/common/Spinner.svelte"
|
||||||
import CreateEditRow from "./modals/CreateEditRow.svelte"
|
|
||||||
import CreateEditUser from "./modals/CreateEditUser.svelte"
|
|
||||||
import {
|
import {
|
||||||
TableNames,
|
TableNames,
|
||||||
UNEDITABLE_USER_FIELDS,
|
UNEDITABLE_USER_FIELDS,
|
||||||
|
@ -33,7 +31,6 @@
|
||||||
$: selectedRows, dispatch("selectionUpdated", selectedRows)
|
$: selectedRows, dispatch("selectionUpdated", selectedRows)
|
||||||
$: isUsersTable = tableId === TableNames.USERS
|
$: isUsersTable = tableId === TableNames.USERS
|
||||||
$: data && resetSelectedRows()
|
$: data && resetSelectedRows()
|
||||||
$: editRowComponent = isUsersTable ? CreateEditUser : CreateEditRow
|
|
||||||
$: {
|
$: {
|
||||||
UNSORTABLE_TYPES.forEach(type => {
|
UNSORTABLE_TYPES.forEach(type => {
|
||||||
Object.values(schema || {}).forEach(col => {
|
Object.values(schema || {}).forEach(col => {
|
||||||
|
|
|
@ -57,7 +57,6 @@
|
||||||
|
|
||||||
let table = $tables.selected
|
let table = $tables.selected
|
||||||
let confirmDeleteDialog
|
let confirmDeleteDialog
|
||||||
let deletion
|
|
||||||
let savingColumn
|
let savingColumn
|
||||||
let deleteColName
|
let deleteColName
|
||||||
let jsonSchemaModal
|
let jsonSchemaModal
|
||||||
|
@ -215,7 +214,6 @@
|
||||||
notifications.success(`Column ${editableColumn.name} deleted`)
|
notifications.success(`Column ${editableColumn.name} deleted`)
|
||||||
confirmDeleteDialog.hide()
|
confirmDeleteDialog.hide()
|
||||||
hide()
|
hide()
|
||||||
deletion = false
|
|
||||||
dispatch("updatecolumns")
|
dispatch("updatecolumns")
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -266,13 +264,11 @@
|
||||||
|
|
||||||
function confirmDelete() {
|
function confirmDelete() {
|
||||||
confirmDeleteDialog.show()
|
confirmDeleteDialog.show()
|
||||||
deletion = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideDeleteDialog() {
|
function hideDeleteDialog() {
|
||||||
confirmDeleteDialog.hide()
|
confirmDeleteDialog.hide()
|
||||||
deleteColName = ""
|
deleteColName = ""
|
||||||
deletion = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRelationshipOptions(field) {
|
function getRelationshipOptions(field) {
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { tables } from "stores/backend"
|
import { tables } from "stores/backend"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { ModalContent, keepOpen, notifications } from "@budibase/bbui"
|
||||||
import RowFieldControl from "../RowFieldControl.svelte"
|
import RowFieldControl from "../RowFieldControl.svelte"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { ModalContent } from "@budibase/bbui"
|
|
||||||
import { FIELDS } from "constants/backend"
|
import { FIELDS } from "constants/backend"
|
||||||
|
|
||||||
const FORMULA_TYPE = FIELDS.FORMULA.type
|
const FORMULA_TYPE = FIELDS.FORMULA.type
|
||||||
|
@ -41,8 +40,8 @@
|
||||||
} else {
|
} else {
|
||||||
notifications.error(`Failed to save row - ${error.message}`)
|
notifications.error(`Failed to save row - ${error.message}`)
|
||||||
}
|
}
|
||||||
// Prevent modal closing if there were errors
|
|
||||||
return false
|
return keepOpen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
import RowFieldControl from "../RowFieldControl.svelte"
|
import RowFieldControl from "../RowFieldControl.svelte"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { ModalContent, Select, Link } from "@budibase/bbui"
|
import { keepOpen, ModalContent, Select, Link } from "@budibase/bbui"
|
||||||
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
errors = [...errors, { message: "Role is required" }]
|
errors = [...errors, { message: "Role is required" }]
|
||||||
}
|
}
|
||||||
if (errors.length) {
|
if (errors.length) {
|
||||||
return false
|
return keepOpen
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -79,8 +79,8 @@
|
||||||
} else {
|
} else {
|
||||||
notifications.error("Error saving user")
|
notifications.error("Error saving user")
|
||||||
}
|
}
|
||||||
// Prevent closing the modal on errors
|
|
||||||
return false
|
return keepOpen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { ModalContent, Select, Input, Button } from "@budibase/bbui"
|
import { keepOpen, ModalContent, Select, Input, Button } from "@budibase/bbui"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
|
@ -76,7 +76,7 @@
|
||||||
errors.push({ message: "Please choose permissions" })
|
errors.push({ message: "Please choose permissions" })
|
||||||
}
|
}
|
||||||
if (errors.length) {
|
if (errors.length) {
|
||||||
return false
|
return keepOpen
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save/create the role
|
// Save/create the role
|
||||||
|
@ -85,7 +85,7 @@
|
||||||
notifications.success("Role saved successfully")
|
notifications.success("Role saved successfully")
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error(`Error saving role - ${error.message}`)
|
notifications.error(`Error saving role - ${error.message}`)
|
||||||
return false
|
return keepOpen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
} from "helpers/data/utils"
|
} from "helpers/data/utils"
|
||||||
import IntegrationIcon from "./IntegrationIcon.svelte"
|
import IntegrationIcon from "./IntegrationIcon.svelte"
|
||||||
import { TableNames } from "constants"
|
import { TableNames } from "constants"
|
||||||
|
import { userSelectedResourceMap } from "builderStore"
|
||||||
|
|
||||||
let openDataSources = []
|
let openDataSources = []
|
||||||
|
|
||||||
|
@ -166,8 +167,9 @@
|
||||||
selected={$isActive("./table/:tableId") &&
|
selected={$isActive("./table/:tableId") &&
|
||||||
$tables.selected?._id === TableNames.USERS}
|
$tables.selected?._id === TableNames.USERS}
|
||||||
on:click={() => selectTable(TableNames.USERS)}
|
on:click={() => selectTable(TableNames.USERS)}
|
||||||
|
selectedBy={$userSelectedResourceMap[TableNames.USERS]}
|
||||||
/>
|
/>
|
||||||
{#each enrichedDataSources as datasource, idx}
|
{#each enrichedDataSources as datasource}
|
||||||
<NavItem
|
<NavItem
|
||||||
border
|
border
|
||||||
text={datasource.name}
|
text={datasource.name}
|
||||||
|
@ -176,6 +178,7 @@
|
||||||
withArrow={true}
|
withArrow={true}
|
||||||
on:click={() => selectDatasource(datasource)}
|
on:click={() => selectDatasource(datasource)}
|
||||||
on:iconClick={() => toggleNode(datasource)}
|
on:iconClick={() => toggleNode(datasource)}
|
||||||
|
selectedBy={$userSelectedResourceMap[datasource._id]}
|
||||||
>
|
>
|
||||||
<div class="datasource-icon" slot="icon">
|
<div class="datasource-icon" slot="icon">
|
||||||
<IntegrationIcon
|
<IntegrationIcon
|
||||||
|
@ -201,6 +204,7 @@
|
||||||
selected={$isActive("./query/:queryId") &&
|
selected={$isActive("./query/:queryId") &&
|
||||||
$queries.selectedQueryId === query._id}
|
$queries.selectedQueryId === query._id}
|
||||||
on:click={() => $goto(`./query/${query._id}`)}
|
on:click={() => $goto(`./query/${query._id}`)}
|
||||||
|
selectedBy={$userSelectedResourceMap[query._id]}
|
||||||
>
|
>
|
||||||
<EditQueryPopover {query} />
|
<EditQueryPopover {query} />
|
||||||
</NavItem>
|
</NavItem>
|
||||||
|
@ -212,7 +216,7 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.hierarchy-items-container {
|
.hierarchy-items-container {
|
||||||
margin: 0 calc(-1 * var(--spacing-xl));
|
margin: 0 calc(-1 * var(--spacing-l));
|
||||||
}
|
}
|
||||||
.datasource-icon {
|
.datasource-icon {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
|
@ -31,65 +31,65 @@
|
||||||
<path
|
<path
|
||||||
class="st1"
|
class="st1"
|
||||||
d="M-83.19,48h-41.79c-1.76,0-3.19-1.43-3.19-3.19V3.02c0-1.76,1.43-3.19,3.19-3.19h41.79
|
d="M-83.19,48h-41.79c-1.76,0-3.19-1.43-3.19-3.19V3.02c0-1.76,1.43-3.19,3.19-3.19h41.79
|
||||||
c1.76,0,3.19,1.43,3.19,3.19v41.79C-80,46.57-81.43,48-83.19,48z"
|
c1.76,0,3.19,1.43,3.19,3.19v41.79C-80,46.57-81.43,48-83.19,48z"
|
||||||
/>
|
/>
|
||||||
<g>
|
<g>
|
||||||
<g>
|
<g>
|
||||||
<path
|
<path
|
||||||
class="st0"
|
class="st0"
|
||||||
d="M-99.62,12.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58c0.86,0.39,1.59,0.91,2.19,1.57
|
d="M-99.62,12.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58c0.86,0.39,1.59,0.91,2.19,1.57
|
||||||
c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89c-0.35,0.9-0.84,1.68-1.47,2.35
|
c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89c-0.35,0.9-0.84,1.68-1.47,2.35
|
||||||
c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V35h-4.89V12.57H-99.62z
|
c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V35h-4.89V12.57H-99.62z
|
||||||
M-93.46,28.11c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69
|
M-93.46,28.11c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69
|
||||||
c-0.38-0.17-0.79-0.26-1.24-0.26c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01
|
c-0.38-0.17-0.79-0.26-1.24-0.26c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01
|
||||||
c-0.17,0.39-0.26,0.8-0.26,1.23c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68
|
c-0.17,0.39-0.26,0.8-0.26,1.23c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68
|
||||||
c0.39,0.17,0.8,0.26,1.23,0.26c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1
|
c0.39,0.17,0.8,0.26,1.23,0.26c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1
|
||||||
C-93.55,28.92-93.46,28.52-93.46,28.11z"
|
C-93.55,28.92-93.46,28.52-93.46,28.11z"
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
<path
|
<path
|
||||||
class="st0"
|
class="st0"
|
||||||
d="M-114.76,12.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58
|
d="M-114.76,12.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58
|
||||||
c0.86,0.39,1.59,0.91,2.19,1.57c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89
|
c0.86,0.39,1.59,0.91,2.19,1.57c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89
|
||||||
c-0.35,0.9-0.84,1.68-1.47,2.35c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V35
|
c-0.35,0.9-0.84,1.68-1.47,2.35c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V35
|
||||||
h-4.89V12.57H-114.76z M-108.6,28.11c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69
|
h-4.89V12.57H-114.76z M-108.6,28.11c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69
|
||||||
c-0.38-0.17-0.79-0.26-1.24-0.26c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01
|
c-0.38-0.17-0.79-0.26-1.24-0.26c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01
|
||||||
c-0.17,0.39-0.26,0.8-0.26,1.23c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68
|
c-0.17,0.39-0.26,0.8-0.26,1.23c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68
|
||||||
c0.39,0.17,0.8,0.26,1.23,0.26c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1
|
c0.39,0.17,0.8,0.26,1.23,0.26c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1
|
||||||
C-108.68,28.92-108.6,28.52-108.6,28.11z"
|
C-108.68,28.92-108.6,28.52-108.6,28.11z"
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<path
|
<path
|
||||||
class="st2"
|
class="st2"
|
||||||
d="M44.81,159H3.02c-1.76,0-3.19-1.43-3.19-3.19v-41.79c0-1.76,1.43-3.19,3.19-3.19h41.79
|
d="M44.81,159H3.02c-1.76,0-3.19-1.43-3.19-3.19v-41.79c0-1.76,1.43-3.19,3.19-3.19h41.79
|
||||||
c1.76,0,3.19,1.43,3.19,3.19v41.79C48,157.57,46.57,159,44.81,159z"
|
c1.76,0,3.19,1.43,3.19,3.19v41.79C48,157.57,46.57,159,44.81,159z"
|
||||||
/>
|
/>
|
||||||
<g>
|
<g>
|
||||||
<g>
|
<g>
|
||||||
<path
|
<path
|
||||||
class="st1"
|
class="st1"
|
||||||
d="M28.38,123.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58c0.86,0.39,1.59,0.91,2.19,1.57
|
d="M28.38,123.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58c0.86,0.39,1.59,0.91,2.19,1.57
|
||||||
c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89c-0.35,0.9-0.84,1.68-1.47,2.35
|
c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89c-0.35,0.9-0.84,1.68-1.47,2.35
|
||||||
c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V146h-4.89v-22.43H28.38z
|
c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V146h-4.89v-22.43H28.38z
|
||||||
M34.54,139.11c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69
|
M34.54,139.11c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69
|
||||||
c-0.38-0.17-0.79-0.26-1.24-0.26c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01
|
c-0.38-0.17-0.79-0.26-1.24-0.26c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01
|
||||||
c-0.17,0.39-0.26,0.8-0.26,1.23c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68
|
c-0.17,0.39-0.26,0.8-0.26,1.23c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68
|
||||||
c0.39,0.17,0.8,0.26,1.23,0.26c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1
|
c0.39,0.17,0.8,0.26,1.23,0.26c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1
|
||||||
C34.45,139.92,34.54,139.52,34.54,139.11z"
|
C34.45,139.92,34.54,139.52,34.54,139.11z"
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
<path
|
<path
|
||||||
class="st1"
|
class="st1"
|
||||||
d="M13.24,123.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58c0.86,0.39,1.59,0.91,2.19,1.57
|
d="M13.24,123.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58c0.86,0.39,1.59,0.91,2.19,1.57
|
||||||
c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89c-0.35,0.9-0.84,1.68-1.47,2.35
|
c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89c-0.35,0.9-0.84,1.68-1.47,2.35
|
||||||
c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V146H8.35v-22.43H13.24z M19.4,139.11
|
c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V146H8.35v-22.43H13.24z M19.4,139.11
|
||||||
c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69c-0.38-0.17-0.79-0.26-1.24-0.26
|
c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69c-0.38-0.17-0.79-0.26-1.24-0.26
|
||||||
c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01c-0.17,0.39-0.26,0.8-0.26,1.23
|
c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01c-0.17,0.39-0.26,0.8-0.26,1.23
|
||||||
c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68c0.39,0.17,0.8,0.26,1.23,0.26
|
c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68c0.39,0.17,0.8,0.26,1.23,0.26
|
||||||
c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1C19.32,139.92,19.4,139.52,19.4,139.11z"
|
c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1C19.32,139.92,19.4,139.52,19.4,139.11z"
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
|
@ -102,24 +102,24 @@
|
||||||
<path
|
<path
|
||||||
class="st1"
|
class="st1"
|
||||||
d="M28.48,12v10.44c1.18-1.27,2.65-1.9,4.42-1.9c1.05,0,2.01,0.2,2.89,0.61c0.87,0.41,1.62,0.96,2.24,1.65
|
d="M28.48,12v10.44c1.18-1.27,2.65-1.9,4.42-1.9c1.05,0,2.01,0.2,2.89,0.61c0.87,0.41,1.62,0.96,2.24,1.65
|
||||||
c0.62,0.69,1.1,1.5,1.45,2.44c0.35,0.94,0.52,1.93,0.52,2.99c0,1.08-0.18,2.09-0.54,3.04c-0.36,0.95-0.86,1.77-1.51,2.47
|
c0.62,0.69,1.1,1.5,1.45,2.44c0.35,0.94,0.52,1.93,0.52,2.99c0,1.08-0.18,2.09-0.54,3.04c-0.36,0.95-0.86,1.77-1.51,2.47
|
||||||
c-0.64,0.7-1.4,1.25-2.28,1.66C34.8,35.8,33.86,36,32.84,36c-1.84,0-3.3-0.69-4.37-2.07v1.62h-5V12H28.48z M34.78,28.31
|
c-0.64,0.7-1.4,1.25-2.28,1.66C34.8,35.8,33.86,36,32.84,36c-1.84,0-3.3-0.69-4.37-2.07v1.62h-5V12H28.48z M34.78,28.31
|
||||||
c0-0.45-0.08-0.88-0.25-1.29c-0.17-0.41-0.4-0.76-0.69-1.06c-0.3-0.3-0.64-0.54-1.02-0.72c-0.39-0.18-0.81-0.27-1.27-0.27
|
c0-0.45-0.08-0.88-0.25-1.29c-0.17-0.41-0.4-0.76-0.69-1.06c-0.3-0.3-0.64-0.54-1.02-0.72c-0.39-0.18-0.81-0.27-1.27-0.27
|
||||||
c-0.44,0-0.86,0.09-1.24,0.26c-0.39,0.17-0.72,0.41-1.01,0.71c-0.29,0.3-0.52,0.66-0.69,1.06c-0.18,0.41-0.26,0.84-0.26,1.29
|
c-0.44,0-0.86,0.09-1.24,0.26c-0.39,0.17-0.72,0.41-1.01,0.71c-0.29,0.3-0.52,0.66-0.69,1.06c-0.18,0.41-0.26,0.84-0.26,1.29
|
||||||
s0.08,0.88,0.25,1.28c0.17,0.4,0.4,0.74,0.69,1.04c0.29,0.29,0.64,0.53,1.04,0.71c0.4,0.18,0.82,0.27,1.26,0.27
|
s0.08,0.88,0.25,1.28c0.17,0.4,0.4,0.74,0.69,1.04c0.29,0.29,0.64,0.53,1.04,0.71c0.4,0.18,0.82,0.27,1.26,0.27
|
||||||
c0.44,0,0.86-0.09,1.24-0.26c0.39-0.17,0.72-0.41,1.01-0.71c0.29-0.3,0.52-0.65,0.69-1.05C34.69,29.16,34.78,28.75,34.78,28.31z"
|
c0.44,0,0.86-0.09,1.24-0.26c0.39-0.17,0.72-0.41,1.01-0.71c0.29-0.3,0.52-0.65,0.69-1.05C34.69,29.16,34.78,28.75,34.78,28.31z"
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
<path
|
<path
|
||||||
class="st1"
|
class="st1"
|
||||||
d="M13,12v10.44c1.18-1.27,2.65-1.9,4.42-1.9c1.05,0,2.01,0.2,2.89,0.61c0.87,0.41,1.62,0.96,2.24,1.65
|
d="M13,12v10.44c1.18-1.27,2.65-1.9,4.42-1.9c1.05,0,2.01,0.2,2.89,0.61c0.87,0.41,1.62,0.96,2.24,1.65
|
||||||
c0.62,0.69,1.1,1.5,1.45,2.44c0.35,0.94,0.52,1.93,0.52,2.99c0,1.08-0.18,2.09-0.54,3.04c-0.36,0.95-0.86,1.77-1.51,2.47
|
c0.62,0.69,1.1,1.5,1.45,2.44c0.35,0.94,0.52,1.93,0.52,2.99c0,1.08-0.18,2.09-0.54,3.04c-0.36,0.95-0.86,1.77-1.51,2.47
|
||||||
c-0.64,0.7-1.4,1.25-2.28,1.66C19.32,35.8,18.38,36,17.37,36c-1.84,0-3.3-0.69-4.37-2.07v1.62H8V12H13z M19.3,28.31
|
c-0.64,0.7-1.4,1.25-2.28,1.66C19.32,35.8,18.38,36,17.37,36c-1.84,0-3.3-0.69-4.37-2.07v1.62H8V12H13z M19.3,28.31
|
||||||
c0-0.45-0.08-0.88-0.25-1.29c-0.17-0.41-0.4-0.76-0.69-1.06c-0.3-0.3-0.64-0.54-1.02-0.72c-0.39-0.18-0.81-0.27-1.27-0.27
|
c0-0.45-0.08-0.88-0.25-1.29c-0.17-0.41-0.4-0.76-0.69-1.06c-0.3-0.3-0.64-0.54-1.02-0.72c-0.39-0.18-0.81-0.27-1.27-0.27
|
||||||
c-0.44,0-0.86,0.09-1.24,0.26c-0.39,0.17-0.72,0.41-1.01,0.71c-0.29,0.3-0.52,0.66-0.69,1.06c-0.18,0.41-0.26,0.84-0.26,1.29
|
c-0.44,0-0.86,0.09-1.24,0.26c-0.39,0.17-0.72,0.41-1.01,0.71c-0.29,0.3-0.52,0.66-0.69,1.06c-0.18,0.41-0.26,0.84-0.26,1.29
|
||||||
s0.08,0.88,0.25,1.28c0.17,0.4,0.4,0.74,0.69,1.04c0.29,0.29,0.64,0.53,1.04,0.71c0.4,0.18,0.82,0.27,1.26,0.27
|
s0.08,0.88,0.25,1.28c0.17,0.4,0.4,0.74,0.69,1.04c0.29,0.29,0.64,0.53,1.04,0.71c0.4,0.18,0.82,0.27,1.26,0.27
|
||||||
c0.44,0,0.86-0.09,1.24-0.26c0.39-0.17,0.72-0.41,1.01-0.71c0.29-0.3,0.52-0.65,0.69-1.05C19.21,29.16,19.3,28.75,19.3,28.31z"
|
c0.44,0,0.86-0.09,1.24-0.26c0.39-0.17,0.72-0.41,1.01-0.71c0.29-0.3,0.52-0.65,0.69-1.05C19.21,29.16,19.3,28.75,19.3,28.31z"
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import {
|
import {
|
||||||
|
keepOpen,
|
||||||
ModalContent,
|
ModalContent,
|
||||||
notifications,
|
notifications,
|
||||||
Body,
|
Body,
|
||||||
|
@ -70,10 +71,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
notifications.success(`Imported successfully.`)
|
notifications.success(`Imported successfully.`)
|
||||||
return true
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error importing queries")
|
notifications.error("Error importing queries")
|
||||||
return false
|
return keepOpen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import {
|
import {
|
||||||
|
keepOpen,
|
||||||
Modal,
|
Modal,
|
||||||
notifications,
|
notifications,
|
||||||
Body,
|
Body,
|
||||||
|
@ -36,7 +37,7 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return keepOpen
|
||||||
}
|
}
|
||||||
|
|
||||||
let createVariableModal
|
let createVariableModal
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { RelationshipTypes } from "constants/backend"
|
import { RelationshipTypes } from "constants/backend"
|
||||||
import {
|
import {
|
||||||
|
keepOpen,
|
||||||
Button,
|
Button,
|
||||||
Input,
|
Input,
|
||||||
ModalContent,
|
ModalContent,
|
||||||
|
@ -277,7 +278,7 @@
|
||||||
|
|
||||||
async function saveRelationship() {
|
async function saveRelationship() {
|
||||||
if (!validate()) {
|
if (!validate()) {
|
||||||
return false
|
return keepOpen
|
||||||
}
|
}
|
||||||
buildRelationships()
|
buildRelationships()
|
||||||
removeExistingRelationship()
|
removeExistingRelationship()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { derived, writable, get } from "svelte/store"
|
import { derived, writable, get } from "svelte/store"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { keepOpen, notifications } from "@budibase/bbui"
|
||||||
import { datasources, ImportTableError, tables } from "stores/backend"
|
import { datasources, ImportTableError, tables } from "stores/backend"
|
||||||
|
|
||||||
export const createTableSelectionStore = (integration, datasource) => {
|
export const createTableSelectionStore = (integration, datasource) => {
|
||||||
|
@ -36,8 +36,7 @@ export const createTableSelectionStore = (integration, datasource) => {
|
||||||
notifications.error("Error fetching tables.")
|
notifications.error("Error fetching tables.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent modal closing
|
return keepOpen
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
let error = null
|
let error = null
|
||||||
let fileName = null
|
let fileName = null
|
||||||
let fileType = null
|
|
||||||
|
|
||||||
let loading = false
|
let loading = false
|
||||||
let updateExistingRows = false
|
let updateExistingRows = false
|
||||||
|
@ -74,7 +73,6 @@
|
||||||
const response = await parseFile(e)
|
const response = await parseFile(e)
|
||||||
rows = response.rows
|
rows = response.rows
|
||||||
fileName = response.fileName
|
fileName = response.fileName
|
||||||
fileType = response.fileType
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
loading = false
|
loading = false
|
||||||
error = e
|
error = e
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
let fileInput
|
let fileInput
|
||||||
let error = null
|
let error = null
|
||||||
let fileName = null
|
let fileName = null
|
||||||
let fileType = null
|
|
||||||
|
|
||||||
let loading = false
|
let loading = false
|
||||||
let validation = {}
|
let validation = {}
|
||||||
|
@ -60,7 +59,6 @@
|
||||||
rows = response.rows
|
rows = response.rows
|
||||||
schema = response.schema
|
schema = response.schema
|
||||||
fileName = response.fileName
|
fileName = response.fileName
|
||||||
fileType = response.fileType
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
loading = false
|
loading = false
|
||||||
error = e
|
error = e
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import EditViewPopover from "./popovers/EditViewPopover.svelte"
|
import EditViewPopover from "./popovers/EditViewPopover.svelte"
|
||||||
import NavItem from "components/common/NavItem.svelte"
|
import NavItem from "components/common/NavItem.svelte"
|
||||||
import { goto, isActive } from "@roxi/routify"
|
import { goto, isActive } from "@roxi/routify"
|
||||||
|
import { userSelectedResourceMap } from "builderStore"
|
||||||
|
|
||||||
const alphabetical = (a, b) =>
|
const alphabetical = (a, b) =>
|
||||||
a.name?.toLowerCase() > b.name?.toLowerCase() ? 1 : -1
|
a.name?.toLowerCase() > b.name?.toLowerCase() ? 1 : -1
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
selected={$isActive("./table/:tableId") &&
|
selected={$isActive("./table/:tableId") &&
|
||||||
$tables.selected?._id === table._id}
|
$tables.selected?._id === table._id}
|
||||||
on:click={() => selectTable(table._id)}
|
on:click={() => selectTable(table._id)}
|
||||||
|
selectedBy={$userSelectedResourceMap[table._id]}
|
||||||
>
|
>
|
||||||
{#if table._id !== TableNames.USERS}
|
{#if table._id !== TableNames.USERS}
|
||||||
<EditTablePopover {table} />
|
<EditTablePopover {table} />
|
||||||
|
@ -42,6 +44,7 @@
|
||||||
text={viewName}
|
text={viewName}
|
||||||
selected={$isActive("./view") && $views.selected?.name === viewName}
|
selected={$isActive("./view") && $views.selected?.name === viewName}
|
||||||
on:click={() => $goto(`./view/${encodeURIComponent(viewName)}`)}
|
on:click={() => $goto(`./view/${encodeURIComponent(viewName)}`)}
|
||||||
|
selectedBy={$userSelectedResourceMap[viewName]}
|
||||||
>
|
>
|
||||||
<EditViewPopover
|
<EditViewPopover
|
||||||
view={{ name: viewName, ...table.views[viewName] }}
|
view={{ name: viewName, ...table.views[viewName] }}
|
||||||
|
|
|
@ -2,21 +2,13 @@
|
||||||
import { goto, url } from "@roxi/routify"
|
import { goto, url } from "@roxi/routify"
|
||||||
import { tables } from "stores/backend"
|
import { tables } from "stores/backend"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
import {
|
import { Input, Label, ModalContent, Layout } from "@budibase/bbui"
|
||||||
Input,
|
|
||||||
Label,
|
|
||||||
ModalContent,
|
|
||||||
Toggle,
|
|
||||||
Divider,
|
|
||||||
Layout,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { datasources } from "stores/backend"
|
import { datasources } from "stores/backend"
|
||||||
import TableDataImport from "../TableDataImport.svelte"
|
import TableDataImport from "../TableDataImport.svelte"
|
||||||
import {
|
import {
|
||||||
BUDIBASE_INTERNAL_DB_ID,
|
BUDIBASE_INTERNAL_DB_ID,
|
||||||
BUDIBASE_DATASOURCE_TYPE,
|
BUDIBASE_DATASOURCE_TYPE,
|
||||||
} from "constants/backend"
|
} from "constants/backend"
|
||||||
import { buildAutoColumn, getAutoColumnInformation } from "builderStore/utils"
|
|
||||||
|
|
||||||
$: tableNames = $tables.list.map(table => table.name)
|
$: tableNames = $tables.list.map(table => table.name)
|
||||||
$: selectedSource = $datasources.list.find(
|
$: selectedSource = $datasources.list.find(
|
||||||
|
@ -43,28 +35,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
let error = ""
|
let error = ""
|
||||||
let autoColumns = getAutoColumnInformation()
|
|
||||||
let schema = {}
|
let schema = {}
|
||||||
let rows = []
|
let rows = []
|
||||||
let allValid = true
|
let allValid = true
|
||||||
let displayColumn = null
|
let displayColumn = null
|
||||||
|
|
||||||
function getAutoColumns() {
|
|
||||||
const selectedAutoColumns = {}
|
|
||||||
|
|
||||||
Object.entries(autoColumns).forEach(([subtype, column]) => {
|
|
||||||
if (column.enabled) {
|
|
||||||
selectedAutoColumns[column.name] = buildAutoColumn(
|
|
||||||
name,
|
|
||||||
column.name,
|
|
||||||
subtype
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return selectedAutoColumns
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkValid(evt) {
|
function checkValid(evt) {
|
||||||
const tableName = evt.target.value
|
const tableName = evt.target.value
|
||||||
if (tableNames.includes(tableName)) {
|
if (tableNames.includes(tableName)) {
|
||||||
|
@ -77,7 +53,7 @@
|
||||||
async function saveTable() {
|
async function saveTable() {
|
||||||
let newTable = {
|
let newTable = {
|
||||||
name,
|
name,
|
||||||
schema: { ...schema, ...getAutoColumns() },
|
schema: { ...schema },
|
||||||
rows,
|
rows,
|
||||||
type: "internal",
|
type: "internal",
|
||||||
sourceId: targetDatasourceId,
|
sourceId: targetDatasourceId,
|
||||||
|
@ -118,21 +94,6 @@
|
||||||
bind:value={name}
|
bind:value={name}
|
||||||
{error}
|
{error}
|
||||||
/>
|
/>
|
||||||
<div class="autocolumns">
|
|
||||||
<Label extraSmall grey>Auto Columns</Label>
|
|
||||||
<div class="toggles">
|
|
||||||
<div class="toggle-1">
|
|
||||||
<Toggle text="Created by" bind:value={autoColumns.createdBy.enabled} />
|
|
||||||
<Toggle text="Created at" bind:value={autoColumns.createdAt.enabled} />
|
|
||||||
<Toggle text="Auto ID" bind:value={autoColumns.autoID.enabled} />
|
|
||||||
</div>
|
|
||||||
<div class="toggle-2">
|
|
||||||
<Toggle text="Updated by" bind:value={autoColumns.updatedBy.enabled} />
|
|
||||||
<Toggle text="Updated at" bind:value={autoColumns.updatedAt.enabled} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Divider />
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<Label grey extraSmall
|
<Label grey extraSmall
|
||||||
|
@ -148,24 +109,3 @@
|
||||||
</Layout>
|
</Layout>
|
||||||
</div>
|
</div>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
||||||
<style>
|
|
||||||
.autocolumns {
|
|
||||||
margin-bottom: -10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggles {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-1 :global(> *) {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-2 :global(> *) {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -93,42 +93,42 @@
|
||||||
`https://github.com/Budibase/budibase/issues/new?assignees=&labels=bug&template=bug_report.md&title=`
|
`https://github.com/Budibase/budibase/issues/new?assignees=&labels=bug&template=bug_report.md&title=`
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
...$datasources?.list.map(datasource => ({
|
...($datasources?.list?.map(datasource => ({
|
||||||
type: "Datasource",
|
type: "Datasource",
|
||||||
name: `${datasource.name}`,
|
name: `${datasource.name}`,
|
||||||
icon: "Data",
|
icon: "Data",
|
||||||
action: () => $goto(`./data/datasource/${datasource._id}`),
|
action: () => $goto(`./data/datasource/${datasource._id}`),
|
||||||
})),
|
})) ?? []),
|
||||||
...$tables?.list.map(table => ({
|
...($tables?.list?.map(table => ({
|
||||||
type: "Table",
|
type: "Table",
|
||||||
name: table.name,
|
name: table.name,
|
||||||
icon: "Table",
|
icon: "Table",
|
||||||
action: () => $goto(`./data/table/${table._id}`),
|
action: () => $goto(`./data/table/${table._id}`),
|
||||||
})),
|
})) ?? []),
|
||||||
...$views?.list.map(view => ({
|
...($views?.list?.map(view => ({
|
||||||
type: "View",
|
type: "View",
|
||||||
name: view.name,
|
name: view.name,
|
||||||
icon: "Remove",
|
icon: "Remove",
|
||||||
action: () => $goto(`./data/view/${view.name}`),
|
action: () => $goto(`./data/view/${view.name}`),
|
||||||
})),
|
})) ?? []),
|
||||||
...$queries?.list.map(query => ({
|
...($queries?.list?.map(query => ({
|
||||||
type: "Query",
|
type: "Query",
|
||||||
name: query.name,
|
name: query.name,
|
||||||
icon: "SQLQuery",
|
icon: "SQLQuery",
|
||||||
action: () => $goto(`./data/query/${query._id}`),
|
action: () => $goto(`./data/query/${query._id}`),
|
||||||
})),
|
})) ?? []),
|
||||||
...$sortedScreens.map(screen => ({
|
...$sortedScreens.map(screen => ({
|
||||||
type: "Screen",
|
type: "Screen",
|
||||||
name: screen.routing.route,
|
name: screen.routing.route,
|
||||||
icon: "WebPage",
|
icon: "WebPage",
|
||||||
action: () => $goto(`./design/${screen._id}/components`),
|
action: () => $goto(`./design/${screen._id}/components`),
|
||||||
})),
|
})),
|
||||||
...$automationStore?.automations.map(automation => ({
|
...($automationStore?.automations?.map(automation => ({
|
||||||
type: "Automation",
|
type: "Automation",
|
||||||
name: automation.name,
|
name: automation.name,
|
||||||
icon: "ShareAndroid",
|
icon: "ShareAndroid",
|
||||||
action: () => $goto(`./automation/${automation._id}`),
|
action: () => $goto(`./automation/${automation._id}`),
|
||||||
})),
|
})) ?? []),
|
||||||
...Constants.Themes.map(theme => ({
|
...Constants.Themes.map(theme => ({
|
||||||
type: "Change Builder Theme",
|
type: "Change Builder Theme",
|
||||||
name: theme.name,
|
name: theme.name,
|
||||||
|
@ -208,8 +208,8 @@
|
||||||
|
|
||||||
async function deployApp() {
|
async function deployApp() {
|
||||||
try {
|
try {
|
||||||
await API.deployAppChanges()
|
await API.publishAppChanges($store.appId)
|
||||||
notifications.success("Application published successfully")
|
notifications.success("App published successfully")
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error publishing app")
|
notifications.error("Error publishing app")
|
||||||
}
|
}
|
||||||
|
@ -237,11 +237,11 @@
|
||||||
<Input bind:value={search} quiet placeholder="Search for command" />
|
<Input bind:value={search} quiet placeholder="Search for command" />
|
||||||
</div>
|
</div>
|
||||||
<div class="commands">
|
<div class="commands">
|
||||||
{#each categories as [name, results], catIdx}
|
{#each categories as [name, results]}
|
||||||
<div class="category">
|
<div class="category">
|
||||||
<Detail>{name}</Detail>
|
<Detail>{name}</Detail>
|
||||||
<div class="options">
|
<div class="options">
|
||||||
{#each results as command, cmdIdx}
|
{#each results as command}
|
||||||
<div
|
<div
|
||||||
class="command"
|
class="command"
|
||||||
on:click={() => runAction(command)}
|
on:click={() => runAction(command)}
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
closeBrackets,
|
closeBrackets,
|
||||||
completionKeymap,
|
completionKeymap,
|
||||||
closeBracketsKeymap,
|
closeBracketsKeymap,
|
||||||
|
acceptCompletion,
|
||||||
|
completionStatus,
|
||||||
} from "@codemirror/autocomplete"
|
} from "@codemirror/autocomplete"
|
||||||
import {
|
import {
|
||||||
EditorView,
|
EditorView,
|
||||||
|
@ -34,7 +36,8 @@
|
||||||
defaultKeymap,
|
defaultKeymap,
|
||||||
historyKeymap,
|
historyKeymap,
|
||||||
history,
|
history,
|
||||||
indentWithTab,
|
indentMore,
|
||||||
|
indentLess,
|
||||||
} from "@codemirror/commands"
|
} from "@codemirror/commands"
|
||||||
import { Compartment } from "@codemirror/state"
|
import { Compartment } from "@codemirror/state"
|
||||||
import { javascript } from "@codemirror/lang-javascript"
|
import { javascript } from "@codemirror/lang-javascript"
|
||||||
|
@ -107,6 +110,22 @@
|
||||||
let isDark = !currentTheme.includes("light")
|
let isDark = !currentTheme.includes("light")
|
||||||
let themeConfig = new Compartment()
|
let themeConfig = new Compartment()
|
||||||
|
|
||||||
|
const indentWithTabCustom = {
|
||||||
|
key: "Tab",
|
||||||
|
run: view => {
|
||||||
|
if (completionStatus(view.state) == "active") {
|
||||||
|
acceptCompletion(view)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
indentMore(view)
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
shift: view => {
|
||||||
|
indentLess(view)
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
const buildKeymap = () => {
|
const buildKeymap = () => {
|
||||||
const baseMap = [
|
const baseMap = [
|
||||||
...closeBracketsKeymap,
|
...closeBracketsKeymap,
|
||||||
|
@ -114,7 +133,7 @@
|
||||||
...historyKeymap,
|
...historyKeymap,
|
||||||
...foldKeymap,
|
...foldKeymap,
|
||||||
...completionKeymap,
|
...completionKeymap,
|
||||||
indentWithTab,
|
indentWithTabCustom,
|
||||||
]
|
]
|
||||||
return baseMap
|
return baseMap
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
<script>
|
||||||
|
import { Icon, Heading } from "@budibase/bbui"
|
||||||
|
|
||||||
|
export let showClose = false
|
||||||
|
export let onClose = () => {}
|
||||||
|
export let heading = ""
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section class="page">
|
||||||
|
<div class="closeButton">
|
||||||
|
{#if showClose}
|
||||||
|
<Icon hoverable name="Close" on:click={onClose} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="heading">
|
||||||
|
<Heading weight="light">{heading}</Heading>
|
||||||
|
</div>
|
||||||
|
<slot />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.page {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeButton {
|
||||||
|
height: 38px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: right;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -20,4 +20,5 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags-->
|
||||||
{@html substituteSize(svgHtml)}
|
{@html substituteSize(svgHtml)}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { Icon } from "@budibase/bbui"
|
import { Icon } from "@budibase/bbui"
|
||||||
import { createEventDispatcher, getContext } from "svelte"
|
import { createEventDispatcher, getContext } from "svelte"
|
||||||
|
import { helpers } from "@budibase/shared-core"
|
||||||
|
import UserAvatars from "../../pages/builder/app/[application]/_components/UserAvatars.svelte"
|
||||||
|
|
||||||
export let icon
|
export let icon
|
||||||
export let withArrow = false
|
export let withArrow = false
|
||||||
|
@ -18,12 +20,15 @@
|
||||||
export let rightAlignIcon = false
|
export let rightAlignIcon = false
|
||||||
export let id
|
export let id
|
||||||
export let showTooltip = false
|
export let showTooltip = false
|
||||||
|
export let selectedBy = null
|
||||||
|
|
||||||
const scrollApi = getContext("scroll")
|
const scrollApi = getContext("scroll")
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let contentRef
|
let contentRef
|
||||||
|
|
||||||
$: selected && contentRef && scrollToView()
|
$: selected && contentRef && scrollToView()
|
||||||
|
$: style = getStyle(indentLevel, selectedBy)
|
||||||
|
|
||||||
const onClick = () => {
|
const onClick = () => {
|
||||||
scrollToView()
|
scrollToView()
|
||||||
|
@ -42,6 +47,14 @@
|
||||||
const bounds = contentRef.getBoundingClientRect()
|
const bounds = contentRef.getBoundingClientRect()
|
||||||
scrollApi.scrollTo(bounds)
|
scrollApi.scrollTo(bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getStyle = (indentLevel, selectedBy) => {
|
||||||
|
let style = `padding-left:calc(${indentLevel * 14}px);`
|
||||||
|
if (selectedBy) {
|
||||||
|
style += `--selected-by-color:${helpers.getUserColor(selectedBy)};`
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -51,8 +64,7 @@
|
||||||
class:withActions
|
class:withActions
|
||||||
class:scrollable
|
class:scrollable
|
||||||
class:highlighted
|
class:highlighted
|
||||||
style={`padding-left: calc(${indentLevel * 14}px)`}
|
class:selectedBy
|
||||||
{draggable}
|
|
||||||
on:dragend
|
on:dragend
|
||||||
on:dragstart
|
on:dragstart
|
||||||
on:dragover
|
on:dragover
|
||||||
|
@ -61,6 +73,8 @@
|
||||||
ondragover="return false"
|
ondragover="return false"
|
||||||
ondragenter="return false"
|
ondragenter="return false"
|
||||||
{id}
|
{id}
|
||||||
|
{style}
|
||||||
|
{draggable}
|
||||||
>
|
>
|
||||||
<div class="nav-item-content" bind:this={contentRef}>
|
<div class="nav-item-content" bind:this={contentRef}>
|
||||||
{#if withArrow}
|
{#if withArrow}
|
||||||
|
@ -85,12 +99,19 @@
|
||||||
<Icon color={iconColor} size="S" name={icon} />
|
<Icon color={iconColor} size="S" name={icon} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="text" title={showTooltip ? text : null}>{text}</div>
|
<div class="text" title={showTooltip ? text : null}>
|
||||||
|
{text}
|
||||||
|
{#if selectedBy}
|
||||||
|
<UserAvatars size="XS" users={selectedBy} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if withActions}
|
{#if withActions}
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $$slots.right}
|
{#if $$slots.right}
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<slot name="right" />
|
<slot name="right" />
|
||||||
|
@ -111,6 +132,7 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
.nav-item.scrollable {
|
.nav-item.scrollable {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -119,13 +141,16 @@
|
||||||
}
|
}
|
||||||
.nav-item.highlighted {
|
.nav-item.highlighted {
|
||||||
background-color: var(--spectrum-global-color-gray-200);
|
background-color: var(--spectrum-global-color-gray-200);
|
||||||
|
--avatars-background: var(--spectrum-global-color-gray-200);
|
||||||
}
|
}
|
||||||
.nav-item.selected {
|
.nav-item.selected {
|
||||||
background-color: var(--spectrum-global-color-gray-300);
|
background-color: var(--spectrum-global-color-gray-300);
|
||||||
|
--avatars-background: var(--spectrum-global-color-gray-300);
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
.nav-item:hover {
|
.nav-item:hover {
|
||||||
background-color: var(--spectrum-global-color-gray-300);
|
background-color: var(--spectrum-global-color-gray-300);
|
||||||
|
--avatars-background: var(--spectrum-global-color-gray-300);
|
||||||
}
|
}
|
||||||
.nav-item:hover .actions {
|
.nav-item:hover .actions {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
|
@ -197,6 +222,9 @@
|
||||||
color: var(--spectrum-global-color-gray-900);
|
color: var(--spectrum-global-color-gray-900);
|
||||||
order: 2;
|
order: 2;
|
||||||
width: 0;
|
width: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
.scrollable .text {
|
.scrollable .text {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|
|
@ -208,7 +208,9 @@
|
||||||
<div class="syntax-error">
|
<div class="syntax-error">
|
||||||
Current Handlebars syntax is invalid, please check the
|
Current Handlebars syntax is invalid, please check the
|
||||||
guide
|
guide
|
||||||
<a href="https://handlebarsjs.com/guide/">here</a>
|
<a href="https://handlebarsjs.com/guide/" target="_blank"
|
||||||
|
>here</a
|
||||||
|
>
|
||||||
for more details.
|
for more details.
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
|
@ -88,6 +88,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
{#if hoverTarget.description}
|
{#if hoverTarget.description}
|
||||||
<div class="helper__description">
|
<div class="helper__description">
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags-->
|
||||||
{@html hoverTarget.description}
|
{@html hoverTarget.description}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -124,7 +125,6 @@
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
||||||
<span
|
<span
|
||||||
class="search-input-icon"
|
class="search-input-icon"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
|
@ -162,7 +162,6 @@
|
||||||
</div>
|
</div>
|
||||||
<ul>
|
<ul>
|
||||||
{#each category.bindings as binding}
|
{#each category.bindings as binding}
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
||||||
<li
|
<li
|
||||||
class="binding"
|
class="binding"
|
||||||
on:mouseenter={e => {
|
on:mouseenter={e => {
|
||||||
|
|
|
@ -10,19 +10,17 @@
|
||||||
Link,
|
Link,
|
||||||
Modal,
|
Modal,
|
||||||
StatusLight,
|
StatusLight,
|
||||||
|
AbsTooltip,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import RevertModal from "components/deploy/RevertModal.svelte"
|
import RevertModal from "components/deploy/RevertModal.svelte"
|
||||||
import VersionModal from "components/deploy/VersionModal.svelte"
|
import VersionModal from "components/deploy/VersionModal.svelte"
|
||||||
import UpdateAppModal from "components/start/UpdateAppModal.svelte"
|
import UpdateAppModal from "components/start/UpdateAppModal.svelte"
|
||||||
|
|
||||||
import { processStringSync } from "@budibase/string-templates"
|
import { processStringSync } from "@budibase/string-templates"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
import analytics, { Events, EventSource } from "analytics"
|
import analytics, { Events, EventSource } from "analytics"
|
||||||
import { checkIncomingDeploymentStatus } from "components/deploy/utils"
|
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { onMount } from "svelte"
|
|
||||||
import { apps } from "stores/portal"
|
import { apps } from "stores/portal"
|
||||||
import { store } from "builderStore"
|
import { deploymentStore, store, isOnlyUser } from "builderStore"
|
||||||
import TourWrap from "components/portal/onboarding/TourWrap.svelte"
|
import TourWrap from "components/portal/onboarding/TourWrap.svelte"
|
||||||
import { TOUR_STEP_KEYS } from "components/portal/onboarding/tours.js"
|
import { TOUR_STEP_KEYS } from "components/portal/onboarding/tours.js"
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
|
@ -34,37 +32,31 @@
|
||||||
let updateAppModal
|
let updateAppModal
|
||||||
let revertModal
|
let revertModal
|
||||||
let versionModal
|
let versionModal
|
||||||
|
|
||||||
let appActionPopover
|
let appActionPopover
|
||||||
let appActionPopoverOpen = false
|
let appActionPopoverOpen = false
|
||||||
let appActionPopoverAnchor
|
let appActionPopoverAnchor
|
||||||
|
|
||||||
let publishing = false
|
let publishing = false
|
||||||
|
|
||||||
$: filteredApps = $apps.filter(app => app.devId === application)
|
$: filteredApps = $apps.filter(app => app.devId === application)
|
||||||
$: selectedApp = filteredApps?.length ? filteredApps[0] : null
|
$: selectedApp = filteredApps?.length ? filteredApps[0] : null
|
||||||
|
$: latestDeployments = $deploymentStore
|
||||||
$: deployments = []
|
|
||||||
$: latestDeployments = deployments
|
|
||||||
.filter(deployment => deployment.status === "SUCCESS")
|
.filter(deployment => deployment.status === "SUCCESS")
|
||||||
.sort((a, b) => a.updatedAt > b.updatedAt)
|
.sort((a, b) => a.updatedAt > b.updatedAt)
|
||||||
|
|
||||||
$: isPublished =
|
$: isPublished =
|
||||||
selectedApp?.status === "published" && latestDeployments?.length > 0
|
selectedApp?.status === "published" && latestDeployments?.length > 0
|
||||||
|
|
||||||
$: updateAvailable =
|
$: updateAvailable =
|
||||||
$store.upgradableVersion &&
|
$store.upgradableVersion &&
|
||||||
$store.version &&
|
$store.version &&
|
||||||
$store.upgradableVersion !== $store.version
|
$store.upgradableVersion !== $store.version
|
||||||
|
|
||||||
$: canPublish = !publishing && loaded
|
$: canPublish = !publishing && loaded
|
||||||
|
$: lastDeployed = getLastDeployedString($deploymentStore)
|
||||||
|
|
||||||
const initialiseApp = async () => {
|
const initialiseApp = async () => {
|
||||||
const applicationPkg = await API.fetchAppPackage($store.devId)
|
const applicationPkg = await API.fetchAppPackage($store.devId)
|
||||||
await store.actions.initialise(applicationPkg)
|
await store.actions.initialise(applicationPkg)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateDeploymentString = () => {
|
const getLastDeployedString = deployments => {
|
||||||
return deployments?.length
|
return deployments?.length
|
||||||
? processStringSync("Published {{ duration time 'millisecond' }} ago", {
|
? processStringSync("Published {{ duration time 'millisecond' }} ago", {
|
||||||
time:
|
time:
|
||||||
|
@ -73,27 +65,6 @@
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
const reviewPendingDeployments = (deployments, newDeployments) => {
|
|
||||||
if (deployments.length > 0) {
|
|
||||||
const pending = checkIncomingDeploymentStatus(deployments, newDeployments)
|
|
||||||
if (pending.length) {
|
|
||||||
notifications.warning(
|
|
||||||
"Deployment has been queued and will be processed shortly"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchDeployments() {
|
|
||||||
try {
|
|
||||||
const newDeployments = await API.getAppDeployments()
|
|
||||||
reviewPendingDeployments(deployments, newDeployments)
|
|
||||||
return newDeployments
|
|
||||||
} catch (err) {
|
|
||||||
notifications.error("Error fetching deployment overview")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const previewApp = () => {
|
const previewApp = () => {
|
||||||
store.update(state => ({
|
store.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
|
@ -116,14 +87,11 @@
|
||||||
async function publishApp() {
|
async function publishApp() {
|
||||||
try {
|
try {
|
||||||
publishing = true
|
publishing = true
|
||||||
|
|
||||||
await API.publishAppChanges($store.appId)
|
await API.publishAppChanges($store.appId)
|
||||||
|
notifications.send("App published successfully", {
|
||||||
notifications.send("App published", {
|
|
||||||
type: "success",
|
type: "success",
|
||||||
icon: "GlobeCheck",
|
icon: "GlobeCheck",
|
||||||
})
|
})
|
||||||
|
|
||||||
await completePublish()
|
await completePublish()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
@ -163,210 +131,201 @@
|
||||||
const completePublish = async () => {
|
const completePublish = async () => {
|
||||||
try {
|
try {
|
||||||
await apps.load()
|
await apps.load()
|
||||||
deployments = await fetchDeployments()
|
await deploymentStore.actions.load()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notifications.error("Error refreshing app")
|
notifications.error("Error refreshing app")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
if (!$apps.length) {
|
|
||||||
await apps.load()
|
|
||||||
}
|
|
||||||
deployments = await fetchDeployments()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $store.hasLock}
|
<div class="action-top-nav">
|
||||||
<div class="action-top-nav" class:has-lock={$store.hasLock}>
|
<div class="action-buttons">
|
||||||
<div class="action-buttons">
|
{#if updateAvailable && $isOnlyUser}
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<div class="app-action-button version" on:click={versionModal.show}>
|
||||||
{#if updateAvailable}
|
|
||||||
<div class="app-action-button version" on:click={versionModal.show}>
|
|
||||||
<div class="app-action">
|
|
||||||
<ActionButton quiet>
|
|
||||||
<StatusLight notice />
|
|
||||||
Update
|
|
||||||
</ActionButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<TourWrap
|
|
||||||
tourStepKey={$store.onboarding
|
|
||||||
? TOUR_STEP_KEYS.BUILDER_USER_MANAGEMENT
|
|
||||||
: TOUR_STEP_KEYS.FEATURE_USER_MANAGEMENT}
|
|
||||||
>
|
|
||||||
<div class="app-action-button users">
|
|
||||||
<div class="app-action" id="builder-app-users-button">
|
|
||||||
<ActionButton
|
|
||||||
quiet
|
|
||||||
icon="UserGroup"
|
|
||||||
on:click={() => {
|
|
||||||
store.update(state => {
|
|
||||||
state.builderSidePanel = true
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Users
|
|
||||||
</ActionButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</TourWrap>
|
|
||||||
|
|
||||||
<div class="app-action-button preview">
|
|
||||||
<div class="app-action">
|
<div class="app-action">
|
||||||
<ActionButton quiet icon="PlayCircle" on:click={previewApp}>
|
<ActionButton quiet>
|
||||||
Preview
|
<StatusLight notice />
|
||||||
|
Update
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<TourWrap
|
||||||
<div
|
tourStepKey={$store.onboarding
|
||||||
class="app-action-button publish app-action-popover"
|
? TOUR_STEP_KEYS.BUILDER_USER_MANAGEMENT
|
||||||
on:click={() => {
|
: TOUR_STEP_KEYS.FEATURE_USER_MANAGEMENT}
|
||||||
if (!appActionPopoverOpen) {
|
>
|
||||||
appActionPopover.show()
|
<div class="app-action-button users">
|
||||||
} else {
|
<div class="app-action" id="builder-app-users-button">
|
||||||
appActionPopover.hide()
|
<ActionButton
|
||||||
}
|
quiet
|
||||||
}}
|
icon="UserGroup"
|
||||||
>
|
on:click={() => {
|
||||||
<div bind:this={appActionPopoverAnchor}>
|
store.update(state => {
|
||||||
<div class="app-action">
|
state.builderSidePanel = true
|
||||||
<Icon name={isPublished ? "GlobeCheck" : "GlobeStrike"} />
|
return state
|
||||||
<TourWrap tourStepKey={TOUR_STEP_KEYS.BUILDER_APP_PUBLISH}>
|
})
|
||||||
<span class="publish-open" id="builder-app-publish-button">
|
}}
|
||||||
Publish
|
>
|
||||||
<Icon
|
Users
|
||||||
name={appActionPopoverOpen ? "ChevronUp" : "ChevronDown"}
|
</ActionButton>
|
||||||
size="M"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</TourWrap>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<Popover
|
</div>
|
||||||
bind:this={appActionPopover}
|
</TourWrap>
|
||||||
align="right"
|
|
||||||
disabled={!isPublished}
|
|
||||||
anchor={appActionPopoverAnchor}
|
|
||||||
offset={35}
|
|
||||||
on:close={() => {
|
|
||||||
appActionPopoverOpen = false
|
|
||||||
}}
|
|
||||||
on:open={() => {
|
|
||||||
appActionPopoverOpen = true
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class="app-action-popover-content">
|
|
||||||
<Layout noPadding gap="M">
|
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
||||||
<Body size="M">
|
|
||||||
<span
|
|
||||||
class="app-link"
|
|
||||||
on:click={() => {
|
|
||||||
if (isPublished) {
|
|
||||||
viewApp()
|
|
||||||
} else {
|
|
||||||
appActionPopover.hide()
|
|
||||||
updateAppModal.show()
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{$store.url}
|
|
||||||
{#if isPublished}
|
|
||||||
<Icon size="S" name="LinkOut" />
|
|
||||||
{:else}
|
|
||||||
<Icon size="S" name="Edit" />
|
|
||||||
{/if}
|
|
||||||
</span>
|
|
||||||
</Body>
|
|
||||||
|
|
||||||
<Body size="S">
|
<div class="app-action-button preview">
|
||||||
<span class="publish-popover-status">
|
<div class="app-action">
|
||||||
{#if isPublished}
|
<ActionButton quiet icon="PlayCircle" on:click={previewApp}>
|
||||||
<span class="status-text">
|
Preview
|
||||||
{updateDeploymentString(deployments)}
|
</ActionButton>
|
||||||
</span>
|
|
||||||
<span class="unpublish-link">
|
|
||||||
<Link quiet on:click={unpublishApp}>Unpublish</Link>
|
|
||||||
</span>
|
|
||||||
<span class="revert-link">
|
|
||||||
<Link quiet secondary on:click={revertApp}>Revert</Link>
|
|
||||||
</span>
|
|
||||||
{:else}
|
|
||||||
<span class="status-text unpublished">Not published</span>
|
|
||||||
{/if}
|
|
||||||
</span>
|
|
||||||
</Body>
|
|
||||||
<div class="action-buttons">
|
|
||||||
{#if $store.hasLock}
|
|
||||||
{#if isPublished}
|
|
||||||
<ActionButton
|
|
||||||
quiet
|
|
||||||
icon="Code"
|
|
||||||
on:click={() => {
|
|
||||||
$goto("./settings/embed")
|
|
||||||
appActionPopover.hide()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Embed
|
|
||||||
</ActionButton>
|
|
||||||
{/if}
|
|
||||||
<Button
|
|
||||||
cta
|
|
||||||
on:click={publishApp}
|
|
||||||
id={"builder-app-publish-button"}
|
|
||||||
disabled={!canPublish}
|
|
||||||
>
|
|
||||||
Publish
|
|
||||||
</Button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Modals -->
|
<div
|
||||||
<ConfirmDialog
|
class="app-action-button publish app-action-popover"
|
||||||
bind:this={unpublishModal}
|
on:click={() => {
|
||||||
title="Confirm unpublish"
|
if (!appActionPopoverOpen) {
|
||||||
okText="Unpublish app"
|
appActionPopover.show()
|
||||||
onOk={confirmUnpublishApp}
|
} else {
|
||||||
>
|
appActionPopover.hide()
|
||||||
Are you sure you want to unpublish the app <b>{selectedApp?.name}</b>?
|
}
|
||||||
</ConfirmDialog>
|
|
||||||
|
|
||||||
<Modal bind:this={updateAppModal} padding={false} width="600px">
|
|
||||||
<UpdateAppModal
|
|
||||||
app={{
|
|
||||||
name: $store.name,
|
|
||||||
url: $store.url,
|
|
||||||
icon: $store.icon,
|
|
||||||
appId: $store.appId,
|
|
||||||
}}
|
}}
|
||||||
onUpdateComplete={async () => {
|
>
|
||||||
await initialiseApp()
|
<div bind:this={appActionPopoverAnchor}>
|
||||||
}}
|
<div class="app-action">
|
||||||
/>
|
<Icon name={isPublished ? "GlobeCheck" : "GlobeStrike"} />
|
||||||
</Modal>
|
<TourWrap tourStepKey={TOUR_STEP_KEYS.BUILDER_APP_PUBLISH}>
|
||||||
|
<span class="publish-open" id="builder-app-publish-button">
|
||||||
|
Publish
|
||||||
|
<Icon
|
||||||
|
name={appActionPopoverOpen ? "ChevronUp" : "ChevronDown"}
|
||||||
|
size="M"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</TourWrap>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Popover
|
||||||
|
bind:this={appActionPopover}
|
||||||
|
align="right"
|
||||||
|
disabled={!isPublished}
|
||||||
|
anchor={appActionPopoverAnchor}
|
||||||
|
offset={35}
|
||||||
|
on:close={() => {
|
||||||
|
appActionPopoverOpen = false
|
||||||
|
}}
|
||||||
|
on:open={() => {
|
||||||
|
appActionPopoverOpen = true
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="app-action-popover-content">
|
||||||
|
<Layout noPadding gap="M">
|
||||||
|
<Body size="M">
|
||||||
|
<span
|
||||||
|
class="app-link"
|
||||||
|
on:click={() => {
|
||||||
|
if (isPublished) {
|
||||||
|
viewApp()
|
||||||
|
} else {
|
||||||
|
appActionPopover.hide()
|
||||||
|
updateAppModal.show()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{$store.url}
|
||||||
|
{#if isPublished}
|
||||||
|
<Icon size="S" name="LinkOut" />
|
||||||
|
{:else}
|
||||||
|
<Icon size="S" name="Edit" />
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</Body>
|
||||||
|
|
||||||
<RevertModal bind:this={revertModal} />
|
<Body size="S">
|
||||||
<VersionModal hideIcon bind:this={versionModal} />
|
<span class="publish-popover-status">
|
||||||
{:else}
|
{#if isPublished}
|
||||||
<div class="app-action-button preview-locked">
|
<span class="status-text">
|
||||||
<div class="app-action">
|
{lastDeployed}
|
||||||
<ActionButton quiet icon="PlayCircle" on:click={previewApp}>
|
</span>
|
||||||
Preview
|
<span class="unpublish-link">
|
||||||
</ActionButton>
|
<Link quiet on:click={unpublishApp}>Unpublish</Link>
|
||||||
|
</span>
|
||||||
|
<span class="revert-link">
|
||||||
|
<AbsTooltip
|
||||||
|
text={$isOnlyUser
|
||||||
|
? null
|
||||||
|
: "Unavailable - another user is editing this app"}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
disabled={!$isOnlyUser}
|
||||||
|
quiet
|
||||||
|
secondary
|
||||||
|
on:click={revertApp}
|
||||||
|
>
|
||||||
|
Revert
|
||||||
|
</Link>
|
||||||
|
</AbsTooltip>
|
||||||
|
</span>
|
||||||
|
{:else}
|
||||||
|
<span class="status-text unpublished">Not published</span>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</Body>
|
||||||
|
<div class="action-buttons">
|
||||||
|
{#if isPublished}
|
||||||
|
<ActionButton
|
||||||
|
quiet
|
||||||
|
icon="Code"
|
||||||
|
on:click={() => {
|
||||||
|
$goto("./settings/embed")
|
||||||
|
appActionPopover.hide()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Embed
|
||||||
|
</ActionButton>
|
||||||
|
{/if}
|
||||||
|
<Button
|
||||||
|
cta
|
||||||
|
on:click={publishApp}
|
||||||
|
id={"builder-app-publish-button"}
|
||||||
|
disabled={!canPublish}
|
||||||
|
>
|
||||||
|
Publish
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
</div>
|
||||||
|
|
||||||
|
<!-- Modals -->
|
||||||
|
<ConfirmDialog
|
||||||
|
bind:this={unpublishModal}
|
||||||
|
title="Confirm unpublish"
|
||||||
|
okText="Unpublish app"
|
||||||
|
onOk={confirmUnpublishApp}
|
||||||
|
>
|
||||||
|
Are you sure you want to unpublish the app <b>{selectedApp?.name}</b>?
|
||||||
|
</ConfirmDialog>
|
||||||
|
|
||||||
|
<Modal bind:this={updateAppModal} padding={false} width="600px">
|
||||||
|
<UpdateAppModal
|
||||||
|
app={{
|
||||||
|
name: $store.name,
|
||||||
|
url: $store.url,
|
||||||
|
icon: $store.icon,
|
||||||
|
appId: $store.appId,
|
||||||
|
}}
|
||||||
|
onUpdateComplete={async () => {
|
||||||
|
await initialiseApp()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<RevertModal bind:this={revertModal} />
|
||||||
|
<VersionModal hideIcon bind:this={versionModal} />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.app-action-popover-content {
|
.app-action-popover-content {
|
||||||
|
@ -450,10 +409,6 @@
|
||||||
gap: var(--spectrum-actionbutton-icon-gap);
|
gap: var(--spectrum-actionbutton-icon-gap);
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-action-button.preview-locked {
|
|
||||||
padding-right: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-action {
|
.app-action {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -1,118 +0,0 @@
|
||||||
<script>
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Modal,
|
|
||||||
notifications,
|
|
||||||
ModalContent,
|
|
||||||
Layout,
|
|
||||||
ProgressCircle,
|
|
||||||
CopyInput,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { API } from "api"
|
|
||||||
import analytics, { Events, EventSource } from "analytics"
|
|
||||||
import { store } from "builderStore"
|
|
||||||
import TourWrap from "../portal/onboarding/TourWrap.svelte"
|
|
||||||
import { TOUR_STEP_KEYS } from "../portal/onboarding/tours.js"
|
|
||||||
|
|
||||||
let publishModal
|
|
||||||
let asyncModal
|
|
||||||
let publishCompleteModal
|
|
||||||
|
|
||||||
let published
|
|
||||||
|
|
||||||
$: publishedUrl = published ? `${window.origin}/app${published.appUrl}` : ""
|
|
||||||
|
|
||||||
export let onOk
|
|
||||||
|
|
||||||
async function publishApp() {
|
|
||||||
try {
|
|
||||||
//In Progress
|
|
||||||
asyncModal.show()
|
|
||||||
publishModal.hide()
|
|
||||||
|
|
||||||
published = await API.publishAppChanges($store.appId)
|
|
||||||
|
|
||||||
if (typeof onOk === "function") {
|
|
||||||
await onOk()
|
|
||||||
}
|
|
||||||
|
|
||||||
//Request completed
|
|
||||||
asyncModal.hide()
|
|
||||||
publishCompleteModal.show()
|
|
||||||
} catch (error) {
|
|
||||||
analytics.captureException(error)
|
|
||||||
notifications.error("Error publishing app")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const viewApp = () => {
|
|
||||||
if (published) {
|
|
||||||
analytics.captureEvent(Events.APP_VIEW_PUBLISHED, {
|
|
||||||
appId: $store.appId,
|
|
||||||
eventSource: EventSource.PORTAL,
|
|
||||||
})
|
|
||||||
window.open(publishedUrl, "_blank")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<TourWrap tourStepKey={TOUR_STEP_KEYS.BUILDER_APP_PUBLISH}>
|
|
||||||
<Button cta on:click={publishModal.show} id={"builder-app-publish-button"}>
|
|
||||||
Publish
|
|
||||||
</Button>
|
|
||||||
</TourWrap>
|
|
||||||
<Modal bind:this={publishModal}>
|
|
||||||
<ModalContent
|
|
||||||
title="Publish to production"
|
|
||||||
confirmText="Publish"
|
|
||||||
onConfirm={publishApp}
|
|
||||||
>
|
|
||||||
The changes you have made will be published to the production version of the
|
|
||||||
application.
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<!-- Publish in progress -->
|
|
||||||
<Modal bind:this={asyncModal}>
|
|
||||||
<ModalContent
|
|
||||||
showCancelButton={false}
|
|
||||||
showConfirmButton={false}
|
|
||||||
showCloseIcon={false}
|
|
||||||
>
|
|
||||||
<Layout justifyItems="center">
|
|
||||||
<ProgressCircle size="XL" />
|
|
||||||
</Layout>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<!-- Publish complete -->
|
|
||||||
<Modal bind:this={publishCompleteModal}>
|
|
||||||
<ModalContent confirmText="Done" cancelText="View App" onCancel={viewApp}>
|
|
||||||
<div slot="header" class="app-published-header">
|
|
||||||
<svg
|
|
||||||
width="26px"
|
|
||||||
height="26px"
|
|
||||||
class="spectrum-Icon success-icon"
|
|
||||||
focusable="false"
|
|
||||||
>
|
|
||||||
<use xlink:href="#spectrum-icon-18-GlobeCheck" />
|
|
||||||
</svg>
|
|
||||||
<span class="app-published-header-text">App Published!</span>
|
|
||||||
</div>
|
|
||||||
<CopyInput value={publishedUrl} label="You can view your app at:" />
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.app-published-header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.success-icon {
|
|
||||||
color: var(--spectrum-global-color-green-600);
|
|
||||||
}
|
|
||||||
.app-published-header .app-published-header-text {
|
|
||||||
padding-left: var(--spacing-l);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,236 +0,0 @@
|
||||||
<script>
|
|
||||||
import { onMount, onDestroy } from "svelte"
|
|
||||||
import Spinner from "components/common/Spinner.svelte"
|
|
||||||
import { slide } from "svelte/transition"
|
|
||||||
import { Heading, Button, Modal, ModalContent } from "@budibase/bbui"
|
|
||||||
import { API } from "api"
|
|
||||||
import { notifications } from "@budibase/bbui"
|
|
||||||
import CreateWebhookDeploymentModal from "./CreateWebhookDeploymentModal.svelte"
|
|
||||||
import { store } from "builderStore"
|
|
||||||
import {
|
|
||||||
checkIncomingDeploymentStatus,
|
|
||||||
DeploymentStatus,
|
|
||||||
} from "components/deploy/utils"
|
|
||||||
|
|
||||||
const DATE_OPTIONS = {
|
|
||||||
fullDate: {
|
|
||||||
weekday: "long",
|
|
||||||
year: "numeric",
|
|
||||||
month: "long",
|
|
||||||
day: "numeric",
|
|
||||||
},
|
|
||||||
timeOnly: {
|
|
||||||
hour: "numeric",
|
|
||||||
minute: "numeric",
|
|
||||||
hourCycle: "h12",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
const POLL_INTERVAL = 5000
|
|
||||||
|
|
||||||
export let appId
|
|
||||||
|
|
||||||
let modal
|
|
||||||
let errorReasonModal
|
|
||||||
let errorReason
|
|
||||||
let poll
|
|
||||||
let deployments = []
|
|
||||||
let urlComponent = $store.url || `/${appId}`
|
|
||||||
let deploymentUrl = `${urlComponent}`
|
|
||||||
|
|
||||||
const formatDate = (date, format) =>
|
|
||||||
Intl.DateTimeFormat("en-GB", DATE_OPTIONS[format]).format(date)
|
|
||||||
|
|
||||||
async function fetchDeployments() {
|
|
||||||
try {
|
|
||||||
const newDeployments = await API.getAppDeployments()
|
|
||||||
if (deployments.length > 0) {
|
|
||||||
const pendingDeployments = checkIncomingDeploymentStatus(
|
|
||||||
deployments,
|
|
||||||
newDeployments
|
|
||||||
)
|
|
||||||
if (pendingDeployments.length) {
|
|
||||||
showErrorReasonModal(pendingDeployments[0].err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
deployments = newDeployments
|
|
||||||
} catch (err) {
|
|
||||||
clearInterval(poll)
|
|
||||||
notifications.error("Error fetching deployment overview")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showErrorReasonModal(err) {
|
|
||||||
if (!err) return
|
|
||||||
errorReason = err
|
|
||||||
errorReasonModal.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
fetchDeployments()
|
|
||||||
poll = setInterval(fetchDeployments, POLL_INTERVAL)
|
|
||||||
})
|
|
||||||
|
|
||||||
onDestroy(() => clearInterval(poll))
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if deployments.length > 0}
|
|
||||||
<section class="deployment-history" in:slide>
|
|
||||||
<header>
|
|
||||||
<Heading>Deployment History</Heading>
|
|
||||||
<div class="deploy-div">
|
|
||||||
{#if deployments.some(deployment => deployment.status === DeploymentStatus.SUCCESS)}
|
|
||||||
<a target="_blank" href={deploymentUrl}> View Your Deployed App → </a>
|
|
||||||
<Button primary on:click={() => modal.show()}>View webhooks</Button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<div class="deployment-list">
|
|
||||||
{#each deployments as deployment}
|
|
||||||
<article class="deployment">
|
|
||||||
<div class="deployment-info">
|
|
||||||
<span class="deploy-date">
|
|
||||||
{formatDate(deployment.updatedAt, "fullDate")}
|
|
||||||
</span>
|
|
||||||
<span class="deploy-time">
|
|
||||||
{formatDate(deployment.updatedAt, "timeOnly")}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="deployment-right">
|
|
||||||
{#if deployment.status.toLowerCase() === "pending"}
|
|
||||||
<Spinner size="10" />
|
|
||||||
{/if}
|
|
||||||
<div
|
|
||||||
on:click={() => showErrorReasonModal(deployment.err)}
|
|
||||||
class={`deployment-status ${deployment.status}`}
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
{deployment.status}
|
|
||||||
{#if deployment.status === DeploymentStatus.FAILURE}
|
|
||||||
<i class="ri-information-line" />
|
|
||||||
{/if}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{/if}
|
|
||||||
<Modal bind:this={modal} width="30%">
|
|
||||||
<CreateWebhookDeploymentModal />
|
|
||||||
</Modal>
|
|
||||||
<Modal bind:this={errorReasonModal} width="30%">
|
|
||||||
<ModalContent
|
|
||||||
title="Deployment Error"
|
|
||||||
confirmText="OK"
|
|
||||||
showCancelButton={false}
|
|
||||||
>
|
|
||||||
{errorReason}
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
section {
|
|
||||||
padding: var(--spacing-xl) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deployment-list {
|
|
||||||
height: 40vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
padding-left: var(--spacing-l);
|
|
||||||
padding-bottom: var(--spacing-xl);
|
|
||||||
padding-right: var(--spacing-l);
|
|
||||||
border-bottom: var(--border-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.deploy-div {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deployment-history {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
background: var(--background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.deployment {
|
|
||||||
padding: var(--spacing-l);
|
|
||||||
height: 60px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
border-bottom: var(--border-light);
|
|
||||||
}
|
|
||||||
.deployment:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deployment-info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin-right: var(--spacing-s);
|
|
||||||
}
|
|
||||||
|
|
||||||
.deploy-date {
|
|
||||||
font-size: var(--font-size-m);
|
|
||||||
}
|
|
||||||
|
|
||||||
.deploy-time {
|
|
||||||
color: var(--grey-7);
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: var(--font-size-s);
|
|
||||||
}
|
|
||||||
|
|
||||||
.deployment-right {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: 16px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deployment-status {
|
|
||||||
font-size: var(--font-size-s);
|
|
||||||
padding: var(--spacing-s);
|
|
||||||
border-radius: var(--border-radius-s);
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: lowercase;
|
|
||||||
width: 80px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.deployment-status:first-letter {
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--blue);
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: var(--font-size-s);
|
|
||||||
}
|
|
||||||
|
|
||||||
.SUCCESS {
|
|
||||||
color: var(--green);
|
|
||||||
background: var(--green-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.PENDING {
|
|
||||||
color: var(--yellow);
|
|
||||||
background: var(--yellow-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.FAILURE {
|
|
||||||
color: var(--red);
|
|
||||||
background: var(--red-light);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
position: relative;
|
|
||||||
top: 2px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,25 +0,0 @@
|
||||||
export const DeploymentStatus = {
|
|
||||||
SUCCESS: "SUCCESS",
|
|
||||||
PENDING: "PENDING",
|
|
||||||
FAILURE: "FAILURE",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required to check any updated deployment statuses between polls
|
|
||||||
export function checkIncomingDeploymentStatus(current, incoming) {
|
|
||||||
return incoming.reduce((acc, incomingDeployment) => {
|
|
||||||
if (incomingDeployment.status === DeploymentStatus.FAILURE) {
|
|
||||||
const currentDeployment = current.find(
|
|
||||||
deployment => deployment._id === incomingDeployment._id
|
|
||||||
)
|
|
||||||
|
|
||||||
//We have just been notified of an ongoing deployments failure
|
|
||||||
if (
|
|
||||||
!currentDeployment ||
|
|
||||||
currentDeployment.status === DeploymentStatus.PENDING
|
|
||||||
) {
|
|
||||||
acc.push(incomingDeployment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return acc
|
|
||||||
}, [])
|
|
||||||
}
|
|
|
@ -16,6 +16,7 @@
|
||||||
makeStateBinding,
|
makeStateBinding,
|
||||||
} from "builderStore/dataBinding"
|
} from "builderStore/dataBinding"
|
||||||
import { currentAsset, store } from "builderStore"
|
import { currentAsset, store } from "builderStore"
|
||||||
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
|
||||||
const flipDurationMs = 150
|
const flipDurationMs = 150
|
||||||
const EVENT_TYPE_KEY = "##eventHandlerType"
|
const EVENT_TYPE_KEY = "##eventHandlerType"
|
||||||
|
@ -29,6 +30,26 @@
|
||||||
let actionQuery
|
let actionQuery
|
||||||
let selectedAction = actions?.length ? actions[0] : null
|
let selectedAction = actions?.length ? actions[0] : null
|
||||||
|
|
||||||
|
const setUpdateActions = actions => {
|
||||||
|
return actions
|
||||||
|
? cloneDeep(actions)
|
||||||
|
.filter(action => {
|
||||||
|
return (
|
||||||
|
action[EVENT_TYPE_KEY] === "Update State" &&
|
||||||
|
action.parameters?.type === "set" &&
|
||||||
|
action.parameters.key
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.reduce((acc, action) => {
|
||||||
|
acc[action.id] = action
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snapshot original action state
|
||||||
|
let updateStateActions = setUpdateActions(actions)
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
// Ensure parameters object is never null
|
// Ensure parameters object is never null
|
||||||
if (selectedAction && !selectedAction.parameters) {
|
if (selectedAction && !selectedAction.parameters) {
|
||||||
|
@ -125,8 +146,9 @@
|
||||||
actions = e.detail.items
|
actions = e.detail.items
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAllBindings = (bindings, eventContextBindings, actions) => {
|
const getAllBindings = (actionBindings, eventContextBindings, actions) => {
|
||||||
let allBindings = []
|
let allBindings = []
|
||||||
|
let cloneActionBindings = cloneDeep(actionBindings)
|
||||||
if (!actions) {
|
if (!actions) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
@ -144,11 +166,19 @@
|
||||||
.forEach(action => {
|
.forEach(action => {
|
||||||
// Check we have a binding for this action, and generate one if not
|
// Check we have a binding for this action, and generate one if not
|
||||||
const stateBinding = makeStateBinding(action.parameters.key)
|
const stateBinding = makeStateBinding(action.parameters.key)
|
||||||
const hasKey = bindings.some(binding => {
|
const hasKey = actionBindings.some(binding => {
|
||||||
return binding.runtimeBinding === stateBinding.runtimeBinding
|
return binding.runtimeBinding === stateBinding.runtimeBinding
|
||||||
})
|
})
|
||||||
if (!hasKey) {
|
if (!hasKey) {
|
||||||
bindings.push(stateBinding)
|
let existing = updateStateActions[action.id]
|
||||||
|
if (existing) {
|
||||||
|
const existingBinding = makeStateBinding(existing.parameters.key)
|
||||||
|
cloneActionBindings = cloneActionBindings.filter(
|
||||||
|
binding =>
|
||||||
|
binding.runtimeBinding !== existingBinding.runtimeBinding
|
||||||
|
)
|
||||||
|
}
|
||||||
|
allBindings.push(stateBinding)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// Get which indexes are asynchronous automations as we want to filter them out from the bindings
|
// Get which indexes are asynchronous automations as we want to filter them out from the bindings
|
||||||
|
@ -164,15 +194,16 @@
|
||||||
.filter(index => index !== undefined)
|
.filter(index => index !== undefined)
|
||||||
|
|
||||||
// Based on the above, filter out the asynchronous automations from the bindings
|
// Based on the above, filter out the asynchronous automations from the bindings
|
||||||
if (asynchronousAutomationIndexes) {
|
let contextBindings = asynchronousAutomationIndexes
|
||||||
allBindings = eventContextBindings
|
? eventContextBindings.filter((binding, index) => {
|
||||||
.filter((binding, index) => {
|
|
||||||
return !asynchronousAutomationIndexes.includes(index)
|
return !asynchronousAutomationIndexes.includes(index)
|
||||||
})
|
})
|
||||||
.concat(bindings)
|
: eventContextBindings
|
||||||
} else {
|
|
||||||
allBindings = eventContextBindings.concat(bindings)
|
allBindings = contextBindings
|
||||||
}
|
.concat(cloneActionBindings)
|
||||||
|
.concat(allBindings)
|
||||||
|
|
||||||
return allBindings
|
return allBindings
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -70,8 +70,9 @@
|
||||||
} set`
|
} set`
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="action-count">{actionText}</div>
|
<div class="action-editor">
|
||||||
<ActionButton on:click={openDrawer}>Define actions</ActionButton>
|
<ActionButton on:click={openDrawer}>{actionText}</ActionButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Drawer bind:this={drawer} title={"Actions"}>
|
<Drawer bind:this={drawer} title={"Actions"}>
|
||||||
<svelte:fragment slot="description">
|
<svelte:fragment slot="description">
|
||||||
|
@ -89,9 +90,7 @@
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.action-count {
|
.action-editor :global(.spectrum-ActionButton) {
|
||||||
padding-top: 6px;
|
width: 100%;
|
||||||
padding-bottom: var(--spacing-s);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -42,7 +42,6 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
$: hasAutomations = automations && automations.length > 0
|
|
||||||
$: selectedAutomation = automations?.find(
|
$: selectedAutomation = automations?.find(
|
||||||
a => a._id === parameters?.automationId
|
a => a._id === parameters?.automationId
|
||||||
)
|
)
|
||||||
|
@ -145,12 +144,6 @@
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.params {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
gap: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.synchronous-info {
|
.synchronous-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--spacing-s);
|
gap: var(--spacing-s);
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
let drawer
|
let drawer
|
||||||
let boundValue
|
let boundValue
|
||||||
|
|
||||||
|
$: text = getText(value)
|
||||||
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
|
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
|
||||||
$: schema = getSchema($currentAsset, datasource)
|
$: schema = getSchema($currentAsset, datasource)
|
||||||
$: options = allowCellEditing
|
$: options = allowCellEditing
|
||||||
|
@ -31,6 +32,17 @@
|
||||||
allowLinks: true,
|
allowLinks: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const getText = value => {
|
||||||
|
if (!value?.length) {
|
||||||
|
return "All columns"
|
||||||
|
}
|
||||||
|
let text = `${value.length} column`
|
||||||
|
if (value.length !== 1) {
|
||||||
|
text += "s"
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
const getSchema = (asset, datasource) => {
|
const getSchema = (asset, datasource) => {
|
||||||
const schema = getSchemaForDatasource(asset, datasource).schema
|
const schema = getSchemaForDatasource(asset, datasource).schema
|
||||||
|
|
||||||
|
@ -76,7 +88,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="column-editor">
|
<div class="column-editor">
|
||||||
<ActionButton on:click={open}>Configure columns</ActionButton>
|
<ActionButton on:click={open}>{text}</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
<Drawer bind:this={drawer} title="Columns">
|
<Drawer bind:this={drawer} title="Columns">
|
||||||
<Button cta slot="buttons" on:click={save}>Save</Button>
|
<Button cta slot="buttons" on:click={save}>Save</Button>
|
||||||
|
|
|
@ -8,32 +8,39 @@
|
||||||
getSchemaForDatasource,
|
getSchemaForDatasource,
|
||||||
} from "builderStore/dataBinding"
|
} from "builderStore/dataBinding"
|
||||||
import { currentAsset } from "builderStore"
|
import { currentAsset } from "builderStore"
|
||||||
import { getFields } from "helpers/searchFields"
|
|
||||||
|
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
export let value = []
|
export let value = []
|
||||||
|
|
||||||
const convertOldColumnFormat = oldColumns => {
|
|
||||||
if (typeof oldColumns?.[0] === "string") {
|
|
||||||
value = oldColumns.map(field => ({ name: field, displayName: field }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$: convertOldColumnFormat(value)
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let drawer
|
let drawer
|
||||||
let boundValue
|
let boundValue
|
||||||
|
|
||||||
|
$: text = getText(value)
|
||||||
|
$: convertOldColumnFormat(value)
|
||||||
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
|
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
|
||||||
$: schema = getSchema($currentAsset, datasource)
|
$: schema = getSchema($currentAsset, datasource)
|
||||||
$: options = Object.keys(schema || {})
|
$: options = Object.keys(schema || {})
|
||||||
$: sanitisedValue = getValidColumns(value, options)
|
$: sanitisedValue = getValidColumns(value, options)
|
||||||
$: updateBoundValue(sanitisedValue)
|
$: updateBoundValue(sanitisedValue)
|
||||||
$: enrichedSchemaFields = getFields(Object.values(schema || {}), {
|
|
||||||
allowLinks: true,
|
const getText = value => {
|
||||||
})
|
if (!value?.length) {
|
||||||
|
return "All fields"
|
||||||
|
}
|
||||||
|
let text = `${value.length} field`
|
||||||
|
if (value.length !== 1) {
|
||||||
|
text += "s"
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
const convertOldColumnFormat = oldColumns => {
|
||||||
|
if (typeof oldColumns?.[0] === "string") {
|
||||||
|
value = oldColumns.map(field => ({ name: field, displayName: field }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const getSchema = (asset, datasource) => {
|
const getSchema = (asset, datasource) => {
|
||||||
const schema = getSchemaForDatasource(asset, datasource).schema
|
const schema = getSchemaForDatasource(asset, datasource).schema
|
||||||
|
@ -79,7 +86,10 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionButton on:click={open}>Configure fields</ActionButton>
|
<div class="field-configuration">
|
||||||
|
<ActionButton on:click={open}>{text}</ActionButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Drawer bind:this={drawer} title="Form Fields">
|
<Drawer bind:this={drawer} title="Form Fields">
|
||||||
<svelte:fragment slot="description">
|
<svelte:fragment slot="description">
|
||||||
Configure the fields in your form.
|
Configure the fields in your form.
|
||||||
|
@ -87,3 +97,9 @@
|
||||||
<Button cta slot="buttons" on:click={save}>Save</Button>
|
<Button cta slot="buttons" on:click={save}>Save</Button>
|
||||||
<ColumnDrawer slot="body" bind:columns={boundValue} {options} {schema} />
|
<ColumnDrawer slot="body" bind:columns={boundValue} {options} {schema} />
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.field-configuration :global(.spectrum-ActionButton) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -192,7 +192,7 @@
|
||||||
<Label>Filters</Label>
|
<Label>Filters</Label>
|
||||||
</div>
|
</div>
|
||||||
<div class="fields">
|
<div class="fields">
|
||||||
{#each rawFilters as filter, idx}
|
{#each rawFilters as filter}
|
||||||
<Select
|
<Select
|
||||||
bind:value={filter.field}
|
bind:value={filter.field}
|
||||||
options={fieldOptions}
|
options={fieldOptions}
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
<i class={icon} />
|
<i class={icon} />
|
||||||
{:else}
|
{:else}
|
||||||
<span>
|
<span>
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
{@html text}
|
{@html text}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -27,7 +27,6 @@
|
||||||
$: nullishValue = value == null || value === ""
|
$: nullishValue = value == null || value === ""
|
||||||
$: allBindings = getAllBindings(bindings, componentBindings, nested)
|
$: allBindings = getAllBindings(bindings, componentBindings, nested)
|
||||||
$: safeValue = getSafeValue(value, defaultValue, allBindings)
|
$: safeValue = getSafeValue(value, defaultValue, allBindings)
|
||||||
$: tempValue = safeValue
|
|
||||||
$: replaceBindings = val => readableToRuntimeBinding(allBindings, val)
|
$: replaceBindings = val => readableToRuntimeBinding(allBindings, val)
|
||||||
|
|
||||||
const getAllBindings = (bindings, componentBindings, nested) => {
|
const getAllBindings = (bindings, componentBindings, nested) => {
|
||||||
|
@ -104,6 +103,7 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if info}
|
{#if info}
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
<div class="text">{@html info}</div>
|
<div class="text">{@html info}</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,31 +2,39 @@
|
||||||
export let text
|
export let text
|
||||||
export let url
|
export let url
|
||||||
export let active = false
|
export let active = false
|
||||||
|
export let disabled = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if url}
|
<div class="side-nav-item">
|
||||||
<a on:click href={url} class:active>
|
{#if url}
|
||||||
{text || ""}
|
<a class="text" on:click href={url} class:active class:disabled>
|
||||||
</a>
|
{text || ""}
|
||||||
{:else}
|
</a>
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
{:else}
|
||||||
<span on:click class:active>
|
<div class="text" on:click class:active class:disabled>
|
||||||
{text || ""}
|
{text || ""}
|
||||||
</span>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
a,
|
.side-nav-item {
|
||||||
span {
|
position: relative;
|
||||||
|
}
|
||||||
|
.text {
|
||||||
|
display: block;
|
||||||
padding: var(--spacing-s) var(--spacing-m);
|
padding: var(--spacing-s) var(--spacing-m);
|
||||||
color: var(--spectrum-global-color-gray-900);
|
color: var(--spectrum-global-color-gray-900);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: background 130ms ease-out;
|
transition: background 130ms ease-out;
|
||||||
}
|
}
|
||||||
.active,
|
.active,
|
||||||
span:hover,
|
.text:hover {
|
||||||
a:hover {
|
|
||||||
background-color: var(--spectrum-global-color-gray-200);
|
background-color: var(--spectrum-global-color-gray-200);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
color: var(--spectrum-global-color-gray-500) !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { ModalContent, Body, notifications, CopyInput } from "@budibase/bbui"
|
import {
|
||||||
|
ModalContent,
|
||||||
|
keepOpen,
|
||||||
|
Body,
|
||||||
|
notifications,
|
||||||
|
CopyInput,
|
||||||
|
} from "@budibase/bbui"
|
||||||
import { auth } from "stores/portal"
|
import { auth } from "stores/portal"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
|
@ -12,8 +18,8 @@
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notifications.error("Unable to generate new API key")
|
notifications.error("Unable to generate new API key")
|
||||||
}
|
}
|
||||||
// need to return false to keep modal open
|
|
||||||
return false
|
return keepOpen
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
import { writable, get as svelteGet } from "svelte/store"
|
import { writable, get as svelteGet } from "svelte/store"
|
||||||
import { notifications, Input, ModalContent, Dropzone } from "@budibase/bbui"
|
import {
|
||||||
|
notifications,
|
||||||
|
keepOpen,
|
||||||
|
Input,
|
||||||
|
ModalContent,
|
||||||
|
Dropzone,
|
||||||
|
} from "@budibase/bbui"
|
||||||
import { store, automationStore } from "builderStore"
|
import { store, automationStore } from "builderStore"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { apps, admin, auth } from "stores/portal"
|
import { apps, admin, auth } from "stores/portal"
|
||||||
|
@ -9,8 +15,6 @@
|
||||||
import { createValidationStore } from "helpers/validation/yup"
|
import { createValidationStore } from "helpers/validation/yup"
|
||||||
import * as appValidation from "helpers/validation/yup/app"
|
import * as appValidation from "helpers/validation/yup/app"
|
||||||
import TemplateCard from "components/common/TemplateCard.svelte"
|
import TemplateCard from "components/common/TemplateCard.svelte"
|
||||||
import createFromScratchScreen from "builderStore/store/screenTemplates/createFromScratchScreen"
|
|
||||||
import { Roles } from "constants/backend"
|
|
||||||
import { lowercase } from "helpers"
|
import { lowercase } from "helpers"
|
||||||
|
|
||||||
export let template
|
export let template
|
||||||
|
@ -136,21 +140,6 @@
|
||||||
// Create user
|
// Create user
|
||||||
await auth.setInitInfo({})
|
await auth.setInitInfo({})
|
||||||
|
|
||||||
// Create a default home screen if no template was selected
|
|
||||||
if (template == null) {
|
|
||||||
let defaultScreenTemplate = createFromScratchScreen.create()
|
|
||||||
defaultScreenTemplate.routing.route = "/home"
|
|
||||||
defaultScreenTemplate.routing.roldId = Roles.BASIC
|
|
||||||
try {
|
|
||||||
await store.actions.screens.save(defaultScreenTemplate)
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Could not create a default application screen", err)
|
|
||||||
notifications.warning(
|
|
||||||
"Encountered an issue creating the default screen."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$goto(`/builder/app/${createdApp.instance._id}`)
|
$goto(`/builder/app/${createdApp.instance._id}`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
creating = false
|
creating = false
|
||||||
|
@ -167,7 +156,7 @@
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
if (encryptedFile) {
|
if (encryptedFile) {
|
||||||
currentStep = Step.SET_PASSWORD
|
currentStep = Step.SET_PASSWORD
|
||||||
return false
|
return keepOpen
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await createNewApp()
|
await createNewApp()
|
||||||
|
@ -190,7 +179,7 @@
|
||||||
message += `: ${lowercase(e.message)}`
|
message += `: ${lowercase(e.message)}`
|
||||||
}
|
}
|
||||||
notifications.error(message)
|
notifications.error(message)
|
||||||
return false
|
return keepOpen
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isValid: $encryptionValidation.valid,
|
isValid: $encryptionValidation.valid,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import {
|
import {
|
||||||
ModalContent,
|
ModalContent,
|
||||||
|
keepOpen,
|
||||||
Toggle,
|
Toggle,
|
||||||
Body,
|
Body,
|
||||||
InlineAlert,
|
InlineAlert,
|
||||||
|
@ -32,7 +33,7 @@
|
||||||
exportApp()
|
exportApp()
|
||||||
} else {
|
} else {
|
||||||
currentStep = Step.SET_PASSWORD
|
currentStep = Step.SET_PASSWORD
|
||||||
return false
|
return keepOpen
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isValid: true,
|
isValid: true,
|
||||||
|
@ -43,7 +44,7 @@
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
await validation.check({ password })
|
await validation.check({ password })
|
||||||
if (!$validation.valid) {
|
if (!$validation.valid) {
|
||||||
return false
|
return keepOpen
|
||||||
}
|
}
|
||||||
exportApp(password)
|
exportApp(password)
|
||||||
},
|
},
|
||||||
|
|
|
@ -114,26 +114,24 @@ export const syncURLToState = options => {
|
||||||
|
|
||||||
// Updates the URL with new state values
|
// Updates the URL with new state values
|
||||||
const mapStateToUrl = state => {
|
const mapStateToUrl = state => {
|
||||||
let needsUpdate = false
|
|
||||||
const urlValue = cachedParams?.[urlParam]
|
const urlValue = cachedParams?.[urlParam]
|
||||||
const stateValue = state?.[stateKey]
|
const stateValue = state?.[stateKey]
|
||||||
if (stateValue !== urlValue) {
|
|
||||||
needsUpdate = true
|
// As the store updated, validate that the current state value is valid
|
||||||
log(`url.${urlParam} (${urlValue}) <= state.${stateKey} (${stateValue})`)
|
if (validate && fallbackUrl) {
|
||||||
if (validate && fallbackUrl) {
|
if (!validate(stateValue)) {
|
||||||
if (!validate(stateValue)) {
|
log("Invalid state param!", stateValue)
|
||||||
log("Invalid state param!", stateValue)
|
redirectUrl(fallbackUrl)
|
||||||
redirectUrl(fallbackUrl)
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid updating the URL if not necessary to prevent a wasted render
|
// Avoid updating the URL if not necessary to prevent a wasted render
|
||||||
// cycle
|
// cycle
|
||||||
if (!needsUpdate) {
|
if (stateValue === urlValue) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log(`url.${urlParam} (${urlValue}) <= state.${stateKey} (${stateValue})`)
|
||||||
|
|
||||||
// Navigate to the new URL
|
// Navigate to the new URL
|
||||||
if (!get(isChangingPage)) {
|
if (!get(isChangingPage)) {
|
||||||
|
|
|
@ -14,17 +14,16 @@
|
||||||
import { groups, licensing, apps, users, auth, admin } from "stores/portal"
|
import { groups, licensing, apps, users, auth, admin } from "stores/portal"
|
||||||
import { fetchData } from "@budibase/frontend-core"
|
import { fetchData } from "@budibase/frontend-core"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { onMount } from "svelte"
|
|
||||||
import GroupIcon from "../../../portal/users/groups/_components/GroupIcon.svelte"
|
import GroupIcon from "../../../portal/users/groups/_components/GroupIcon.svelte"
|
||||||
import RoleSelect from "components/common/RoleSelect.svelte"
|
import RoleSelect from "components/common/RoleSelect.svelte"
|
||||||
import UpgradeModal from "components/common/users/UpgradeModal.svelte"
|
import UpgradeModal from "components/common/users/UpgradeModal.svelte"
|
||||||
import { Constants, Utils } from "@budibase/frontend-core"
|
import { Constants, Utils } from "@budibase/frontend-core"
|
||||||
import { emailValidator } from "helpers/validation"
|
import { emailValidator } from "helpers/validation"
|
||||||
import { roles } from "stores/backend"
|
import { roles } from "stores/backend"
|
||||||
|
import { fly } from "svelte/transition"
|
||||||
|
|
||||||
let query = null
|
let query = null
|
||||||
let loaded = false
|
let loaded = false
|
||||||
let rendered = false
|
|
||||||
let inviting = false
|
let inviting = false
|
||||||
let searchFocus = false
|
let searchFocus = false
|
||||||
|
|
||||||
|
@ -383,10 +382,6 @@
|
||||||
|
|
||||||
$: initSidePanel($store.builderSidePanel)
|
$: initSidePanel($store.builderSidePanel)
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
rendered = true
|
|
||||||
})
|
|
||||||
|
|
||||||
function handleKeyDown(evt) {
|
function handleKeyDown(evt) {
|
||||||
if (evt.key === "Enter" && queryIsEmail && !inviting) {
|
if (evt.key === "Enter" && queryIsEmail && !inviting) {
|
||||||
onInviteUser()
|
onInviteUser()
|
||||||
|
@ -418,16 +413,14 @@
|
||||||
<svelte:window on:keydown={handleKeyDown} />
|
<svelte:window on:keydown={handleKeyDown} />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
transition:fly={{ x: 400, duration: 260 }}
|
||||||
id="builder-side-panel-container"
|
id="builder-side-panel-container"
|
||||||
class:open={$store.builderSidePanel}
|
use:clickOutside={() => {
|
||||||
use:clickOutside={$store.builderSidePanel
|
store.update(state => {
|
||||||
? () => {
|
state.builderSidePanel = false
|
||||||
store.update(state => {
|
return state
|
||||||
state.builderSidePanel = false
|
})
|
||||||
return state
|
}}
|
||||||
})
|
|
||||||
}
|
|
||||||
: () => {}}
|
|
||||||
>
|
>
|
||||||
<div class="builder-side-panel-header">
|
<div class="builder-side-panel-header">
|
||||||
<Heading size="S">Users</Heading>
|
<Heading size="S">Users</Heading>
|
||||||
|
@ -737,12 +730,11 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
transition: transform 130ms ease-out;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 400px;
|
width: 400px;
|
||||||
right: 0;
|
right: 0;
|
||||||
transform: translateX(100%);
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
box-shadow: 0 0 40px 10px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.builder-side-panel-header,
|
.builder-side-panel-header,
|
||||||
|
@ -792,11 +784,6 @@
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
#builder-side-panel-container.open {
|
|
||||||
transform: translateX(0);
|
|
||||||
box-shadow: 0 0 40px 10px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.builder-side-panel-header {
|
.builder-side-panel-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
window.isBuilder = true
|
||||||
window.closePreview = () => {
|
window.closePreview = () => {
|
||||||
store.update(state => ({
|
store.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
import { UserAvatar } from "@budibase/frontend-core"
|
import { UserAvatar } from "@budibase/frontend-core"
|
||||||
|
import { TooltipPosition, Avatar } from "@budibase/bbui"
|
||||||
|
|
||||||
export let users = []
|
export let users = []
|
||||||
|
export let order = "ltr"
|
||||||
|
export let size = "S"
|
||||||
|
export let tooltipPosition = TooltipPosition.Top
|
||||||
|
|
||||||
$: uniqueUsers = unique(users)
|
$: uniqueUsers = unique(users, order)
|
||||||
|
$: avatars = getAvatars(uniqueUsers, order)
|
||||||
|
|
||||||
const unique = users => {
|
const unique = users => {
|
||||||
let uniqueUsers = {}
|
let uniqueUsers = {}
|
||||||
|
@ -12,17 +17,51 @@
|
||||||
})
|
})
|
||||||
return Object.values(uniqueUsers)
|
return Object.values(uniqueUsers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getAvatars = (users, order) => {
|
||||||
|
const avatars = users.slice(0, 3)
|
||||||
|
if (users.length > 3) {
|
||||||
|
const overflow = {
|
||||||
|
_id: "overflow",
|
||||||
|
label: `+${users.length - 3}`,
|
||||||
|
}
|
||||||
|
if (order === "ltr") {
|
||||||
|
avatars.push(overflow)
|
||||||
|
} else {
|
||||||
|
avatars.unshift(overflow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return avatars.map((user, idx) => ({
|
||||||
|
...user,
|
||||||
|
zIndex: order === "ltr" ? idx : uniqueUsers.length - idx,
|
||||||
|
}))
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="avatars">
|
<div class="avatars">
|
||||||
{#each uniqueUsers as user}
|
{#each avatars as user}
|
||||||
<UserAvatar {user} tooltipDirection="bottom" />
|
<span style="z-index:{user.zIndex};">
|
||||||
|
{#if user._id === "overflow"}
|
||||||
|
<Avatar
|
||||||
|
{size}
|
||||||
|
initials={user.label}
|
||||||
|
color="var(--spectrum-global-color-gray-500)"
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<UserAvatar {size} {user} {tooltipPosition} />
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.avatars {
|
.avatars {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4px;
|
}
|
||||||
|
span:not(:first-of-type) {
|
||||||
|
margin-left: -6px;
|
||||||
|
}
|
||||||
|
.avatars :global(.spectrum-Avatar) {
|
||||||
|
border: 2px solid var(--avatars-background, var(--background));
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
import { store, automationStore, userStore } from "builderStore"
|
import {
|
||||||
|
store,
|
||||||
|
automationStore,
|
||||||
|
userStore,
|
||||||
|
deploymentStore,
|
||||||
|
} from "builderStore"
|
||||||
import { roles, flags } from "stores/backend"
|
import { roles, flags } from "stores/backend"
|
||||||
import { auth } from "stores/portal"
|
import { auth, apps } from "stores/portal"
|
||||||
import { TENANT_FEATURE_FLAGS, isEnabled } from "helpers/featureFlags"
|
import { TENANT_FEATURE_FLAGS, isEnabled } from "helpers/featureFlags"
|
||||||
import {
|
import {
|
||||||
Icon,
|
Icon,
|
||||||
|
@ -10,6 +15,7 @@
|
||||||
Heading,
|
Heading,
|
||||||
Modal,
|
Modal,
|
||||||
notifications,
|
notifications,
|
||||||
|
TooltipPosition,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import AppActions from "components/deploy/AppActions.svelte"
|
import AppActions from "components/deploy/AppActions.svelte"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
|
@ -44,6 +50,8 @@
|
||||||
await automationStore.actions.fetch()
|
await automationStore.actions.fetch()
|
||||||
await roles.fetch()
|
await roles.fetch()
|
||||||
await flags.fetch()
|
await flags.fetch()
|
||||||
|
await apps.load()
|
||||||
|
await deploymentStore.actions.load()
|
||||||
loaded = true
|
loaded = true
|
||||||
return pkg
|
return pkg
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -69,18 +77,13 @@
|
||||||
|
|
||||||
// Event handler for the command palette
|
// Event handler for the command palette
|
||||||
const handleKeyDown = e => {
|
const handleKeyDown = e => {
|
||||||
if (e.key === "k" && (e.ctrlKey || e.metaKey) && $store.hasLock) {
|
if (e.key === "k" && (e.ctrlKey || e.metaKey)) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
commandPaletteModal.toggle()
|
commandPaletteModal.toggle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const initTour = async () => {
|
const initTour = async () => {
|
||||||
// Skip tour if we don't have the lock
|
|
||||||
if (!$store.hasLock) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if onboarding is enabled.
|
// Check if onboarding is enabled.
|
||||||
if (isEnabled(TENANT_FEATURE_FLAGS.ONBOARDING_TOUR)) {
|
if (isEnabled(TENANT_FEATURE_FLAGS.ONBOARDING_TOUR)) {
|
||||||
if (!$auth.user?.onboardedAt) {
|
if (!$auth.user?.onboardedAt) {
|
||||||
|
@ -140,7 +143,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="root" class:blur={$store.showPreview}>
|
<div class="root" class:blur={$store.showPreview}>
|
||||||
<div class="top-nav" class:has-lock={$store.hasLock}>
|
<div class="top-nav">
|
||||||
{#if $store.initialised}
|
{#if $store.initialised}
|
||||||
<div class="topleftnav">
|
<div class="topleftnav">
|
||||||
<span class="back-to-apps">
|
<span class="back-to-apps">
|
||||||
|
@ -151,38 +154,30 @@
|
||||||
on:click={() => $goto("../../portal/apps")}
|
on:click={() => $goto("../../portal/apps")}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
{#if $store.hasLock}
|
<Tabs {selected} size="M">
|
||||||
<Tabs {selected} size="M">
|
{#each $layout.children as { path, title }}
|
||||||
{#each $layout.children as { path, title }}
|
<TourWrap tourStepKey={`builder-${title}-section`}>
|
||||||
<TourWrap tourStepKey={`builder-${title}-section`}>
|
<Tab
|
||||||
<Tab
|
quiet
|
||||||
quiet
|
selected={$isActive(path)}
|
||||||
selected={$isActive(path)}
|
on:click={topItemNavigate(path)}
|
||||||
on:click={topItemNavigate(path)}
|
title={capitalise(title)}
|
||||||
title={capitalise(title)}
|
id={`builder-${title}-tab`}
|
||||||
id={`builder-${title}-tab`}
|
/>
|
||||||
/>
|
</TourWrap>
|
||||||
</TourWrap>
|
{/each}
|
||||||
{/each}
|
</Tabs>
|
||||||
</Tabs>
|
|
||||||
{:else}
|
|
||||||
<div class="secondary-editor">
|
|
||||||
<Icon name="LockClosed" />
|
|
||||||
<div
|
|
||||||
class="secondary-editor-body"
|
|
||||||
title="Another user is currently editing your screens and automations"
|
|
||||||
>
|
|
||||||
Another user is currently editing your screens and automations
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="topcenternav">
|
<div class="topcenternav">
|
||||||
<Heading size="XS">{$store.name}</Heading>
|
<Heading size="XS">{$store.name}</Heading>
|
||||||
</div>
|
</div>
|
||||||
<div class="toprightnav">
|
<div class="toprightnav">
|
||||||
<span class:nav-lock={!$store.hasLock}>
|
<span>
|
||||||
<UserAvatars users={$userStore} />
|
<UserAvatars
|
||||||
|
users={$userStore}
|
||||||
|
order="rtl"
|
||||||
|
tooltipPosition={TooltipPosition.Bottom}
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
<AppActions {application} {loaded} />
|
<AppActions {application} {loaded} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -238,7 +233,7 @@
|
||||||
.top-nav {
|
.top-nav {
|
||||||
flex: 0 0 60px;
|
flex: 0 0 60px;
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
padding: 0 var(--spacing-xl);
|
padding-left: var(--spacing-xl);
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto 1fr;
|
grid-template-columns: 1fr auto 1fr;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -248,10 +243,6 @@
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-nav.has-lock {
|
|
||||||
padding-right: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topcenternav {
|
.topcenternav {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -290,23 +281,6 @@
|
||||||
margin-right: var(--spacing-l);
|
margin-right: var(--spacing-l);
|
||||||
}
|
}
|
||||||
|
|
||||||
.secondary-editor {
|
|
||||||
align-self: center;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: 8px;
|
|
||||||
min-width: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
margin-left: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
|
|
||||||
.secondary-editor-body {
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
min-width: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
|
@ -9,14 +9,9 @@
|
||||||
import { syncURLToState } from "helpers/urlStateSync"
|
import { syncURLToState } from "helpers/urlStateSync"
|
||||||
import * as routify from "@roxi/routify"
|
import * as routify from "@roxi/routify"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import { redirect } from "@roxi/routify"
|
|
||||||
|
|
||||||
// Prevent access for other users than the lock holder
|
$: automationId = $selectedAutomation?._id
|
||||||
$: {
|
$: store.actions.websocket.selectResource(automationId)
|
||||||
if (!$store.hasLock) {
|
|
||||||
$redirect("../data")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep URL and state in sync for selected screen ID
|
// Keep URL and state in sync for selected screen ID
|
||||||
const stopSyncing = syncURLToState({
|
const stopSyncing = syncURLToState({
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { Modal } from "@budibase/bbui"
|
import { Modal, keepOpen } from "@budibase/bbui"
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import { IntegrationTypes } from "constants/backend"
|
import { IntegrationTypes } from "constants/backend"
|
||||||
import GoogleAuthPrompt from "./GoogleAuthPrompt.svelte"
|
import GoogleAuthPrompt from "./GoogleAuthPrompt.svelte"
|
||||||
|
@ -75,8 +75,7 @@
|
||||||
notifications.error(`Error creating datasource: ${e.message}`)
|
notifications.error(`Error creating datasource: ${e.message}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent modal closing
|
return keepOpen
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { Modal, notifications } from "@budibase/bbui"
|
import { keepOpen, Modal, notifications } from "@budibase/bbui"
|
||||||
import { integrationForDatasource } from "stores/selectors"
|
import { integrationForDatasource } from "stores/selectors"
|
||||||
import { datasources, integrations } from "stores/backend"
|
import { datasources, integrations } from "stores/backend"
|
||||||
import DatasourceConfigEditor from "components/backend/Datasources/ConfigEditor/index.svelte"
|
import DatasourceConfigEditor from "components/backend/Datasources/ConfigEditor/index.svelte"
|
||||||
|
@ -23,8 +23,8 @@
|
||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notifications.error(err?.message ?? "Error saving datasource")
|
notifications.error(err?.message ?? "Error saving datasource")
|
||||||
// prevent the modal from closing
|
|
||||||
return false
|
return keepOpen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -49,7 +49,6 @@
|
||||||
return datasource.config.spreadsheetId
|
return datasource.config.spreadsheetId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$: subtitle = getSubtitle(datasource)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="button" on:click>
|
<div class="button" on:click>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import {
|
import {
|
||||||
|
keepOpen,
|
||||||
ModalContent,
|
ModalContent,
|
||||||
notifications,
|
notifications,
|
||||||
Body,
|
Body,
|
||||||
|
@ -70,10 +71,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
notifications.success("Imported successfully")
|
notifications.success("Imported successfully")
|
||||||
return true
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error importing queries")
|
notifications.error("Error importing queries")
|
||||||
return false
|
|
||||||
|
return keepOpen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -4,6 +4,10 @@
|
||||||
import { syncURLToState } from "helpers/urlStateSync"
|
import { syncURLToState } from "helpers/urlStateSync"
|
||||||
import * as routify from "@roxi/routify"
|
import * as routify from "@roxi/routify"
|
||||||
import { onDestroy } from "svelte"
|
import { onDestroy } from "svelte"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
|
||||||
|
$: datasourceId = $datasources.selectedDatasourceId
|
||||||
|
$: store.actions.websocket.selectResource(datasourceId)
|
||||||
|
|
||||||
const stopSyncing = syncURLToState({
|
const stopSyncing = syncURLToState({
|
||||||
urlParam: "datasourceId",
|
urlParam: "datasourceId",
|
||||||
|
|
|
@ -7,9 +7,11 @@
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { BUDIBASE_INTERNAL_DB_ID } from "constants/backend"
|
import { BUDIBASE_INTERNAL_DB_ID } from "constants/backend"
|
||||||
import { TableNames } from "constants"
|
import { TableNames } from "constants"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
|
|
||||||
|
$: store.actions.websocket.selectResource(BUDIBASE_INTERNAL_DB_ID)
|
||||||
$: internalTablesBySourceId = $tables.list.filter(
|
$: internalTablesBySourceId = $tables.list.filter(
|
||||||
table =>
|
table =>
|
||||||
table.type !== "external" &&
|
table.type !== "external" &&
|
||||||
|
|
|
@ -6,8 +6,11 @@
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import { DEFAULT_BB_DATASOURCE_ID } from "constants/backend"
|
import { DEFAULT_BB_DATASOURCE_ID } from "constants/backend"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
|
|
||||||
|
$: store.actions.websocket.selectResource(DEFAULT_BB_DATASOURCE_ID)
|
||||||
$: internalTablesBySourceId = $tables.list.filter(
|
$: internalTablesBySourceId = $tables.list.filter(
|
||||||
table =>
|
table =>
|
||||||
table.type !== "external" && table.sourceId === DEFAULT_BB_DATASOURCE_ID
|
table.type !== "external" && table.sourceId === DEFAULT_BB_DATASOURCE_ID
|
||||||
|
|
|
@ -7,14 +7,14 @@
|
||||||
} from "stores/backend"
|
} from "stores/backend"
|
||||||
|
|
||||||
import { hasData } from "stores/selectors"
|
import { hasData } from "stores/selectors"
|
||||||
import { Icon, notifications, Heading, Body } from "@budibase/bbui"
|
import { notifications, Body, Icon, AbsTooltip } from "@budibase/bbui"
|
||||||
import { params, goto } from "@roxi/routify"
|
import { params, goto } from "@roxi/routify"
|
||||||
import CreateExternalDatasourceModal from "./_components/CreateExternalDatasourceModal/index.svelte"
|
import CreateExternalDatasourceModal from "./_components/CreateExternalDatasourceModal/index.svelte"
|
||||||
import CreateInternalTableModal from "./_components/CreateInternalTableModal.svelte"
|
import CreateInternalTableModal from "./_components/CreateInternalTableModal.svelte"
|
||||||
import DatasourceOption from "./_components/DatasourceOption.svelte"
|
import DatasourceOption from "./_components/DatasourceOption.svelte"
|
||||||
import IntegrationIcon from "components/backend/DatasourceNavigator/IntegrationIcon.svelte"
|
import IntegrationIcon from "components/backend/DatasourceNavigator/IntegrationIcon.svelte"
|
||||||
|
import CreationPage from "components/common/CreationPage.svelte"
|
||||||
import ICONS from "components/backend/DatasourceNavigator/icons/index.js"
|
import ICONS from "components/backend/DatasourceNavigator/icons/index.js"
|
||||||
import FontAwesomeIcon from "components/common/FontAwesomeIcon.svelte"
|
|
||||||
|
|
||||||
let internalTableModal
|
let internalTableModal
|
||||||
let externalDatasourceModal
|
let externalDatasourceModal
|
||||||
|
@ -46,25 +46,16 @@
|
||||||
bind:this={externalDatasourceModal}
|
bind:this={externalDatasourceModal}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="page">
|
<CreationPage
|
||||||
<div class="closeButton">
|
showClose={hasData($datasources, $tables)}
|
||||||
{#if hasData($datasources, $tables)}
|
onClose={() => $goto("./table")}
|
||||||
<Icon hoverable name="Close" on:click={$goto("./table")} />
|
heading="Add new data source"
|
||||||
{/if}
|
>
|
||||||
</div>
|
|
||||||
<div class="heading">
|
|
||||||
<Heading weight="light">Add new data source</Heading>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="subHeading">
|
<div class="subHeading">
|
||||||
<Body>Get started with our Budibase DB</Body>
|
<Body>Get started with our Budibase DB</Body>
|
||||||
<div
|
<AbsTooltip text="Budibase DB is built with CouchDB">
|
||||||
role="tooltip"
|
<Icon name="Info" size="S" />
|
||||||
title="Budibase DB is built with CouchDB"
|
</AbsTooltip>
|
||||||
class="tooltip"
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon name="fa-solid fa-circle-info" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="options">
|
<div class="options">
|
||||||
|
@ -113,37 +104,19 @@
|
||||||
</DatasourceOption>
|
</DatasourceOption>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</CreationPage>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.page {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.closeButton {
|
|
||||||
height: 38px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: right;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heading {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subHeading {
|
.subHeading {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 24px;
|
margin-top: 12px;
|
||||||
|
margin-bottom: 36px;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
.subHeading :global(p) {
|
||||||
.tooltip {
|
color: var(--spectrum-global-color-gray-600) !important;
|
||||||
margin-left: 6px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.options {
|
.options {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
|
@ -3,6 +3,10 @@
|
||||||
import { syncURLToState } from "helpers/urlStateSync"
|
import { syncURLToState } from "helpers/urlStateSync"
|
||||||
import * as routify from "@roxi/routify"
|
import * as routify from "@roxi/routify"
|
||||||
import { onDestroy } from "svelte"
|
import { onDestroy } from "svelte"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
|
||||||
|
$: queryId = $queries.selectedQueryId
|
||||||
|
$: store.actions.websocket.selectResource(queryId)
|
||||||
|
|
||||||
const stopSyncing = syncURLToState({
|
const stopSyncing = syncURLToState({
|
||||||
urlParam: "queryId",
|
urlParam: "queryId",
|
||||||
|
|
|
@ -3,6 +3,10 @@
|
||||||
import { tables } from "stores/backend"
|
import { tables } from "stores/backend"
|
||||||
import * as routify from "@roxi/routify"
|
import * as routify from "@roxi/routify"
|
||||||
import { onDestroy } from "svelte"
|
import { onDestroy } from "svelte"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
|
||||||
|
$: tableId = $tables.selectedTableId
|
||||||
|
$: store.actions.websocket.selectResource(tableId)
|
||||||
|
|
||||||
const stopSyncing = syncURLToState({
|
const stopSyncing = syncURLToState({
|
||||||
urlParam: "tableId",
|
urlParam: "tableId",
|
||||||
|
|
|
@ -3,6 +3,10 @@
|
||||||
import { syncURLToState } from "helpers/urlStateSync"
|
import { syncURLToState } from "helpers/urlStateSync"
|
||||||
import * as routify from "@roxi/routify"
|
import * as routify from "@roxi/routify"
|
||||||
import { onDestroy } from "svelte"
|
import { onDestroy } from "svelte"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
|
||||||
|
$: viewName = $views.selectedViewName
|
||||||
|
$: store.actions.websocket.selectResource(viewName)
|
||||||
|
|
||||||
const stopSyncing = syncURLToState({
|
const stopSyncing = syncURLToState({
|
||||||
urlParam: "viewName",
|
urlParam: "viewName",
|
||||||
|
|
|
@ -272,6 +272,7 @@
|
||||||
{:else if error}
|
{:else if error}
|
||||||
<div class="center error">
|
<div class="center error">
|
||||||
<Layout justifyItems="center" gap="S">
|
<Layout justifyItems="center" gap="S">
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
{@html ErrorSVG}
|
{@html ErrorSVG}
|
||||||
<Heading size="L">App preview failed to load</Heading>
|
<Heading size="L">App preview failed to load</Heading>
|
||||||
<Body size="S">{error}</Body>
|
<Body size="S">{error}</Body>
|
||||||
|
|
|
@ -7,6 +7,9 @@
|
||||||
import { onDestroy } from "svelte"
|
import { onDestroy } from "svelte"
|
||||||
const { isActive, goto } = routify
|
const { isActive, goto } = routify
|
||||||
|
|
||||||
|
$: screenId = $store.selectedScreenId
|
||||||
|
$: store.actions.websocket.selectResource(screenId)
|
||||||
|
|
||||||
// Keep URL and state in sync for selected screen ID
|
// Keep URL and state in sync for selected screen ID
|
||||||
const stopSyncing = syncURLToState({
|
const stopSyncing = syncURLToState({
|
||||||
urlParam: "screenId",
|
urlParam: "screenId",
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
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 } from "@roxi/routify"
|
||||||
import { store, selectedScreen } from "builderStore"
|
import { store, selectedScreen, userSelectedResourceMap } 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 DNDPositionIndicator from "./DNDPositionIndicator.svelte"
|
import DNDPositionIndicator from "./DNDPositionIndicator.svelte"
|
||||||
|
@ -41,6 +41,7 @@
|
||||||
$store.selectedComponentId = $selectedScreen?.props._id
|
$store.selectedComponentId = $selectedScreen?.props._id
|
||||||
}}
|
}}
|
||||||
id={`component-${$selectedScreen?.props._id}`}
|
id={`component-${$selectedScreen?.props._id}`}
|
||||||
|
selectedBy={$userSelectedResourceMap[$selectedScreen?.props._id]}
|
||||||
>
|
>
|
||||||
<ScreenslotDropdownMenu component={$selectedScreen?.props} />
|
<ScreenslotDropdownMenu component={$selectedScreen?.props} />
|
||||||
</NavItem>
|
</NavItem>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { store } from "builderStore"
|
import { store, userSelectedResourceMap } from "builderStore"
|
||||||
import ComponentDropdownMenu from "./ComponentDropdownMenu.svelte"
|
import ComponentDropdownMenu from "./ComponentDropdownMenu.svelte"
|
||||||
import NavItem from "components/common/NavItem.svelte"
|
import NavItem from "components/common/NavItem.svelte"
|
||||||
import { capitalise } from "helpers"
|
import { capitalise } from "helpers"
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
let closedNodes = {}
|
let closedNodes = {}
|
||||||
|
|
||||||
$: currentScreen = get(selectedScreen)
|
|
||||||
|
|
||||||
$: filteredComponents = components?.filter(component => {
|
$: filteredComponents = components?.filter(component => {
|
||||||
return (
|
return (
|
||||||
!$store.componentToPaste?.isCut ||
|
!$store.componentToPaste?.isCut ||
|
||||||
|
@ -123,6 +121,7 @@
|
||||||
selected={$store.selectedComponentId === component._id}
|
selected={$store.selectedComponentId === component._id}
|
||||||
{opened}
|
{opened}
|
||||||
highlighted={isChildOfSelectedComponent(component)}
|
highlighted={isChildOfSelectedComponent(component)}
|
||||||
|
selectedBy={$userSelectedResourceMap[component._id]}
|
||||||
>
|
>
|
||||||
<ComponentDropdownMenu {component} />
|
<ComponentDropdownMenu {component} />
|
||||||
</NavItem>
|
</NavItem>
|
||||||
|
|
|
@ -7,6 +7,9 @@
|
||||||
import ComponentListPanel from "./_components/navigation/ComponentListPanel.svelte"
|
import ComponentListPanel from "./_components/navigation/ComponentListPanel.svelte"
|
||||||
import ComponentSettingsPanel from "./_components/settings/ComponentSettingsPanel.svelte"
|
import ComponentSettingsPanel from "./_components/settings/ComponentSettingsPanel.svelte"
|
||||||
|
|
||||||
|
$: componentId = $store.selectedComponentId
|
||||||
|
$: store.actions.websocket.selectResource(componentId)
|
||||||
|
|
||||||
const cleanUrl = url => {
|
const cleanUrl = url => {
|
||||||
// Strip trailing slashes
|
// Strip trailing slashes
|
||||||
if (url?.endsWith("/index")) {
|
if (url?.endsWith("/index")) {
|
||||||
|
|
|
@ -1,165 +0,0 @@
|
||||||
<script>
|
|
||||||
import { tables } from "stores/backend"
|
|
||||||
import { ModalContent, Body, Layout, Icon, Heading } from "@budibase/bbui"
|
|
||||||
import blankScreenPreview from "./blankScreenPreview.png"
|
|
||||||
import listScreenPreview from "./listScreenPreview.png"
|
|
||||||
|
|
||||||
export let onConfirm
|
|
||||||
export let onCancel
|
|
||||||
|
|
||||||
let listScreenModeKey = "autoCreate"
|
|
||||||
let blankScreenModeKey = "blankScreen"
|
|
||||||
|
|
||||||
let selectedScreenMode
|
|
||||||
|
|
||||||
const confirmScreenSelection = async () => {
|
|
||||||
await onConfirm(selectedScreenMode)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<ModalContent
|
|
||||||
title="Add screens"
|
|
||||||
confirmText="Continue"
|
|
||||||
cancelText="Cancel"
|
|
||||||
onConfirm={confirmScreenSelection}
|
|
||||||
{onCancel}
|
|
||||||
disabled={!selectedScreenMode}
|
|
||||||
size="M"
|
|
||||||
>
|
|
||||||
<Layout noPadding gap="S">
|
|
||||||
<div
|
|
||||||
class="screen-type item blankView"
|
|
||||||
class:selected={selectedScreenMode == blankScreenModeKey}
|
|
||||||
on:click={() => {
|
|
||||||
selectedScreenMode = blankScreenModeKey
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class="content screen-type-wrap">
|
|
||||||
<img
|
|
||||||
alt="blank screen preview"
|
|
||||||
class="preview"
|
|
||||||
src={blankScreenPreview}
|
|
||||||
/>
|
|
||||||
<div class="screen-type-text">
|
|
||||||
<Heading size="XS">Blank screen</Heading>
|
|
||||||
<Body size="S">Add an empty blank screen</Body>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style="color: var(--spectrum-global-color-green-600); float: right"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class={`checkmark-spacing ${
|
|
||||||
selectedScreenMode == blankScreenModeKey ? "visible" : ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Icon size="S" name="CheckmarkCircle" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="listViewTitle">
|
|
||||||
<Heading size="XS">Quickly create a screen from your data</Heading>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="screen-type item"
|
|
||||||
class:selected={selectedScreenMode == listScreenModeKey}
|
|
||||||
on:click={() => {
|
|
||||||
selectedScreenMode = listScreenModeKey
|
|
||||||
}}
|
|
||||||
class:disabled={!$tables.list.filter(table => table._id !== "ta_users")
|
|
||||||
.length}
|
|
||||||
>
|
|
||||||
<div class="content screen-type-wrap">
|
|
||||||
<img
|
|
||||||
alt="list screen preview"
|
|
||||||
class="preview"
|
|
||||||
src={listScreenPreview}
|
|
||||||
/>
|
|
||||||
<div class="screen-type-text">
|
|
||||||
<Heading size="XS">List view</Heading>
|
|
||||||
<Body size="S">
|
|
||||||
Create, edit and view your data in a list view screen with side
|
|
||||||
panel
|
|
||||||
</Body>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style="color: var(--spectrum-global-color-green-600); float: right"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class={`checkmark-spacing ${
|
|
||||||
selectedScreenMode == listScreenModeKey ? "visible" : ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Icon size="S" name="CheckmarkCircle" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
</ModalContent>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.screen-type-wrap {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.disabled {
|
|
||||||
opacity: 0.3;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.checkmark-spacing {
|
|
||||||
margin-right: var(--spacing-m);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
letter-spacing: 0px;
|
|
||||||
}
|
|
||||||
.item {
|
|
||||||
cursor: pointer;
|
|
||||||
grid-gap: var(--spectrum-alias-grid-margin-xsmall);
|
|
||||||
background: var(--spectrum-alias-background-color-secondary);
|
|
||||||
transition: 0.3s all;
|
|
||||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
|
||||||
border-radius: 4px;
|
|
||||||
border-width: 1px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.item:hover,
|
|
||||||
.selected {
|
|
||||||
background: var(--spectrum-alias-background-color-tertiary);
|
|
||||||
}
|
|
||||||
.screen-type-wrap .screen-type-text {
|
|
||||||
padding-left: var(--spectrum-alias-item-padding-xl);
|
|
||||||
}
|
|
||||||
.screen-type-wrap .screen-type-text :global(h1) {
|
|
||||||
padding-bottom: var(--spacing-xs);
|
|
||||||
}
|
|
||||||
.screen-type-wrap :global(.spectrum-Icon) {
|
|
||||||
min-width: var(--spectrum-icon-size-m);
|
|
||||||
}
|
|
||||||
.screen-type-wrap :global(.spectrum-Heading) {
|
|
||||||
padding-bottom: var(--spectrum-alias-item-padding-s);
|
|
||||||
}
|
|
||||||
.preview {
|
|
||||||
width: 140px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.listViewTitle {
|
|
||||||
margin-top: 35px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blankView {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.visible {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -12,8 +12,8 @@
|
||||||
$: role = $roles.find(role => role._id === roleId)
|
$: role = $roles.find(role => role._id === roleId)
|
||||||
$: tooltip =
|
$: tooltip =
|
||||||
roleId === Roles.PUBLIC
|
roleId === Roles.PUBLIC
|
||||||
? "This screen is open to the public"
|
? "Open to the public"
|
||||||
: `Requires at least ${role?.name} access`
|
: `Requires ${role?.name} access`
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -44,14 +44,14 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
width: 130px;
|
width: 200px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.tooltip :global(.spectrum-Tooltip) {
|
.tooltip :global(.spectrum-Tooltip) {
|
||||||
background: var(--color);
|
background: var(--color);
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
max-width: 130px;
|
max-width: 200px;
|
||||||
}
|
}
|
||||||
.tooltip :global(.spectrum-Tooltip-tip) {
|
.tooltip :global(.spectrum-Tooltip-tip) {
|
||||||
border-top-color: var(--color);
|
border-top-color: var(--color);
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
Helpers,
|
Helpers,
|
||||||
notifications,
|
notifications,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import ScreenDetailsModal from "./ScreenDetailsModal.svelte"
|
import ScreenDetailsModal from "components/design/ScreenDetailsModal.svelte"
|
||||||
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
||||||
import { makeComponentUnique } from "builderStore/componentUtils"
|
import { makeComponentUnique } from "builderStore/componentUtils"
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
<script>
|
<script>
|
||||||
import { Search, Layout, Select, Body, Button } from "@budibase/bbui"
|
import { Search, Layout, Select, Body, Button } from "@budibase/bbui"
|
||||||
import Panel from "components/design/Panel.svelte"
|
import Panel from "components/design/Panel.svelte"
|
||||||
|
import { goto } from "@roxi/routify"
|
||||||
import { roles } from "stores/backend"
|
import { roles } from "stores/backend"
|
||||||
import { store, sortedScreens } from "builderStore"
|
import { store, sortedScreens, userSelectedResourceMap } from "builderStore"
|
||||||
import NavItem from "components/common/NavItem.svelte"
|
import NavItem from "components/common/NavItem.svelte"
|
||||||
import ScreenDropdownMenu from "./ScreenDropdownMenu.svelte"
|
import ScreenDropdownMenu from "./ScreenDropdownMenu.svelte"
|
||||||
import ScreenWizard from "./ScreenWizard.svelte"
|
|
||||||
import RoleIndicator from "./RoleIndicator.svelte"
|
import RoleIndicator from "./RoleIndicator.svelte"
|
||||||
import { RoleUtils } from "@budibase/frontend-core"
|
import { RoleUtils } from "@budibase/frontend-core"
|
||||||
|
|
||||||
let searchString
|
let searchString
|
||||||
let accessRole = "all"
|
let accessRole = "all"
|
||||||
let showNewScreenModal
|
|
||||||
|
|
||||||
$: filteredScreens = getFilteredScreens(
|
$: filteredScreens = getFilteredScreens(
|
||||||
$sortedScreens,
|
$sortedScreens,
|
||||||
|
@ -31,7 +30,7 @@
|
||||||
|
|
||||||
<Panel title="Screens" borderRight>
|
<Panel title="Screens" borderRight>
|
||||||
<Layout paddingX="L" paddingY="XL" gap="S">
|
<Layout paddingX="L" paddingY="XL" gap="S">
|
||||||
<Button on:click={showNewScreenModal} cta>Add screen</Button>
|
<Button on:click={() => $goto("../../new")} cta>Add screen</Button>
|
||||||
<Search
|
<Search
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
value={searchString}
|
value={searchString}
|
||||||
|
@ -60,6 +59,7 @@
|
||||||
on:click={() => store.actions.screens.select(screen._id)}
|
on:click={() => store.actions.screens.select(screen._id)}
|
||||||
rightAlignIcon
|
rightAlignIcon
|
||||||
showTooltip
|
showTooltip
|
||||||
|
selectedBy={$userSelectedResourceMap[screen._id]}
|
||||||
>
|
>
|
||||||
<ScreenDropdownMenu screenId={screen._id} />
|
<ScreenDropdownMenu screenId={screen._id} />
|
||||||
<RoleIndicator slot="right" roleId={screen.routing.roleId} />
|
<RoleIndicator slot="right" roleId={screen.routing.roleId} />
|
||||||
|
@ -73,5 +73,3 @@
|
||||||
</Layout>
|
</Layout>
|
||||||
{/if}
|
{/if}
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
<ScreenWizard bind:showModal={showNewScreenModal} />
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue