Merge branch 'feature/backups-ui' of github.com:Budibase/budibase into feature/backups-ui
This commit is contained in:
commit
92131a7ccb
|
@ -24,6 +24,21 @@ http {
|
||||||
default "upgrade";
|
default "upgrade";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
upstream app-service {
|
||||||
|
server {{address}}:4001;
|
||||||
|
keepalive 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream worker-service {
|
||||||
|
server {{address}}:4002;
|
||||||
|
keepalive 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream builder {
|
||||||
|
server {{address}}:3000;
|
||||||
|
keepalive 32;
|
||||||
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 10000 default_server;
|
listen 10000 default_server;
|
||||||
server_name _;
|
server_name _;
|
||||||
|
@ -43,45 +58,78 @@ http {
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ ^/api/(system|admin|global)/ {
|
location ~ ^/api/(system|admin|global)/ {
|
||||||
proxy_pass http://{{ address }}:4002;
|
proxy_pass http://worker-service;
|
||||||
|
proxy_read_timeout 120s;
|
||||||
|
proxy_connect_timeout 120s;
|
||||||
|
proxy_send_timeout 120s;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection "";
|
||||||
}
|
}
|
||||||
|
|
||||||
location /api/ {
|
location /api/ {
|
||||||
proxy_read_timeout 120s;
|
proxy_read_timeout 120s;
|
||||||
proxy_connect_timeout 120s;
|
proxy_connect_timeout 120s;
|
||||||
proxy_send_timeout 120s;
|
proxy_send_timeout 120s;
|
||||||
proxy_pass http://{{ address }}:4001;
|
proxy_pass http://app-service;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection "";
|
||||||
}
|
}
|
||||||
|
|
||||||
location = / {
|
location = / {
|
||||||
proxy_pass http://{{ address }}:4001;
|
proxy_pass http://app-service;
|
||||||
|
proxy_read_timeout 120s;
|
||||||
|
proxy_connect_timeout 120s;
|
||||||
|
proxy_send_timeout 120s;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection "";
|
||||||
}
|
}
|
||||||
|
|
||||||
location /app_ {
|
location /app_ {
|
||||||
proxy_pass http://{{ address }}:4001;
|
proxy_pass http://app-service;
|
||||||
|
proxy_read_timeout 120s;
|
||||||
|
proxy_connect_timeout 120s;
|
||||||
|
proxy_send_timeout 120s;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection "";
|
||||||
}
|
}
|
||||||
|
|
||||||
location /app {
|
location /app {
|
||||||
proxy_pass http://{{ address }}:4001;
|
proxy_pass http://app-service;
|
||||||
|
proxy_read_timeout 120s;
|
||||||
|
proxy_connect_timeout 120s;
|
||||||
|
proxy_send_timeout 120s;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection "";
|
||||||
}
|
}
|
||||||
|
|
||||||
location /builder {
|
location /builder {
|
||||||
proxy_pass http://{{ address }}:3000;
|
proxy_pass http://builder;
|
||||||
|
proxy_read_timeout 120s;
|
||||||
|
proxy_connect_timeout 120s;
|
||||||
|
proxy_send_timeout 120s;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection "";
|
||||||
rewrite ^/builder(.*)$ /builder/$1 break;
|
rewrite ^/builder(.*)$ /builder/$1 break;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /builder/ {
|
location /builder/ {
|
||||||
proxy_pass http://{{ address }}:3000;
|
proxy_pass http://builder;
|
||||||
|
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Connection $connection_upgrade;
|
proxy_set_header Connection $connection_upgrade;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_read_timeout 120s;
|
||||||
|
proxy_connect_timeout 120s;
|
||||||
|
proxy_send_timeout 120s;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /vite/ {
|
location /vite/ {
|
||||||
proxy_pass http://{{ address }}:3000;
|
proxy_pass http://builder;
|
||||||
|
proxy_read_timeout 120s;
|
||||||
|
proxy_connect_timeout 120s;
|
||||||
|
proxy_send_timeout 120s;
|
||||||
rewrite ^/vite(.*)$ /$1 break;
|
rewrite ^/vite(.*)$ /$1 break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +139,7 @@ http {
|
||||||
proxy_set_header Connection 'upgrade';
|
proxy_set_header Connection 'upgrade';
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_cache_bypass $http_upgrade;
|
proxy_cache_bypass $http_upgrade;
|
||||||
proxy_pass http://{{ address }}:4001;
|
proxy_pass http://app-service;
|
||||||
}
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.0.30-alpha.7",
|
"version": "2.0.30-alpha.12",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/backend-core",
|
"name": "@budibase/backend-core",
|
||||||
"version": "2.0.30-alpha.7",
|
"version": "2.0.30-alpha.12",
|
||||||
"description": "Budibase backend core libraries used in server and worker",
|
"description": "Budibase backend core libraries used in server and worker",
|
||||||
"main": "dist/src/index.js",
|
"main": "dist/src/index.js",
|
||||||
"types": "dist/src/index.d.ts",
|
"types": "dist/src/index.d.ts",
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
"test:watch": "jest --watchAll"
|
"test:watch": "jest --watchAll"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/types": "2.0.30-alpha.7",
|
"@budibase/types": "2.0.30-alpha.12",
|
||||||
"@shopify/jest-koa-mocks": "5.0.1",
|
"@shopify/jest-koa-mocks": "5.0.1",
|
||||||
"@techpass/passport-openidconnect": "0.3.2",
|
"@techpass/passport-openidconnect": "0.3.2",
|
||||||
"aws-sdk": "2.1030.0",
|
"aws-sdk": "2.1030.0",
|
||||||
|
@ -64,7 +64,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chance": "1.1.3",
|
"@types/chance": "1.1.3",
|
||||||
"@types/ioredis": "4.28.10",
|
"@types/ioredis": "4.28.0",
|
||||||
"@types/jest": "27.5.1",
|
"@types/jest": "27.5.1",
|
||||||
"@types/koa": "2.0.52",
|
"@types/koa": "2.0.52",
|
||||||
"@types/lodash": "4.14.180",
|
"@types/lodash": "4.14.180",
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { AppBackup, AppBackupRevertEvent, Event } from "@budibase/types"
|
import { AppBackup, AppBackupRestoreEvent, Event } from "@budibase/types"
|
||||||
import { publishEvent } from "../events"
|
import { publishEvent } from "../events"
|
||||||
|
|
||||||
export async function appBackupRestored(backup: AppBackup) {
|
export async function appBackupRestored(backup: AppBackup) {
|
||||||
const properties: AppBackupRevertEvent = {
|
const properties: AppBackupRestoreEvent = {
|
||||||
appId: backup.appId,
|
appId: backup.appId,
|
||||||
backupName: backup.name!,
|
backupName: backup.name!,
|
||||||
backupCreatedAt: backup.timestamp,
|
backupCreatedAt: backup.timestamp,
|
||||||
|
|
|
@ -6,18 +6,18 @@ export type StalledFn = (job: Job) => Promise<void>
|
||||||
export function addListeners(
|
export function addListeners(
|
||||||
queue: Queue,
|
queue: Queue,
|
||||||
jobQueue: JobQueue,
|
jobQueue: JobQueue,
|
||||||
removeStalled?: StalledFn
|
removeStalledCb?: StalledFn
|
||||||
) {
|
) {
|
||||||
logging(queue, jobQueue)
|
logging(queue, jobQueue)
|
||||||
if (removeStalled) {
|
if (removeStalledCb) {
|
||||||
handleStalled(queue, removeStalled)
|
handleStalled(queue, removeStalledCb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleStalled(queue: Queue, removeStalled?: StalledFn) {
|
function handleStalled(queue: Queue, removeStalledCb?: StalledFn) {
|
||||||
queue.on("stalled", async (job: Job) => {
|
queue.on("stalled", async (job: Job) => {
|
||||||
if (removeStalled) {
|
if (removeStalledCb) {
|
||||||
await removeStalled(job)
|
await removeStalledCb(job)
|
||||||
} else if (job.opts.repeat) {
|
} else if (job.opts.repeat) {
|
||||||
const jobId = job.id
|
const jobId = job.id
|
||||||
const repeatJobs = await queue.getRepeatableJobs()
|
const repeatJobs = await queue.getRepeatableJobs()
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { JobQueue } from "./constants"
|
||||||
import InMemoryQueue from "./inMemoryQueue"
|
import InMemoryQueue from "./inMemoryQueue"
|
||||||
import BullQueue from "bull"
|
import BullQueue from "bull"
|
||||||
import { addListeners, StalledFn } from "./listeners"
|
import { addListeners, StalledFn } from "./listeners"
|
||||||
const { opts, redisProtocolUrl } = getRedisOptions()
|
const { opts: redisOpts, redisProtocolUrl } = getRedisOptions()
|
||||||
|
|
||||||
const CLEANUP_PERIOD_MS = 60 * 1000
|
const CLEANUP_PERIOD_MS = 60 * 1000
|
||||||
let QUEUES: BullQueue.Queue[] | InMemoryQueue[] = []
|
let QUEUES: BullQueue.Queue[] | InMemoryQueue[] = []
|
||||||
|
@ -18,16 +18,16 @@ async function cleanup() {
|
||||||
|
|
||||||
export function createQueue<T>(
|
export function createQueue<T>(
|
||||||
jobQueue: JobQueue,
|
jobQueue: JobQueue,
|
||||||
removeStalled?: StalledFn
|
opts: { removeStalledCb?: StalledFn } = {}
|
||||||
): BullQueue.Queue<T> {
|
): BullQueue.Queue<T> {
|
||||||
const queueConfig: any = redisProtocolUrl || { redis: opts }
|
const queueConfig: any = redisProtocolUrl || { redis: redisOpts }
|
||||||
let queue: any
|
let queue: any
|
||||||
if (!env.isTest()) {
|
if (!env.isTest()) {
|
||||||
queue = new BullQueue(jobQueue, queueConfig)
|
queue = new BullQueue(jobQueue, queueConfig)
|
||||||
} else {
|
} else {
|
||||||
queue = new InMemoryQueue(jobQueue, queueConfig)
|
queue = new InMemoryQueue(jobQueue, queueConfig)
|
||||||
}
|
}
|
||||||
addListeners(queue, jobQueue, removeStalled)
|
addListeners(queue, jobQueue, opts?.removeStalledCb)
|
||||||
QUEUES.push(queue)
|
QUEUES.push(queue)
|
||||||
if (!cleanupInterval) {
|
if (!cleanupInterval) {
|
||||||
cleanupInterval = setInterval(cleanup, CLEANUP_PERIOD_MS)
|
cleanupInterval = setInterval(cleanup, CLEANUP_PERIOD_MS)
|
||||||
|
|
|
@ -291,11 +291,6 @@
|
||||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||||
|
|
||||||
"@budibase/types@2.0.30-alpha.3":
|
|
||||||
version "2.0.30-alpha.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.0.30-alpha.3.tgz#cb55bcced75b711cc8a675284fbacaa8ebf1c0f2"
|
|
||||||
integrity sha512-rHeFVuNbSSE4fMnX6uyrM2r47m+neqFXlVNOkhHU9i7KoIcIZbEYInU8CjUFR2da3ruST9ajXjJ5UenX2+MnTg==
|
|
||||||
|
|
||||||
"@hapi/hoek@^9.0.0":
|
"@hapi/hoek@^9.0.0":
|
||||||
version "9.3.0"
|
version "9.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb"
|
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb"
|
||||||
|
@ -698,14 +693,6 @@
|
||||||
"@types/connect" "*"
|
"@types/connect" "*"
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/bull@^3.15.9":
|
|
||||||
version "3.15.9"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/bull/-/bull-3.15.9.tgz#e10e0901ec3762bff85716b3c580277960751c93"
|
|
||||||
integrity sha512-MPUcyPPQauAmynoO3ezHAmCOhbB0pWmYyijr/5ctaCqhbKWsjW0YCod38ZcLzUBprosfZ9dPqfYIcfdKjk7RNQ==
|
|
||||||
dependencies:
|
|
||||||
"@types/ioredis" "*"
|
|
||||||
"@types/redis" "^2.8.0"
|
|
||||||
|
|
||||||
"@types/chance@1.1.3":
|
"@types/chance@1.1.3":
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/chance/-/chance-1.1.3.tgz#d19fe9391288d60fdccd87632bfc9ab2b4523fea"
|
resolved "https://registry.yarnpkg.com/@types/chance/-/chance-1.1.3.tgz#d19fe9391288d60fdccd87632bfc9ab2b4523fea"
|
||||||
|
@ -776,10 +763,10 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-1.8.2.tgz#7315b4c4c54f82d13fa61c228ec5c2ea5cc9e0e1"
|
resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-1.8.2.tgz#7315b4c4c54f82d13fa61c228ec5c2ea5cc9e0e1"
|
||||||
integrity sha512-EqX+YQxINb+MeXaIqYDASb6U6FCHbWjkj4a1CKDBks3d/QiB2+PqBLyO72vLDgAO1wUI4O+9gweRcQK11bTL/w==
|
integrity sha512-EqX+YQxINb+MeXaIqYDASb6U6FCHbWjkj4a1CKDBks3d/QiB2+PqBLyO72vLDgAO1wUI4O+9gweRcQK11bTL/w==
|
||||||
|
|
||||||
"@types/ioredis@*", "@types/ioredis@^4.28.10":
|
"@types/ioredis@4.28.0":
|
||||||
version "4.28.10"
|
version "4.28.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/ioredis/-/ioredis-4.28.10.tgz#40ceb157a4141088d1394bb87c98ed09a75a06ff"
|
resolved "https://registry.yarnpkg.com/@types/ioredis/-/ioredis-4.28.0.tgz#609b2ea0d91231df2dd7f67dd77436bc72584911"
|
||||||
integrity sha512-69LyhUgrXdgcNDv7ogs1qXZomnfOEnSmrmMFqKgt1XMJxmoOSG/u3wYy13yACIfKuMJ8IhKgHafDO3sx19zVQQ==
|
integrity sha512-HSA/JQivJgV0e+353gvgu6WVoWvGRe0HyHOnAN2AvbVIhUlJBhNnnkP8gEEokrDWrxywrBkwo8NuDZ6TVPL9XA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
@ -1547,7 +1534,7 @@ buffer@^5.5.0, buffer@^5.6.0:
|
||||||
base64-js "^1.3.1"
|
base64-js "^1.3.1"
|
||||||
ieee754 "^1.1.13"
|
ieee754 "^1.1.13"
|
||||||
|
|
||||||
bull@^4.10.1:
|
bull@4.10.1:
|
||||||
version "4.10.1"
|
version "4.10.1"
|
||||||
resolved "https://registry.yarnpkg.com/bull/-/bull-4.10.1.tgz#f14974b6089358b62b495a2cbf838aadc098e43f"
|
resolved "https://registry.yarnpkg.com/bull/-/bull-4.10.1.tgz#f14974b6089358b62b495a2cbf838aadc098e43f"
|
||||||
integrity sha512-Fp21tRPb2EaZPVfmM+ONZKVz2RA+to+zGgaTLyCKt3JMSU8OOBqK8143OQrnGuGpsyE5G+9FevFAGhdZZfQP2g==
|
integrity sha512-Fp21tRPb2EaZPVfmM+ONZKVz2RA+to+zGgaTLyCKt3JMSU8OOBqK8143OQrnGuGpsyE5G+9FevFAGhdZZfQP2g==
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/bbui",
|
"name": "@budibase/bbui",
|
||||||
"description": "A UI solution used in the different Budibase projects.",
|
"description": "A UI solution used in the different Budibase projects.",
|
||||||
"version": "2.0.30-alpha.7",
|
"version": "2.0.30-alpha.12",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"module": "dist/bbui.es.js",
|
"module": "dist/bbui.es.js",
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
|
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
|
||||||
"@budibase/string-templates": "2.0.30-alpha.7",
|
"@budibase/string-templates": "2.0.30-alpha.12",
|
||||||
"@spectrum-css/actionbutton": "^1.0.1",
|
"@spectrum-css/actionbutton": "^1.0.1",
|
||||||
"@spectrum-css/actiongroup": "^1.0.1",
|
"@spectrum-css/actiongroup": "^1.0.1",
|
||||||
"@spectrum-css/avatar": "^3.0.2",
|
"@spectrum-css/avatar": "^3.0.2",
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
export default function clickOutside(element, callbackFunction) {
|
export default function clickOutside(element, callbackFunction) {
|
||||||
function onClick(event) {
|
function onClick(event) {
|
||||||
if (!element.contains(event.target)) {
|
if (!element.contains(event.target)) {
|
||||||
callbackFunction()
|
callbackFunction(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.body.addEventListener("mousedown", onClick, true)
|
document.body.addEventListener("click", onClick, true)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
update(newCallbackFunction) {
|
update(newCallbackFunction) {
|
||||||
callbackFunction = newCallbackFunction
|
callbackFunction = newCallbackFunction
|
||||||
},
|
},
|
||||||
destroy() {
|
destroy() {
|
||||||
document.body.removeEventListener("mousedown", onClick, true)
|
document.body.removeEventListener("click", onClick, true)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,6 +119,13 @@
|
||||||
|
|
||||||
return "var(--spectrum-global-color-static-gray-900)"
|
return "var(--spectrum-global-color-static-gray-900)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleOutsideClick = event => {
|
||||||
|
if (open) {
|
||||||
|
event.stopPropagation()
|
||||||
|
open = false
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -131,7 +138,7 @@
|
||||||
</div>
|
</div>
|
||||||
{#if open}
|
{#if open}
|
||||||
<div
|
<div
|
||||||
use:clickOutside={() => (open = false)}
|
use:clickOutside={handleOutsideClick}
|
||||||
transition:fly={{ y: -20, duration: 200 }}
|
transition:fly={{ y: -20, duration: 200 }}
|
||||||
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open"
|
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open"
|
||||||
class:spectrum-Popover--align-right={alignRight}
|
class:spectrum-Popover--align-right={alignRight}
|
||||||
|
|
|
@ -85,7 +85,7 @@
|
||||||
class:is-invalid={!!error}
|
class:is-invalid={!!error}
|
||||||
class:is-open={open}
|
class:is-open={open}
|
||||||
aria-haspopup="listbox"
|
aria-haspopup="listbox"
|
||||||
on:mousedown={onClick}
|
on:click={onClick}
|
||||||
>
|
>
|
||||||
{#if fieldIcon}
|
{#if fieldIcon}
|
||||||
<span class="option-extra">
|
<span class="option-extra">
|
||||||
|
|
|
@ -87,6 +87,20 @@
|
||||||
updateValue(event.target.value)
|
updateValue(event.target.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handlePrimaryOutsideClick = event => {
|
||||||
|
if (primaryOpen) {
|
||||||
|
event.stopPropagation()
|
||||||
|
primaryOpen = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSecondaryOutsideClick = event => {
|
||||||
|
if (secondaryOpen) {
|
||||||
|
event.stopPropagation()
|
||||||
|
secondaryOpen = false
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -148,7 +162,7 @@
|
||||||
</div>
|
</div>
|
||||||
{#if primaryOpen}
|
{#if primaryOpen}
|
||||||
<div
|
<div
|
||||||
use:clickOutside={() => (primaryOpen = false)}
|
use:clickOutside={handlePrimaryOutsideClick}
|
||||||
transition:fly|local={{ y: -20, duration: 200 }}
|
transition:fly|local={{ y: -20, duration: 200 }}
|
||||||
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open"
|
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open"
|
||||||
class:auto-width={autoWidth}
|
class:auto-width={autoWidth}
|
||||||
|
@ -256,7 +270,7 @@
|
||||||
{disabled}
|
{disabled}
|
||||||
class:is-open={secondaryOpen}
|
class:is-open={secondaryOpen}
|
||||||
aria-haspopup="listbox"
|
aria-haspopup="listbox"
|
||||||
on:mousedown={onClickSecondary}
|
on:click={onClickSecondary}
|
||||||
>
|
>
|
||||||
{#if secondaryFieldIcon}
|
{#if secondaryFieldIcon}
|
||||||
<span class="option-left">
|
<span class="option-left">
|
||||||
|
@ -281,7 +295,7 @@
|
||||||
</button>
|
</button>
|
||||||
{#if secondaryOpen}
|
{#if secondaryOpen}
|
||||||
<div
|
<div
|
||||||
use:clickOutside={() => (secondaryOpen = false)}
|
use:clickOutside={handleSecondaryOutsideClick}
|
||||||
transition:fly|local={{ y: -20, duration: 200 }}
|
transition:fly|local={{ y: -20, duration: 200 }}
|
||||||
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open"
|
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open"
|
||||||
style="width: 30%"
|
style="width: 30%"
|
||||||
|
|
|
@ -50,6 +50,13 @@
|
||||||
dispatch("change", value)
|
dispatch("change", value)
|
||||||
open = false
|
open = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleOutsideClick = event => {
|
||||||
|
if (open) {
|
||||||
|
event.stopPropagation()
|
||||||
|
open = false
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -64,7 +71,7 @@
|
||||||
</div>
|
</div>
|
||||||
{#if open}
|
{#if open}
|
||||||
<div
|
<div
|
||||||
use:clickOutside={() => (open = false)}
|
use:clickOutside={handleOutsideClick}
|
||||||
transition:fly={{ y: -20, duration: 200 }}
|
transition:fly={{ y: -20, duration: 200 }}
|
||||||
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open"
|
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open"
|
||||||
class:spectrum-Popover--align-right={alignRight}
|
class:spectrum-Popover--align-right={alignRight}
|
||||||
|
|
|
@ -33,6 +33,13 @@
|
||||||
open = false
|
open = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleOutsideClick = e => {
|
||||||
|
if (open) {
|
||||||
|
e.stopPropagation()
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let open = null
|
let open = null
|
||||||
|
|
||||||
function handleEscape(e) {
|
function handleEscape(e) {
|
||||||
|
@ -47,7 +54,7 @@
|
||||||
<div
|
<div
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
use:positionDropdown={{ anchor, align, maxWidth }}
|
use:positionDropdown={{ anchor, align, maxWidth }}
|
||||||
use:clickOutside={hide}
|
use:clickOutside={handleOutsideClick}
|
||||||
on:keydown={handleEscape}
|
on:keydown={handleEscape}
|
||||||
class={"spectrum-Popover is-open " + (tooltipClasses || "")}
|
class={"spectrum-Popover is-open " + (tooltipClasses || "")}
|
||||||
role="presentation"
|
role="presentation"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "2.0.30-alpha.7",
|
"version": "2.0.30-alpha.12",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -71,10 +71,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "2.0.30-alpha.7",
|
"@budibase/bbui": "2.0.30-alpha.12",
|
||||||
"@budibase/client": "2.0.30-alpha.7",
|
"@budibase/client": "2.0.30-alpha.12",
|
||||||
"@budibase/frontend-core": "2.0.30-alpha.7",
|
"@budibase/frontend-core": "2.0.30-alpha.12",
|
||||||
"@budibase/string-templates": "2.0.30-alpha.7",
|
"@budibase/string-templates": "2.0.30-alpha.12",
|
||||||
"@sentry/browser": "5.19.1",
|
"@sentry/browser": "5.19.1",
|
||||||
"@spectrum-css/page": "^3.0.1",
|
"@spectrum-css/page": "^3.0.1",
|
||||||
"@spectrum-css/vars": "^3.0.1",
|
"@spectrum-css/vars": "^3.0.1",
|
||||||
|
|
|
@ -451,7 +451,7 @@ export const getFrontendStore = () => {
|
||||||
...extras,
|
...extras,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
create: async (componentName, presetProps) => {
|
create: async (componentName, presetProps, parent, index) => {
|
||||||
const state = get(store)
|
const state = get(store)
|
||||||
const componentInstance = store.actions.components.createInstance(
|
const componentInstance = store.actions.components.createInstance(
|
||||||
componentName,
|
componentName,
|
||||||
|
@ -461,48 +461,62 @@ export const getFrontendStore = () => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patch selected screen
|
// Insert in position if specified
|
||||||
await store.actions.screens.patch(screen => {
|
if (parent && index != null) {
|
||||||
// Find the selected component
|
await store.actions.screens.patch(screen => {
|
||||||
const currentComponent = findComponent(
|
let parentComponent = findComponent(screen.props, parent)
|
||||||
screen.props,
|
if (!parentComponent._children?.length) {
|
||||||
state.selectedComponentId
|
parentComponent._children = [componentInstance]
|
||||||
)
|
|
||||||
if (!currentComponent) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find parent node to attach this component to
|
|
||||||
let parentComponent
|
|
||||||
if (currentComponent) {
|
|
||||||
// Use selected component as parent if one is selected
|
|
||||||
const definition = store.actions.components.getDefinition(
|
|
||||||
currentComponent._component
|
|
||||||
)
|
|
||||||
if (definition?.hasChildren) {
|
|
||||||
// Use selected component if it allows children
|
|
||||||
parentComponent = currentComponent
|
|
||||||
} else {
|
} else {
|
||||||
// Otherwise we need to use the parent of this component
|
parentComponent._children.splice(index, 0, componentInstance)
|
||||||
parentComponent = findComponentParent(
|
|
||||||
screen.props,
|
|
||||||
currentComponent._id
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
})
|
||||||
// Use screen or layout if no component is selected
|
}
|
||||||
parentComponent = screen.props
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attach new component
|
// Otherwise we work out where this component should be inserted
|
||||||
if (!parentComponent) {
|
else {
|
||||||
return false
|
await store.actions.screens.patch(screen => {
|
||||||
}
|
// Find the selected component
|
||||||
if (!parentComponent._children) {
|
const currentComponent = findComponent(
|
||||||
parentComponent._children = []
|
screen.props,
|
||||||
}
|
state.selectedComponentId
|
||||||
parentComponent._children.push(componentInstance)
|
)
|
||||||
})
|
if (!currentComponent) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find parent node to attach this component to
|
||||||
|
let parentComponent
|
||||||
|
if (currentComponent) {
|
||||||
|
// Use selected component as parent if one is selected
|
||||||
|
const definition = store.actions.components.getDefinition(
|
||||||
|
currentComponent._component
|
||||||
|
)
|
||||||
|
if (definition?.hasChildren) {
|
||||||
|
// Use selected component if it allows children
|
||||||
|
parentComponent = currentComponent
|
||||||
|
} else {
|
||||||
|
// Otherwise we need to use the parent of this component
|
||||||
|
parentComponent = findComponentParent(
|
||||||
|
screen.props,
|
||||||
|
currentComponent._id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Use screen or layout if no component is selected
|
||||||
|
parentComponent = screen.props
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach new component
|
||||||
|
if (!parentComponent) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!parentComponent._children) {
|
||||||
|
parentComponent._children = []
|
||||||
|
}
|
||||||
|
parentComponent._children.push(componentInstance)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Select new component
|
// Select new component
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
|
@ -990,6 +1004,19 @@ export const getFrontendStore = () => {
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
dnd: {
|
||||||
|
start: component => {
|
||||||
|
store.actions.preview.sendEvent("dragging-new-component", {
|
||||||
|
dragging: true,
|
||||||
|
component,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
stop: () => {
|
||||||
|
store.actions.preview.sendEvent("dragging-new-component", {
|
||||||
|
dragging: false,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return store
|
return store
|
||||||
|
|
|
@ -1,16 +1,31 @@
|
||||||
<script>
|
<script>
|
||||||
import { Body } from "@budibase/bbui"
|
import { Label, Body } from "@budibase/bbui"
|
||||||
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
|
|
||||||
|
export let parameters
|
||||||
|
export let bindings = []
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<Body size="S">Navigate To screen, or leave blank.</Body>
|
||||||
|
<br />
|
||||||
<div class="root">
|
<div class="root">
|
||||||
<Body size="S">This action doesn't require any additional settings.</Body>
|
<Label small>Screen</Label>
|
||||||
<Body size="S">
|
<DrawerBindableInput
|
||||||
This action won't do anything if there isn't a screen modal open.
|
title="Destination URL"
|
||||||
</Body>
|
placeholder="/screen"
|
||||||
|
value={parameters.url}
|
||||||
|
on:change={value => (parameters.url = value.detail)}
|
||||||
|
{bindings}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.root {
|
.root {
|
||||||
|
display: grid;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
max-width: 400px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -10,6 +10,7 @@ export const syncURLToState = options => {
|
||||||
fallbackUrl,
|
fallbackUrl,
|
||||||
store,
|
store,
|
||||||
routify,
|
routify,
|
||||||
|
beforeNavigate,
|
||||||
} = options || {}
|
} = options || {}
|
||||||
if (
|
if (
|
||||||
!urlParam ||
|
!urlParam ||
|
||||||
|
@ -41,6 +42,15 @@ export const syncURLToState = options => {
|
||||||
|
|
||||||
// Navigate to a certain URL
|
// Navigate to a certain URL
|
||||||
const gotoUrl = (url, params) => {
|
const gotoUrl = (url, params) => {
|
||||||
|
if (beforeNavigate) {
|
||||||
|
const res = beforeNavigate(url, params)
|
||||||
|
if (res?.url) {
|
||||||
|
url = res.url
|
||||||
|
}
|
||||||
|
if (res?.params) {
|
||||||
|
params = res.params
|
||||||
|
}
|
||||||
|
}
|
||||||
log("Navigating to", url, "with params", params)
|
log("Navigating to", url, "with params", params)
|
||||||
cachedGoto(url, params)
|
cachedGoto(url, params)
|
||||||
}
|
}
|
||||||
|
|
|
@ -213,6 +213,9 @@
|
||||||
await store.actions.components.handleEjectBlock(id, definition)
|
await store.actions.components.handleEjectBlock(id, definition)
|
||||||
} else if (type === "reload-plugin") {
|
} else if (type === "reload-plugin") {
|
||||||
await store.actions.components.refreshDefinitions()
|
await store.actions.components.refreshDefinitions()
|
||||||
|
} else if (type === "drop-new-component") {
|
||||||
|
const { component, parent, index } = data
|
||||||
|
await store.actions.components.create(component, null, parent, index)
|
||||||
} else {
|
} else {
|
||||||
console.warn(`Client sent unknown event type: ${type}`)
|
console.warn(`Client sent unknown event type: ${type}`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,9 @@
|
||||||
const compDef = store.actions.components.getDefinition(
|
const compDef = store.actions.components.getDefinition(
|
||||||
$dndStore.source?._component
|
$dndStore.source?._component
|
||||||
)
|
)
|
||||||
|
if (!compDef) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const compTypeName = compDef.name.toLowerCase()
|
const compTypeName = compDef.name.toLowerCase()
|
||||||
const path = findComponentPath(currentScreen.props, component._id)
|
const path = findComponentPath(currentScreen.props, component._id)
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,18 @@
|
||||||
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"
|
||||||
|
|
||||||
|
const cleanUrl = url => {
|
||||||
|
// Strip trailing slashes
|
||||||
|
if (url?.endsWith("/index")) {
|
||||||
|
url = url.replace("/index", "")
|
||||||
|
}
|
||||||
|
// Hide new component panel whenever component ID changes
|
||||||
|
if (url?.endsWith("/new")) {
|
||||||
|
url = url.replace("/new", "")
|
||||||
|
}
|
||||||
|
return { url }
|
||||||
|
}
|
||||||
|
|
||||||
// Keep URL and state in sync for selected component ID
|
// Keep URL and state in sync for selected component ID
|
||||||
const stopSyncing = syncURLToState({
|
const stopSyncing = syncURLToState({
|
||||||
urlParam: "componentId",
|
urlParam: "componentId",
|
||||||
|
@ -15,6 +27,7 @@
|
||||||
fallbackUrl: "../",
|
fallbackUrl: "../",
|
||||||
store,
|
store,
|
||||||
routify,
|
routify,
|
||||||
|
beforeNavigate: cleanUrl,
|
||||||
})
|
})
|
||||||
|
|
||||||
onDestroy(stopSyncing)
|
onDestroy(stopSyncing)
|
||||||
|
|
|
@ -169,6 +169,14 @@
|
||||||
window.removeEventListener("keydown", handleKeyDown)
|
window.removeEventListener("keydown", handleKeyDown)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const onDragStart = component => {
|
||||||
|
store.actions.dnd.start(component)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDragEnd = () => {
|
||||||
|
store.actions.dnd.stop()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container" transition:fly|local={{ x: 260, duration: 300 }}>
|
<div class="container" transition:fly|local={{ x: 260, duration: 300 }}>
|
||||||
|
@ -206,6 +214,9 @@
|
||||||
<div class="category-label">{category.name}</div>
|
<div class="category-label">{category.name}</div>
|
||||||
{#each category.children as component}
|
{#each category.children as component}
|
||||||
<div
|
<div
|
||||||
|
draggable="true"
|
||||||
|
on:dragstart={() => onDragStart(component.component)}
|
||||||
|
on:dragend={onDragEnd}
|
||||||
data-cy={`component-${component.name}`}
|
data-cy={`component-${component.name}`}
|
||||||
class="component"
|
class="component"
|
||||||
class:selected={selectedIndex ===
|
class:selected={selectedIndex ===
|
||||||
|
@ -229,8 +240,11 @@
|
||||||
<Layout noPadding gap="XS">
|
<Layout noPadding gap="XS">
|
||||||
{#each blocks as block}
|
{#each blocks as block}
|
||||||
<div
|
<div
|
||||||
|
draggable="true"
|
||||||
class="component"
|
class="component"
|
||||||
on:click={() => addComponent(block.component)}
|
on:click={() => addComponent(block.component)}
|
||||||
|
on:dragstart={() => onDragStart(block.component)}
|
||||||
|
on:dragend={onDragEnd}
|
||||||
>
|
>
|
||||||
<Icon name={block.icon} />
|
<Icon name={block.icon} />
|
||||||
<Body size="XS">{block.name}</Body>
|
<Body size="XS">{block.name}</Body>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "2.0.30-alpha.7",
|
"version": "2.0.30-alpha.12",
|
||||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -26,9 +26,9 @@
|
||||||
"outputPath": "build"
|
"outputPath": "build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/backend-core": "2.0.30-alpha.7",
|
"@budibase/backend-core": "2.0.30-alpha.12",
|
||||||
"@budibase/string-templates": "2.0.30-alpha.7",
|
"@budibase/string-templates": "2.0.30-alpha.12",
|
||||||
"@budibase/types": "2.0.30-alpha.7",
|
"@budibase/types": "2.0.30-alpha.12",
|
||||||
"axios": "0.21.2",
|
"axios": "0.21.2",
|
||||||
"chalk": "4.1.0",
|
"chalk": "4.1.0",
|
||||||
"cli-progress": "3.11.2",
|
"cli-progress": "3.11.2",
|
||||||
|
|
|
@ -85,6 +85,10 @@
|
||||||
"icon": "Selection",
|
"icon": "Selection",
|
||||||
"hasChildren": true,
|
"hasChildren": true,
|
||||||
"showSettingsBar": true,
|
"showSettingsBar": true,
|
||||||
|
"size": {
|
||||||
|
"width": 400,
|
||||||
|
"height": 100
|
||||||
|
},
|
||||||
"styles": [
|
"styles": [
|
||||||
"padding",
|
"padding",
|
||||||
"size",
|
"size",
|
||||||
|
@ -255,6 +259,10 @@
|
||||||
"section"
|
"section"
|
||||||
],
|
],
|
||||||
"showEmptyState": false,
|
"showEmptyState": false,
|
||||||
|
"size": {
|
||||||
|
"width": 400,
|
||||||
|
"height": 100
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "section",
|
"type": "section",
|
||||||
|
@ -276,6 +284,10 @@
|
||||||
"icon": "Button",
|
"icon": "Button",
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"showSettingsBar": true,
|
"showSettingsBar": true,
|
||||||
|
"size": {
|
||||||
|
"width": 105,
|
||||||
|
"height": 35
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -368,6 +380,10 @@
|
||||||
"illegalChildren": [
|
"illegalChildren": [
|
||||||
"section"
|
"section"
|
||||||
],
|
],
|
||||||
|
"size": {
|
||||||
|
"width": 400,
|
||||||
|
"height": 10
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
|
@ -405,6 +421,10 @@
|
||||||
],
|
],
|
||||||
"hasChildren": true,
|
"hasChildren": true,
|
||||||
"showSettingsBar": true,
|
"showSettingsBar": true,
|
||||||
|
"size": {
|
||||||
|
"width": 400,
|
||||||
|
"height": 100
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "dataProvider",
|
"type": "dataProvider",
|
||||||
|
@ -584,6 +604,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"card": {
|
"card": {
|
||||||
|
"deprecated": true,
|
||||||
"name": "Vertical Card",
|
"name": "Vertical Card",
|
||||||
"description": "A basic card component that can contain content and actions.",
|
"description": "A basic card component that can contain content and actions.",
|
||||||
"icon": "ViewColumn",
|
"icon": "ViewColumn",
|
||||||
|
@ -664,6 +685,10 @@
|
||||||
],
|
],
|
||||||
"showSettingsBar": true,
|
"showSettingsBar": true,
|
||||||
"editable": true,
|
"editable": true,
|
||||||
|
"size": {
|
||||||
|
"width": 400,
|
||||||
|
"height": 30
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -786,6 +811,10 @@
|
||||||
],
|
],
|
||||||
"showSettingsBar": true,
|
"showSettingsBar": true,
|
||||||
"editable": true,
|
"editable": true,
|
||||||
|
"size": {
|
||||||
|
"width": 400,
|
||||||
|
"height": 40
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -903,6 +932,10 @@
|
||||||
"name": "Tag",
|
"name": "Tag",
|
||||||
"icon": "Label",
|
"icon": "Label",
|
||||||
"showSettingsBar": true,
|
"showSettingsBar": true,
|
||||||
|
"size": {
|
||||||
|
"width": 100,
|
||||||
|
"height": 25
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -954,12 +987,13 @@
|
||||||
"name": "Image",
|
"name": "Image",
|
||||||
"description": "A basic component for displaying images",
|
"description": "A basic component for displaying images",
|
||||||
"icon": "Image",
|
"icon": "Image",
|
||||||
"illegalChildren": [
|
|
||||||
"section"
|
|
||||||
],
|
|
||||||
"styles": [
|
"styles": [
|
||||||
"size"
|
"size"
|
||||||
],
|
],
|
||||||
|
"size": {
|
||||||
|
"width": 400,
|
||||||
|
"height": 300
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -976,9 +1010,10 @@
|
||||||
"styles": [
|
"styles": [
|
||||||
"size"
|
"size"
|
||||||
],
|
],
|
||||||
"illegalChildren": [
|
"size": {
|
||||||
"section"
|
"width": 400,
|
||||||
],
|
"height": 300
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -1036,9 +1071,10 @@
|
||||||
"name": "Icon",
|
"name": "Icon",
|
||||||
"description": "A basic component for displaying icons",
|
"description": "A basic component for displaying icons",
|
||||||
"icon": "Shapes",
|
"icon": "Shapes",
|
||||||
"illegalChildren": [
|
"size": {
|
||||||
"section"
|
"width": 25,
|
||||||
],
|
"height": 25
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "icon",
|
"type": "icon",
|
||||||
|
@ -1155,9 +1191,10 @@
|
||||||
"icon": "Link",
|
"icon": "Link",
|
||||||
"showSettingsBar": true,
|
"showSettingsBar": true,
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"illegalChildren": [
|
"size": {
|
||||||
"section"
|
"width": 200,
|
||||||
],
|
"height": 30
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -1267,12 +1304,10 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"cardhorizontal": {
|
"cardhorizontal": {
|
||||||
|
"deprecated": true,
|
||||||
"name": "Horizontal Card",
|
"name": "Horizontal Card",
|
||||||
"description": "A basic card component that can contain content and actions.",
|
"description": "A basic card component that can contain content and actions.",
|
||||||
"icon": "ViewRow",
|
"icon": "ViewRow",
|
||||||
"illegalChildren": [
|
|
||||||
"section"
|
|
||||||
],
|
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -1363,27 +1398,31 @@
|
||||||
"name": "Stat Card",
|
"name": "Stat Card",
|
||||||
"description": "A card component for displaying numbers.",
|
"description": "A card component for displaying numbers.",
|
||||||
"icon": "Card",
|
"icon": "Card",
|
||||||
"illegalChildren": [
|
"size": {
|
||||||
"section"
|
"width": 260,
|
||||||
],
|
"height": 143
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"label": "Title",
|
"label": "Title",
|
||||||
"key": "title",
|
"key": "title",
|
||||||
"placeholder": "Total Revenue"
|
"placeholder": "Total Revenue",
|
||||||
|
"defaultValue": "Title"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"label": "Value",
|
"label": "Value",
|
||||||
"key": "value",
|
"key": "value",
|
||||||
"placeholder": "$1,981,983"
|
"placeholder": "$1,981,983",
|
||||||
|
"defaultValue": "Value"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"label": "Label",
|
"label": "Label",
|
||||||
"key": "label",
|
"key": "label",
|
||||||
"placeholder": "Stripe"
|
"placeholder": "Stripe",
|
||||||
|
"defaultValue": "Label"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1391,12 +1430,13 @@
|
||||||
"name": "Embed",
|
"name": "Embed",
|
||||||
"icon": "Code",
|
"icon": "Code",
|
||||||
"description": "Embed content from 3rd party sources",
|
"description": "Embed content from 3rd party sources",
|
||||||
"illegalChildren": [
|
|
||||||
"section"
|
|
||||||
],
|
|
||||||
"styles": [
|
"styles": [
|
||||||
"size"
|
"size"
|
||||||
],
|
],
|
||||||
|
"size": {
|
||||||
|
"width": 400,
|
||||||
|
"height": 100
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -1410,9 +1450,10 @@
|
||||||
"name": "Bar Chart",
|
"name": "Bar Chart",
|
||||||
"description": "Bar chart",
|
"description": "Bar chart",
|
||||||
"icon": "GraphBarVertical",
|
"icon": "GraphBarVertical",
|
||||||
"illegalChildren": [
|
"size": {
|
||||||
"section"
|
"width": 600,
|
||||||
],
|
"height": 400
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -1571,9 +1612,10 @@
|
||||||
"name": "Line Chart",
|
"name": "Line Chart",
|
||||||
"description": "Line chart",
|
"description": "Line chart",
|
||||||
"icon": "GraphTrend",
|
"icon": "GraphTrend",
|
||||||
"illegalChildren": [
|
"size": {
|
||||||
"section"
|
"width": 600,
|
||||||
],
|
"height": 400
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -1731,9 +1773,10 @@
|
||||||
"name": "Area Chart",
|
"name": "Area Chart",
|
||||||
"description": "Line chart",
|
"description": "Line chart",
|
||||||
"icon": "GraphAreaStacked",
|
"icon": "GraphAreaStacked",
|
||||||
"illegalChildren": [
|
"size": {
|
||||||
"section"
|
"width": 600,
|
||||||
],
|
"height": 400
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -1903,9 +1946,10 @@
|
||||||
"name": "Pie Chart",
|
"name": "Pie Chart",
|
||||||
"description": "Pie chart",
|
"description": "Pie chart",
|
||||||
"icon": "GraphPie",
|
"icon": "GraphPie",
|
||||||
"illegalChildren": [
|
"size": {
|
||||||
"section"
|
"width": 600,
|
||||||
],
|
"height": 400
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -2031,9 +2075,10 @@
|
||||||
"name": "Donut Chart",
|
"name": "Donut Chart",
|
||||||
"description": "Donut chart",
|
"description": "Donut chart",
|
||||||
"icon": "GraphDonut",
|
"icon": "GraphDonut",
|
||||||
"illegalChildren": [
|
"size": {
|
||||||
"section"
|
"width": 600,
|
||||||
],
|
"height": 400
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -2159,9 +2204,10 @@
|
||||||
"name": "Candlestick Chart",
|
"name": "Candlestick Chart",
|
||||||
"description": "Candlestick chart",
|
"description": "Candlestick chart",
|
||||||
"icon": "GraphBarVerticalStacked",
|
"icon": "GraphBarVerticalStacked",
|
||||||
"illegalChildren": [
|
"size": {
|
||||||
"section"
|
"width": 600,
|
||||||
],
|
"height": 400
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -2266,6 +2312,10 @@
|
||||||
"styles": [
|
"styles": [
|
||||||
"size"
|
"size"
|
||||||
],
|
],
|
||||||
|
"size": {
|
||||||
|
"width": 400,
|
||||||
|
"height": 400
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
|
@ -2352,6 +2402,10 @@
|
||||||
"styles": [
|
"styles": [
|
||||||
"size"
|
"size"
|
||||||
],
|
],
|
||||||
|
"size": {
|
||||||
|
"width": 400,
|
||||||
|
"height": 400
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
@ -2372,6 +2426,10 @@
|
||||||
"size"
|
"size"
|
||||||
],
|
],
|
||||||
"hasChildren": true,
|
"hasChildren": true,
|
||||||
|
"size": {
|
||||||
|
"width": 400,
|
||||||
|
"height": 400
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
|
@ -2398,13 +2456,14 @@
|
||||||
"stringfield": {
|
"stringfield": {
|
||||||
"name": "Text Field",
|
"name": "Text Field",
|
||||||
"icon": "Text",
|
"icon": "Text",
|
||||||
"illegalChildren": [
|
|
||||||
"section"
|
|
||||||
],
|
|
||||||
"styles": [
|
"styles": [
|
||||||
"size"
|
"size"
|
||||||
],
|
],
|
||||||
"editable": true,
|
"editable": true,
|
||||||
|
"size": {
|
||||||
|
"width": 400,
|
||||||
|
"height": 50
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "field/string",
|
"type": "field/string",
|
||||||
|
@ -2492,9 +2551,10 @@
|
||||||
"size"
|
"size"
|
||||||
],
|
],
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"illegalChildren": [
|
"size": {
|
||||||
"section"
|
"width": 400,
|
||||||
],
|
"height": 50
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "field/number",
|
"type": "field/number",
|
||||||
|
@ -2548,9 +2608,10 @@
|
||||||
"size"
|
"size"
|
||||||
],
|
],
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"illegalChildren": [
|
"size": {
|
||||||
"section"
|
"width": 400,
|
||||||
],
|
"height": 50
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "field/string",
|
"type": "field/string",
|
||||||
|
@ -2604,9 +2665,10 @@
|
||||||
"size"
|
"size"
|
||||||
],
|
],
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"illegalChildren": [
|
"size": {
|
||||||
"section"
|
"width": 400,
|
||||||
],
|
"height": 50
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "field/options",
|
"type": "field/options",
|
||||||
|
@ -2771,9 +2833,10 @@
|
||||||
"size"
|
"size"
|
||||||
],
|
],
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"illegalChildren": [
|
"size": {
|
||||||
"section"
|
"width": 400,
|
||||||
],
|
"height": 50
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "field/array",
|
"type": "field/array",
|
||||||
|
@ -2929,9 +2992,10 @@
|
||||||
"name": "Checkbox",
|
"name": "Checkbox",
|
||||||
"icon": "SelectBox",
|
"icon": "SelectBox",
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"illegalChildren": [
|
"size": {
|
||||||
"section"
|
"width": 400,
|
||||||
],
|
"height": 50
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "field/boolean",
|
"type": "field/boolean",
|
||||||
|
@ -3009,6 +3073,10 @@
|
||||||
"size"
|
"size"
|
||||||
],
|
],
|
||||||
"editable": true,
|
"editable": true,
|
||||||
|
"size": {
|
||||||
|
"width": 400,
|
||||||
|
"height": 150
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "field/longform",
|
"type": "field/longform",
|
||||||
|
@ -3084,9 +3152,10 @@
|
||||||
"size"
|
"size"
|
||||||
],
|
],
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"illegalChildren": [
|
"size": {
|
||||||
"section"
|
"width": 400,
|
||||||
],
|
"height": 50
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "field/datetime",
|
"type": "field/datetime",
|
||||||
|
@ -3163,9 +3232,10 @@
|
||||||
"styles": [
|
"styles": [
|
||||||
"size"
|
"size"
|
||||||
],
|
],
|
||||||
"illegalChildren": [
|
"size": {
|
||||||
"section"
|
"width": 400,
|
||||||
],
|
"height": 50
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "field/barcode/qr",
|
"type": "field/barcode/qr",
|
||||||
|
@ -3214,29 +3284,27 @@
|
||||||
"size"
|
"size"
|
||||||
],
|
],
|
||||||
"draggable": false,
|
"draggable": false,
|
||||||
"illegalChildren": [
|
"size": {
|
||||||
"section"
|
"width": 400,
|
||||||
],
|
"height": 320
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "dataProvider",
|
"type": "dataProvider",
|
||||||
"label": "Provider",
|
"label": "Provider",
|
||||||
"key": "dataProvider",
|
"key": "dataProvider"
|
||||||
"required": true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"label": "Latitude Key",
|
"label": "Latitude Key",
|
||||||
"key": "latitudeKey",
|
"key": "latitudeKey",
|
||||||
"dependsOn": "dataProvider",
|
"dependsOn": "dataProvider"
|
||||||
"required": true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"label": "Longitude Key",
|
"label": "Longitude Key",
|
||||||
"key": "longitudeKey",
|
"key": "longitudeKey",
|
||||||
"dependsOn": "dataProvider",
|
"dependsOn": "dataProvider"
|
||||||
"required": true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
|
@ -3330,9 +3398,10 @@
|
||||||
"size"
|
"size"
|
||||||
],
|
],
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"illegalChildren": [
|
"size": {
|
||||||
"section"
|
"width": 400,
|
||||||
],
|
"height": 200
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "field/attachment",
|
"type": "field/attachment",
|
||||||
|
@ -3387,9 +3456,10 @@
|
||||||
"size"
|
"size"
|
||||||
],
|
],
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"illegalChildren": [
|
"size": {
|
||||||
"section"
|
"width": 400,
|
||||||
],
|
"height": 50
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "field/link",
|
"type": "field/link",
|
||||||
|
@ -3449,6 +3519,10 @@
|
||||||
"size"
|
"size"
|
||||||
],
|
],
|
||||||
"editable": true,
|
"editable": true,
|
||||||
|
"size": {
|
||||||
|
"width": 400,
|
||||||
|
"height": 100
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "field/json",
|
"type": "field/json",
|
||||||
|
@ -3497,6 +3571,10 @@
|
||||||
"size"
|
"size"
|
||||||
],
|
],
|
||||||
"editable": true,
|
"editable": true,
|
||||||
|
"size": {
|
||||||
|
"width": 400,
|
||||||
|
"height": 200
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "field/attachment",
|
"type": "field/attachment",
|
||||||
|
@ -3559,6 +3637,10 @@
|
||||||
"actions": [
|
"actions": [
|
||||||
"RefreshDatasource"
|
"RefreshDatasource"
|
||||||
],
|
],
|
||||||
|
"size": {
|
||||||
|
"width": 400,
|
||||||
|
"height": 100
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "dataSource",
|
"type": "dataSource",
|
||||||
|
@ -3639,6 +3721,10 @@
|
||||||
],
|
],
|
||||||
"hasChildren": true,
|
"hasChildren": true,
|
||||||
"showEmptyState": false,
|
"showEmptyState": false,
|
||||||
|
"size": {
|
||||||
|
"width": 600,
|
||||||
|
"height": 400
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "dataProvider",
|
"type": "dataProvider",
|
||||||
|
@ -3737,6 +3823,10 @@
|
||||||
"size"
|
"size"
|
||||||
],
|
],
|
||||||
"hasChildren": false,
|
"hasChildren": false,
|
||||||
|
"size": {
|
||||||
|
"width": 200,
|
||||||
|
"height": 50
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "dataProvider",
|
"type": "dataProvider",
|
||||||
|
@ -3773,21 +3863,28 @@
|
||||||
"styles": [
|
"styles": [
|
||||||
"size"
|
"size"
|
||||||
],
|
],
|
||||||
|
"size": {
|
||||||
|
"width": 300,
|
||||||
|
"height": 120
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"key": "title",
|
"key": "title",
|
||||||
"label": "Title"
|
"label": "Title",
|
||||||
|
"defaultValue": "Title"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"key": "subtitle",
|
"key": "subtitle",
|
||||||
"label": "Subtitle"
|
"label": "Subtitle",
|
||||||
|
"defaultValue": "Subtitle"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"key": "description",
|
"key": "description",
|
||||||
"label": "Description"
|
"label": "Description",
|
||||||
|
"defaultValue": "Description"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -3831,6 +3928,10 @@
|
||||||
"name": "Dynamic Filter",
|
"name": "Dynamic Filter",
|
||||||
"icon": "Filter",
|
"icon": "Filter",
|
||||||
"showSettingsBar": true,
|
"showSettingsBar": true,
|
||||||
|
"size": {
|
||||||
|
"width": 100,
|
||||||
|
"height": 35
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "dataProvider",
|
"type": "dataProvider",
|
||||||
|
@ -3878,6 +3979,10 @@
|
||||||
"styles": [
|
"styles": [
|
||||||
"size"
|
"size"
|
||||||
],
|
],
|
||||||
|
"size": {
|
||||||
|
"width": 600,
|
||||||
|
"height": 400
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -4043,6 +4148,10 @@
|
||||||
"styles": [
|
"styles": [
|
||||||
"size"
|
"size"
|
||||||
],
|
],
|
||||||
|
"size": {
|
||||||
|
"width": 600,
|
||||||
|
"height": 400
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -4101,19 +4210,22 @@
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"key": "cardTitle",
|
"key": "cardTitle",
|
||||||
"label": "Title",
|
"label": "Title",
|
||||||
"nested": true
|
"nested": true,
|
||||||
|
"defaultValue": "Title"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"key": "cardSubtitle",
|
"key": "cardSubtitle",
|
||||||
"label": "Subtitle",
|
"label": "Subtitle",
|
||||||
"nested": true
|
"nested": true,
|
||||||
|
"defaultValue": "Subtitle"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"key": "cardDescription",
|
"key": "cardDescription",
|
||||||
"label": "Description",
|
"label": "Description",
|
||||||
"nested": true
|
"nested": true,
|
||||||
|
"defaultValue": "Description"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -4215,6 +4327,10 @@
|
||||||
],
|
],
|
||||||
"hasChildren": true,
|
"hasChildren": true,
|
||||||
"showSettingsBar": true,
|
"showSettingsBar": true,
|
||||||
|
"size": {
|
||||||
|
"width": 400,
|
||||||
|
"height": 100
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "dataSource",
|
"type": "dataSource",
|
||||||
|
@ -4437,6 +4553,10 @@
|
||||||
"styles": [
|
"styles": [
|
||||||
"size"
|
"size"
|
||||||
],
|
],
|
||||||
|
"size": {
|
||||||
|
"width": 400,
|
||||||
|
"height": 100
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -4454,6 +4574,10 @@
|
||||||
],
|
],
|
||||||
"block": true,
|
"block": true,
|
||||||
"info": "Form blocks are only compatible with internal or SQL tables",
|
"info": "Form blocks are only compatible with internal or SQL tables",
|
||||||
|
"size": {
|
||||||
|
"width": 400,
|
||||||
|
"height": 400
|
||||||
|
},
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "2.0.30-alpha.7",
|
"version": "2.0.30-alpha.12",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"module": "dist/budibase-client.js",
|
"module": "dist/budibase-client.js",
|
||||||
"main": "dist/budibase-client.js",
|
"main": "dist/budibase-client.js",
|
||||||
|
@ -19,9 +19,9 @@
|
||||||
"dev:builder": "rollup -cw"
|
"dev:builder": "rollup -cw"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "2.0.30-alpha.7",
|
"@budibase/bbui": "2.0.30-alpha.12",
|
||||||
"@budibase/frontend-core": "2.0.30-alpha.7",
|
"@budibase/frontend-core": "2.0.30-alpha.12",
|
||||||
"@budibase/string-templates": "2.0.30-alpha.7",
|
"@budibase/string-templates": "2.0.30-alpha.12",
|
||||||
"@spectrum-css/button": "^3.0.3",
|
"@spectrum-css/button": "^3.0.3",
|
||||||
"@spectrum-css/card": "^3.0.3",
|
"@spectrum-css/card": "^3.0.3",
|
||||||
"@spectrum-css/divider": "^1.0.3",
|
"@spectrum-css/divider": "^1.0.3",
|
||||||
|
|
|
@ -67,6 +67,9 @@
|
||||||
// any depth
|
// any depth
|
||||||
id: $component.id,
|
id: $component.id,
|
||||||
|
|
||||||
|
// Name can be used down the tree in placeholders
|
||||||
|
name: $component.name,
|
||||||
|
|
||||||
// We register block components with their raw props so that we can eject
|
// We register block components with their raw props so that we can eject
|
||||||
// blocks later on
|
// blocks later on
|
||||||
registerComponent: registerBlockComponent,
|
registerComponent: registerBlockComponent,
|
||||||
|
|
|
@ -16,7 +16,14 @@
|
||||||
propsAreSame,
|
propsAreSame,
|
||||||
getSettingsDefinition,
|
getSettingsDefinition,
|
||||||
} from "utils/componentProps"
|
} from "utils/componentProps"
|
||||||
import { builderStore, devToolsStore, componentStore, appStore } from "stores"
|
import {
|
||||||
|
builderStore,
|
||||||
|
devToolsStore,
|
||||||
|
componentStore,
|
||||||
|
appStore,
|
||||||
|
dndIsDragging,
|
||||||
|
dndComponentPath,
|
||||||
|
} from "stores"
|
||||||
import { Helpers } from "@budibase/bbui"
|
import { Helpers } from "@budibase/bbui"
|
||||||
import { getActiveConditions, reduceConditionActions } from "utils/conditions"
|
import { getActiveConditions, reduceConditionActions } from "utils/conditions"
|
||||||
import Placeholder from "components/app/Placeholder.svelte"
|
import Placeholder from "components/app/Placeholder.svelte"
|
||||||
|
@ -27,6 +34,7 @@
|
||||||
export let isLayout = false
|
export let isLayout = false
|
||||||
export let isScreen = false
|
export let isScreen = false
|
||||||
export let isBlock = false
|
export let isBlock = false
|
||||||
|
export let parent = null
|
||||||
|
|
||||||
// Get parent contexts
|
// Get parent contexts
|
||||||
const context = getContext("context")
|
const context = getContext("context")
|
||||||
|
@ -97,6 +105,7 @@
|
||||||
$builderStore.inBuilder && $builderStore.selectedComponentId === id
|
$builderStore.inBuilder && $builderStore.selectedComponentId === id
|
||||||
$: inSelectedPath = $componentStore.selectedComponentPath?.includes(id)
|
$: inSelectedPath = $componentStore.selectedComponentPath?.includes(id)
|
||||||
$: inDragPath = inSelectedPath && $builderStore.editMode
|
$: inDragPath = inSelectedPath && $builderStore.editMode
|
||||||
|
$: inDndPath = $dndComponentPath?.includes(id)
|
||||||
|
|
||||||
// Derive definition properties which can all be optional, so need to be
|
// Derive definition properties which can all be optional, so need to be
|
||||||
// coerced to booleans
|
// coerced to booleans
|
||||||
|
@ -108,7 +117,7 @@
|
||||||
// Interactive components can be selected, dragged and highlighted inside
|
// Interactive components can be selected, dragged and highlighted inside
|
||||||
// the builder preview
|
// the builder preview
|
||||||
$: builderInteractive =
|
$: builderInteractive =
|
||||||
$builderStore.inBuilder && insideScreenslot && !isBlock
|
$builderStore.inBuilder && insideScreenslot && !isBlock && !instance.static
|
||||||
$: devToolsInteractive = $devToolsStore.allowSelection && !isBlock
|
$: devToolsInteractive = $devToolsStore.allowSelection && !isBlock
|
||||||
$: interactive = builderInteractive || devToolsInteractive
|
$: interactive = builderInteractive || devToolsInteractive
|
||||||
$: editing = editable && selected && $builderStore.editMode
|
$: editing = editable && selected && $builderStore.editMode
|
||||||
|
@ -118,7 +127,7 @@
|
||||||
!isLayout &&
|
!isLayout &&
|
||||||
!isScreen &&
|
!isScreen &&
|
||||||
definition?.draggable !== false
|
definition?.draggable !== false
|
||||||
$: droppable = interactive && !isLayout && !isScreen
|
$: droppable = interactive
|
||||||
$: builderHidden =
|
$: builderHidden =
|
||||||
$builderStore.inBuilder && $builderStore.hiddenComponentIds?.includes(id)
|
$builderStore.inBuilder && $builderStore.hiddenComponentIds?.includes(id)
|
||||||
|
|
||||||
|
@ -126,8 +135,9 @@
|
||||||
// Empty states can be shown for these components, but can be disabled
|
// Empty states can be shown for these components, but can be disabled
|
||||||
// in the component manifest.
|
// in the component manifest.
|
||||||
$: empty =
|
$: empty =
|
||||||
(interactive && !children.length && hasChildren) ||
|
!isBlock &&
|
||||||
hasMissingRequiredSettings
|
((interactive && !children.length && hasChildren) ||
|
||||||
|
hasMissingRequiredSettings)
|
||||||
$: emptyState = empty && showEmptyState
|
$: emptyState = empty && showEmptyState
|
||||||
|
|
||||||
// Enrich component settings
|
// Enrich component settings
|
||||||
|
@ -149,6 +159,12 @@
|
||||||
// Scroll the selected element into view
|
// Scroll the selected element into view
|
||||||
$: selected && scrollIntoView()
|
$: selected && scrollIntoView()
|
||||||
|
|
||||||
|
// When dragging and dropping, pad components to allow dropping between
|
||||||
|
// nested layers. Only reset this when dragging stops.
|
||||||
|
let pad = false
|
||||||
|
$: pad = pad || (interactive && hasChildren && inDndPath)
|
||||||
|
$: $dndIsDragging, (pad = false)
|
||||||
|
|
||||||
// Update component context
|
// Update component context
|
||||||
$: store.set({
|
$: store.set({
|
||||||
id,
|
id,
|
||||||
|
@ -405,6 +421,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const scrollIntoView = () => {
|
const scrollIntoView = () => {
|
||||||
|
// Don't scroll into view if we selected this component because we were
|
||||||
|
// starting dragging on it
|
||||||
|
if (get(dndIsDragging)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const node = document.getElementsByClassName(id)?.[0]?.children[0]
|
const node = document.getElementsByClassName(id)?.[0]?.children[0]
|
||||||
if (!node) {
|
if (!node) {
|
||||||
return
|
return
|
||||||
|
@ -452,17 +473,20 @@
|
||||||
class:empty
|
class:empty
|
||||||
class:interactive
|
class:interactive
|
||||||
class:editing
|
class:editing
|
||||||
|
class:pad
|
||||||
|
class:parent={hasChildren}
|
||||||
class:block={isBlock}
|
class:block={isBlock}
|
||||||
data-id={id}
|
data-id={id}
|
||||||
data-name={name}
|
data-name={name}
|
||||||
data-icon={icon}
|
data-icon={icon}
|
||||||
|
data-parent={parent}
|
||||||
>
|
>
|
||||||
<svelte:component this={constructor} bind:this={ref} {...initialSettings}>
|
<svelte:component this={constructor} bind:this={ref} {...initialSettings}>
|
||||||
{#if hasMissingRequiredSettings}
|
{#if hasMissingRequiredSettings}
|
||||||
<ComponentPlaceholder />
|
<ComponentPlaceholder />
|
||||||
{:else if children.length}
|
{:else if children.length}
|
||||||
{#each children as child (child._id)}
|
{#each children as child (child._id)}
|
||||||
<svelte:self instance={child} />
|
<svelte:self instance={child} parent={id} />
|
||||||
{/each}
|
{/each}
|
||||||
{:else if emptyState}
|
{:else if emptyState}
|
||||||
{#if isScreen}
|
{#if isScreen}
|
||||||
|
@ -481,16 +505,14 @@
|
||||||
.component {
|
.component {
|
||||||
display: contents;
|
display: contents;
|
||||||
}
|
}
|
||||||
|
.component.pad :global(> *) {
|
||||||
.interactive :global(*:hover) {
|
padding: var(--spacing-l) !important;
|
||||||
cursor: pointer;
|
gap: var(--spacing-l) !important;
|
||||||
|
border: 2px dashed var(--spectrum-global-color-gray-400) !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
transition: padding 260ms ease-out, border 260ms ease-out;
|
||||||
}
|
}
|
||||||
|
.interactive :global(*) {
|
||||||
.draggable :global(*:hover) {
|
cursor: default;
|
||||||
cursor: grab;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editing :global(*:hover) {
|
|
||||||
cursor: auto;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const onRouteLoading = ({ detail }) => {
|
const onRouteLoading = ({ detail }) => {
|
||||||
|
routeStore.actions.setRouteParams(detail.params || {})
|
||||||
routeStore.actions.setActiveRoute(detail.route)
|
routeStore.actions.setActiveRoute(detail.route)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,6 @@
|
||||||
|
|
||||||
const context = getContext("context")
|
const context = getContext("context")
|
||||||
|
|
||||||
// Keep route params up to date
|
|
||||||
$: routeStore.actions.setRouteParams(params || {})
|
|
||||||
|
|
||||||
// Get the screen definition for the current route
|
// Get the screen definition for the current route
|
||||||
$: screenDefinition = $screenStore.activeScreen?.props
|
$: screenDefinition = $screenStore.activeScreen?.props
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,14 @@
|
||||||
|
|
||||||
const { builderStore } = getContext("sdk")
|
const { builderStore } = getContext("sdk")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
|
const block = getContext("block")
|
||||||
|
|
||||||
export let text
|
export let text
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $builderStore.inBuilder}
|
{#if $builderStore.inBuilder}
|
||||||
<div>
|
<div>
|
||||||
{text || $component.name || "Placeholder"}
|
{text || block?.name || $component.name || "Placeholder"}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,9 @@
|
||||||
},
|
},
|
||||||
[MessageTypes.CLOSE_SCREEN_MODAL]: () => {
|
[MessageTypes.CLOSE_SCREEN_MODAL]: () => {
|
||||||
peekStore.actions.hidePeek()
|
peekStore.actions.hidePeek()
|
||||||
|
if (message.data?.url) {
|
||||||
|
routeStore.actions.navigate(message.data.url)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[MessageTypes.INVALIDATE_DATASOURCE]: () => {
|
[MessageTypes.INVALIDATE_DATASOURCE]: () => {
|
||||||
proxyInvalidation(message.data)
|
proxyInvalidation(message.data)
|
||||||
|
|
|
@ -1,201 +1,298 @@
|
||||||
<script context="module">
|
|
||||||
export const Sides = {
|
|
||||||
Top: "Top",
|
|
||||||
Right: "Right",
|
|
||||||
Bottom: "Bottom",
|
|
||||||
Left: "Left",
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { onMount, onDestroy } from "svelte"
|
import { onMount, onDestroy } from "svelte"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import IndicatorSet from "./IndicatorSet.svelte"
|
import IndicatorSet from "./IndicatorSet.svelte"
|
||||||
import DNDPositionIndicator from "./DNDPositionIndicator.svelte"
|
import {
|
||||||
import { builderStore } from "stores"
|
builderStore,
|
||||||
|
screenStore,
|
||||||
|
dndStore,
|
||||||
|
dndParent,
|
||||||
|
dndIsDragging,
|
||||||
|
} from "stores"
|
||||||
|
import DNDPlaceholderOverlay from "./DNDPlaceholderOverlay.svelte"
|
||||||
|
import { Utils } from "@budibase/frontend-core"
|
||||||
|
import { findComponentById } from "utils/components.js"
|
||||||
|
import { DNDPlaceholderID } from "constants"
|
||||||
|
|
||||||
let dragInfo
|
const ThrottleRate = 130
|
||||||
let dropInfo
|
|
||||||
|
|
||||||
const getEdges = (bounds, mousePoint) => {
|
// Cache some dnd store state as local variables as it massively helps
|
||||||
const { width, height, top, left } = bounds
|
// performance. It lets us avoid calling svelte getters on every DOM action.
|
||||||
return {
|
$: source = $dndStore.source
|
||||||
[Sides.Top]: [mousePoint[0], top],
|
$: target = $dndStore.target
|
||||||
[Sides.Right]: [left + width, mousePoint[1]],
|
$: drop = $dndStore.drop
|
||||||
[Sides.Bottom]: [mousePoint[0], top + height],
|
|
||||||
[Sides.Left]: [left, mousePoint[1]],
|
// Util to get the inner DOM node by a component ID
|
||||||
|
const getDOMNode = id => {
|
||||||
|
const component = document.getElementsByClassName(id)[0]
|
||||||
|
return [...component.children][0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Util to calculate the variance of a set of data
|
||||||
|
const variance = arr => {
|
||||||
|
const mean = arr.reduce((a, b) => a + b, 0) / arr.length
|
||||||
|
let squareSum = 0
|
||||||
|
arr.forEach(value => {
|
||||||
|
const delta = value - mean
|
||||||
|
squareSum += delta * delta
|
||||||
|
})
|
||||||
|
return squareSum / arr.length
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback when drag stops (whether dropped or not)
|
||||||
|
const stopDragging = () => {
|
||||||
|
// Reset listener
|
||||||
|
if (source?.id) {
|
||||||
|
const component = document.getElementsByClassName(source?.id)[0]
|
||||||
|
if (component) {
|
||||||
|
component.removeEventListener("dragend", stopDragging)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const calculatePointDelta = (point1, point2) => {
|
// Reset state
|
||||||
const deltaX = Math.abs(point1[0] - point2[0])
|
dndStore.actions.reset()
|
||||||
const deltaY = Math.abs(point1[1] - point2[1])
|
|
||||||
return Math.sqrt(deltaX * deltaX + deltaY * deltaY)
|
|
||||||
}
|
|
||||||
|
|
||||||
const getDOMNodeForComponent = component => {
|
|
||||||
const parent = component.closest(".component")
|
|
||||||
const children = Array.from(parent.children)
|
|
||||||
return children[0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback when initially starting a drag on a draggable component
|
// Callback when initially starting a drag on a draggable component
|
||||||
const onDragStart = e => {
|
const onDragStart = e => {
|
||||||
const parent = e.target.closest(".component")
|
const component = e.target.closest(".component")
|
||||||
if (!parent?.classList.contains("draggable")) {
|
if (!component?.classList.contains("draggable")) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update state
|
// Hide drag ghost image
|
||||||
dragInfo = {
|
e.dataTransfer.setDragImage(new Image(), 0, 0)
|
||||||
target: parent.dataset.id,
|
|
||||||
parent: parent.dataset.parent,
|
|
||||||
}
|
|
||||||
builderStore.actions.selectComponent(dragInfo.target)
|
|
||||||
builderStore.actions.setDragging(true)
|
|
||||||
|
|
||||||
// Highlight being dragged by setting opacity
|
// Add event handler to clear all drag state when dragging ends
|
||||||
const child = getDOMNodeForComponent(e.target)
|
component.addEventListener("dragend", stopDragging)
|
||||||
if (child) {
|
|
||||||
child.style.opacity = "0.5"
|
// Update state
|
||||||
}
|
const id = component.dataset.id
|
||||||
|
const parentId = component.dataset.parent
|
||||||
|
const parent = findComponentById(
|
||||||
|
get(screenStore).activeScreen?.props,
|
||||||
|
parentId
|
||||||
|
)
|
||||||
|
const index = parent._children.findIndex(
|
||||||
|
x => x._id === component.dataset.id
|
||||||
|
)
|
||||||
|
dndStore.actions.startDraggingExistingComponent({
|
||||||
|
id,
|
||||||
|
bounds: component.children[0].getBoundingClientRect(),
|
||||||
|
parent: parentId,
|
||||||
|
index,
|
||||||
|
})
|
||||||
|
builderStore.actions.selectComponent(id)
|
||||||
|
|
||||||
|
// Set initial drop info to show placeholder exactly where the dragged
|
||||||
|
// component is.
|
||||||
|
// Execute this asynchronously to prevent bugs caused by updating state in
|
||||||
|
// the same handler as selecting a new component (which causes a client
|
||||||
|
// re-initialisation).
|
||||||
|
setTimeout(() => {
|
||||||
|
dndStore.actions.updateDrop({
|
||||||
|
parent: parentId,
|
||||||
|
index,
|
||||||
|
})
|
||||||
|
}, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback when drag stops (whether dropped or not)
|
// Core logic for handling drop events and determining where to render the
|
||||||
const onDragEnd = e => {
|
// drop target placeholder
|
||||||
// Reset opacity style
|
const processEvent = (mouseX, mouseY) => {
|
||||||
if (dragInfo) {
|
if (!target) {
|
||||||
const child = getDOMNodeForComponent(e.target)
|
return null
|
||||||
if (child) {
|
}
|
||||||
child.style.opacity = ""
|
let { id, parent, node, acceptsChildren, empty } = target
|
||||||
}
|
|
||||||
|
// If we're over something that does not accept children then we go up a
|
||||||
|
// level and consider the mouse position relative to the parent
|
||||||
|
if (!acceptsChildren) {
|
||||||
|
id = parent
|
||||||
|
empty = false
|
||||||
|
node = getDOMNode(parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset state and styles
|
// We're now hovering over something which does accept children.
|
||||||
dragInfo = null
|
// If it is empty, just go inside it.
|
||||||
dropInfo = null
|
if (empty) {
|
||||||
builderStore.actions.setDragging(false)
|
dndStore.actions.updateDrop({
|
||||||
|
parent: id,
|
||||||
|
index: 0,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// As the first DOM node in a component may not necessarily contain the
|
||||||
|
// child components, we can find to try the parent of the first child
|
||||||
|
// component and use that as the real parent DOM node
|
||||||
|
const childNode = node.getElementsByClassName("component")[0]
|
||||||
|
if (childNode?.parentNode) {
|
||||||
|
node = childNode.parentNode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append an ephemeral div to allow us to determine layout if only one
|
||||||
|
// child exists
|
||||||
|
let ephemeralDiv
|
||||||
|
if (node.children.length === 1) {
|
||||||
|
ephemeralDiv = document.createElement("div")
|
||||||
|
ephemeralDiv.dataset.id = DNDPlaceholderID
|
||||||
|
node.appendChild(ephemeralDiv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're now hovering over something which accepts children and is not
|
||||||
|
// empty, so we need to work out where to inside the placeholder
|
||||||
|
// Calculate the coordinates of various locations on each child.
|
||||||
|
const childCoords = [...(node.children || [])].map(node => {
|
||||||
|
const child = node.children?.[0] || node
|
||||||
|
const bounds = child.getBoundingClientRect()
|
||||||
|
return {
|
||||||
|
placeholder: node.dataset.id === DNDPlaceholderID,
|
||||||
|
centerX: bounds.left + bounds.width / 2,
|
||||||
|
centerY: bounds.top + bounds.height / 2,
|
||||||
|
left: bounds.left,
|
||||||
|
right: bounds.right,
|
||||||
|
top: bounds.top,
|
||||||
|
bottom: bounds.bottom,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Now that we've calculated the position of the children, we no longer need
|
||||||
|
// the ephemeral div
|
||||||
|
if (ephemeralDiv) {
|
||||||
|
node.removeChild(ephemeralDiv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the variance between each set of positions on the children
|
||||||
|
const variances = Object.keys(childCoords[0])
|
||||||
|
.filter(x => x !== "placeholder")
|
||||||
|
.map(key => {
|
||||||
|
const coords = childCoords.map(x => x[key])
|
||||||
|
return {
|
||||||
|
variance: variance(coords),
|
||||||
|
side: key,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Sort by variance. The lowest variance position indicates whether we are
|
||||||
|
// in a row or column layout
|
||||||
|
variances.sort((a, b) => {
|
||||||
|
return a.variance < b.variance ? -1 : 1
|
||||||
|
})
|
||||||
|
const column = ["centerX", "left", "right"].includes(variances[0].side)
|
||||||
|
|
||||||
|
// Calculate breakpoints between child components so we can determine the
|
||||||
|
// index to drop the component in.
|
||||||
|
// We want to ignore the placeholder from this calculation as it should not
|
||||||
|
// be considered a real child of the parent.
|
||||||
|
let breakpoints = childCoords
|
||||||
|
.filter(x => !x.placeholder)
|
||||||
|
.map(x => {
|
||||||
|
return column ? x.centerY : x.centerX
|
||||||
|
})
|
||||||
|
|
||||||
|
// Determine the index to drop the component in
|
||||||
|
const mousePosition = column ? mouseY : mouseX
|
||||||
|
let idx = 0
|
||||||
|
while (idx < breakpoints.length && breakpoints[idx] < mousePosition) {
|
||||||
|
idx++
|
||||||
|
}
|
||||||
|
dndStore.actions.updateDrop({
|
||||||
|
parent: id,
|
||||||
|
index: idx,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const throttledProcessEvent = Utils.throttle(processEvent, ThrottleRate)
|
||||||
|
|
||||||
|
const handleEvent = e => {
|
||||||
|
e.preventDefault()
|
||||||
|
throttledProcessEvent(e.clientX, e.clientY)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback when on top of a component
|
// Callback when on top of a component
|
||||||
const onDragOver = e => {
|
const onDragOver = e => {
|
||||||
// Skip if we aren't validly dragging currently
|
if (!source || !target) {
|
||||||
if (!dragInfo || !dropInfo) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
handleEvent(e)
|
||||||
e.preventDefault()
|
|
||||||
const { droppableInside, bounds } = dropInfo
|
|
||||||
const { top, left, height, width } = bounds
|
|
||||||
const mouseY = e.clientY
|
|
||||||
const mouseX = e.clientX
|
|
||||||
const snapFactor = droppableInside ? 0.33 : 0.5
|
|
||||||
const snapLimitV = Math.min(40, height * snapFactor)
|
|
||||||
const snapLimitH = Math.min(40, width * snapFactor)
|
|
||||||
|
|
||||||
// Determine all sies we are within snap range of
|
|
||||||
let sides = []
|
|
||||||
if (mouseY <= top + snapLimitV) {
|
|
||||||
sides.push(Sides.Top)
|
|
||||||
} else if (mouseY >= top + height - snapLimitV) {
|
|
||||||
sides.push(Sides.Bottom)
|
|
||||||
}
|
|
||||||
if (mouseX < left + snapLimitH) {
|
|
||||||
sides.push(Sides.Left)
|
|
||||||
} else if (mouseX > left + width - snapLimitH) {
|
|
||||||
sides.push(Sides.Right)
|
|
||||||
}
|
|
||||||
|
|
||||||
// When no edges match, drop inside if possible
|
|
||||||
if (!sides.length) {
|
|
||||||
dropInfo.mode = droppableInside ? "inside" : null
|
|
||||||
dropInfo.side = null
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// When one edge matches, use that edge
|
|
||||||
if (sides.length === 1) {
|
|
||||||
dropInfo.side = sides[0]
|
|
||||||
if ([Sides.Top, Sides.Left].includes(sides[0])) {
|
|
||||||
dropInfo.mode = "above"
|
|
||||||
} else {
|
|
||||||
dropInfo.mode = "below"
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// When 2 edges match, work out which is closer
|
|
||||||
const mousePoint = [mouseX, mouseY]
|
|
||||||
const edges = getEdges(bounds, mousePoint)
|
|
||||||
const edge1 = edges[sides[0]]
|
|
||||||
const delta1 = calculatePointDelta(mousePoint, edge1)
|
|
||||||
const edge2 = edges[sides[1]]
|
|
||||||
const delta2 = calculatePointDelta(mousePoint, edge2)
|
|
||||||
const edge = delta1 < delta2 ? sides[0] : sides[1]
|
|
||||||
dropInfo.side = edge
|
|
||||||
if ([Sides.Top, Sides.Left].includes(edge)) {
|
|
||||||
dropInfo.mode = "above"
|
|
||||||
} else {
|
|
||||||
dropInfo.mode = "below"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback when entering a potential drop target
|
// Callback when entering a potential drop target
|
||||||
const onDragEnter = e => {
|
const onDragEnter = e => {
|
||||||
// Skip if we aren't validly dragging currently
|
if (!source) {
|
||||||
if (!dragInfo || !e.target.closest) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const element = e.target.closest(".component:not(.block)")
|
// Find the next valid component to consider dropping over, ignoring nested
|
||||||
if (
|
// block components
|
||||||
element &&
|
const component = e.target?.closest?.(
|
||||||
element.classList.contains("droppable") &&
|
`.component:not(.block):not(.${source.id})`
|
||||||
element.dataset.id !== dragInfo.target
|
)
|
||||||
) {
|
if (component && component.classList.contains("droppable")) {
|
||||||
// Do nothing if this is the same target
|
dndStore.actions.updateTarget({
|
||||||
if (element.dataset.id === dropInfo?.target) {
|
id: component.dataset.id,
|
||||||
return
|
parent: component.dataset.parent,
|
||||||
}
|
node: getDOMNode(component.dataset.id),
|
||||||
|
empty: component.classList.contains("empty"),
|
||||||
// Ensure the dragging flag is always set.
|
acceptsChildren: component.classList.contains("parent"),
|
||||||
// There's a bit of a race condition between the app reinitialisation
|
})
|
||||||
// after selecting the DND component and setting this the first time
|
handleEvent(e)
|
||||||
if (!get(builderStore).isDragging) {
|
|
||||||
builderStore.actions.setDragging(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store target ID
|
|
||||||
const target = element.dataset.id
|
|
||||||
|
|
||||||
// Precompute and store some info to avoid recalculating everything in
|
|
||||||
// dragOver
|
|
||||||
const child = getDOMNodeForComponent(e.target)
|
|
||||||
const bounds = child.getBoundingClientRect()
|
|
||||||
dropInfo = {
|
|
||||||
target,
|
|
||||||
name: element.dataset.name,
|
|
||||||
icon: element.dataset.icon,
|
|
||||||
droppableInside: element.classList.contains("empty"),
|
|
||||||
bounds,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dropInfo = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback when leaving a potential drop target.
|
|
||||||
// Since we don't style our targets, we don't need to unset anything.
|
|
||||||
const onDragLeave = () => {}
|
|
||||||
|
|
||||||
// Callback when dropping a drag on top of some component
|
// Callback when dropping a drag on top of some component
|
||||||
const onDrop = e => {
|
const onDrop = () => {
|
||||||
e.preventDefault()
|
if (!source || !drop?.parent || drop?.index == null) {
|
||||||
if (dropInfo?.mode) {
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we're adding a new component rather than moving one
|
||||||
|
if (source.newComponentType) {
|
||||||
|
builderStore.actions.dropNewComponent(
|
||||||
|
source.newComponentType,
|
||||||
|
drop.parent,
|
||||||
|
drop.index
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert parent + index into target + mode
|
||||||
|
let legacyDropTarget, legacyDropMode
|
||||||
|
const parent = findComponentById(
|
||||||
|
get(screenStore).activeScreen?.props,
|
||||||
|
drop.parent
|
||||||
|
)
|
||||||
|
if (!parent) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do nothing if we didn't change the location
|
||||||
|
if (source.parent === drop.parent && source.index === drop.index) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out source component and placeholder from consideration
|
||||||
|
const children = parent._children?.filter(
|
||||||
|
x => x._id !== DNDPlaceholderID && x._id !== source.id
|
||||||
|
)
|
||||||
|
|
||||||
|
// Use inside if no existing children
|
||||||
|
if (!children?.length) {
|
||||||
|
legacyDropTarget = parent._id
|
||||||
|
legacyDropMode = "inside"
|
||||||
|
} else if (drop.index === 0) {
|
||||||
|
legacyDropTarget = children[0]?._id
|
||||||
|
legacyDropMode = "above"
|
||||||
|
} else {
|
||||||
|
legacyDropTarget = children[drop.index - 1]?._id
|
||||||
|
legacyDropMode = "below"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (legacyDropTarget && legacyDropMode) {
|
||||||
builderStore.actions.moveComponent(
|
builderStore.actions.moveComponent(
|
||||||
dragInfo.target,
|
source.id,
|
||||||
dropInfo.target,
|
legacyDropTarget,
|
||||||
dropInfo.mode
|
legacyDropMode
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,39 +300,32 @@
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// Events fired on the draggable target
|
// Events fired on the draggable target
|
||||||
document.addEventListener("dragstart", onDragStart, false)
|
document.addEventListener("dragstart", onDragStart, false)
|
||||||
document.addEventListener("dragend", onDragEnd, false)
|
|
||||||
|
|
||||||
// Events fired on the drop targets
|
// Events fired on the drop targets
|
||||||
document.addEventListener("dragover", onDragOver, false)
|
document.addEventListener("dragover", onDragOver, false)
|
||||||
document.addEventListener("dragenter", onDragEnter, false)
|
document.addEventListener("dragenter", onDragEnter, false)
|
||||||
document.addEventListener("dragleave", onDragLeave, false)
|
|
||||||
document.addEventListener("drop", onDrop, false)
|
document.addEventListener("drop", onDrop, false)
|
||||||
})
|
})
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
// Events fired on the draggable target
|
// Events fired on the draggable target
|
||||||
document.removeEventListener("dragstart", onDragStart, false)
|
document.removeEventListener("dragstart", onDragStart, false)
|
||||||
document.removeEventListener("dragend", onDragEnd, false)
|
|
||||||
|
|
||||||
// Events fired on the drop targets
|
// Events fired on the drop targets
|
||||||
document.removeEventListener("dragover", onDragOver, false)
|
document.removeEventListener("dragover", onDragOver, false)
|
||||||
document.removeEventListener("dragenter", onDragEnter, false)
|
document.removeEventListener("dragenter", onDragEnter, false)
|
||||||
document.removeEventListener("dragleave", onDragLeave, false)
|
|
||||||
document.removeEventListener("drop", onDrop, false)
|
document.removeEventListener("drop", onDrop, false)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<IndicatorSet
|
<IndicatorSet
|
||||||
componentId={dropInfo?.mode === "inside" ? dropInfo.target : null}
|
componentId={$dndParent}
|
||||||
color="var(--spectrum-global-color-static-green-500)"
|
color="var(--spectrum-global-color-static-green-500)"
|
||||||
zIndex="930"
|
zIndex="930"
|
||||||
transition
|
transition
|
||||||
prefix="Inside"
|
prefix="Inside"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DNDPositionIndicator
|
{#if $dndIsDragging}
|
||||||
{dropInfo}
|
<DNDPlaceholderOverlay />
|
||||||
color="var(--spectrum-global-color-static-green-500)"
|
{/if}
|
||||||
zIndex="940"
|
|
||||||
transition
|
|
||||||
/>
|
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
<script>
|
||||||
|
import { dndBounds } from "stores"
|
||||||
|
import { DNDPlaceholderID } from "constants"
|
||||||
|
|
||||||
|
$: style = getStyle($dndBounds)
|
||||||
|
|
||||||
|
const getStyle = bounds => {
|
||||||
|
if (!bounds) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return `--height: ${bounds.height}px; --width: ${bounds.width}px;`
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if style}
|
||||||
|
<div class="wrapper">
|
||||||
|
<div class="placeholder" id={DNDPlaceholderID} {style} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.wrapper {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.placeholder {
|
||||||
|
display: block;
|
||||||
|
height: var(--height);
|
||||||
|
width: var(--width);
|
||||||
|
max-height: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,47 @@
|
||||||
|
<script>
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import { DNDPlaceholderID } from "constants"
|
||||||
|
import { domDebounce } from "utils/domDebounce.js"
|
||||||
|
|
||||||
|
let left, top, height, width
|
||||||
|
|
||||||
|
const updatePosition = () => {
|
||||||
|
const node = document.getElementById(DNDPlaceholderID)
|
||||||
|
if (!node) {
|
||||||
|
height = 0
|
||||||
|
width = 0
|
||||||
|
} else {
|
||||||
|
const bounds = node.getBoundingClientRect()
|
||||||
|
left = bounds.left
|
||||||
|
top = bounds.top
|
||||||
|
height = bounds.height
|
||||||
|
width = bounds.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const debouncedUpdate = domDebounce(updatePosition)
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
const interval = setInterval(debouncedUpdate, 100)
|
||||||
|
return () => {
|
||||||
|
clearInterval(interval)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if left != null && top != null && width && height}
|
||||||
|
<div
|
||||||
|
class="overlay"
|
||||||
|
style="left: {left}px; top: {top}px; width: {width}px; height: {height}px;"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.overlay {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 800;
|
||||||
|
background: hsl(160, 64%, 90%);
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 130ms ease-out;
|
||||||
|
border: 2px solid var(--spectrum-global-color-static-green-500);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,66 +0,0 @@
|
||||||
<script>
|
|
||||||
import Indicator from "./Indicator.svelte"
|
|
||||||
import { Sides } from "./DNDHandler.svelte"
|
|
||||||
|
|
||||||
export let dropInfo
|
|
||||||
export let zIndex
|
|
||||||
export let color
|
|
||||||
export let transition
|
|
||||||
|
|
||||||
$: dimensions = getDimensions(dropInfo)
|
|
||||||
$: prefix = dropInfo?.mode === "above" ? "Before" : "After"
|
|
||||||
$: text = `${prefix} ${dropInfo?.name}`
|
|
||||||
$: icon = dropInfo?.icon
|
|
||||||
$: renderKey = `${dropInfo?.target}-${dropInfo?.side}`
|
|
||||||
|
|
||||||
const getDimensions = info => {
|
|
||||||
const { bounds, side } = info ?? {}
|
|
||||||
if (!bounds || !side) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get preview offset
|
|
||||||
const root = document.getElementById("clip-root")
|
|
||||||
const rootBounds = root.getBoundingClientRect()
|
|
||||||
|
|
||||||
// Subtract preview offset from bounds
|
|
||||||
let { left, top, width, height } = bounds
|
|
||||||
left -= rootBounds.left
|
|
||||||
top -= rootBounds.top
|
|
||||||
|
|
||||||
// Determine position
|
|
||||||
if (side === Sides.Top || side === Sides.Bottom) {
|
|
||||||
return {
|
|
||||||
top: side === Sides.Top ? top - 4 : top + height,
|
|
||||||
left: left - 2,
|
|
||||||
width: width + 4,
|
|
||||||
height: 0,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
top: top - 2,
|
|
||||||
left: side === Sides.Left ? left - 4 : left + width,
|
|
||||||
width: 0,
|
|
||||||
height: height + 4,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#key renderKey}
|
|
||||||
{#if dimensions && dropInfo?.mode !== "inside"}
|
|
||||||
<Indicator
|
|
||||||
left={Math.round(dimensions.left)}
|
|
||||||
top={Math.round(dimensions.top)}
|
|
||||||
width={dimensions.width}
|
|
||||||
height={dimensions.height}
|
|
||||||
{text}
|
|
||||||
{icon}
|
|
||||||
{zIndex}
|
|
||||||
{color}
|
|
||||||
{transition}
|
|
||||||
alignRight={dropInfo?.side === Sides.Right}
|
|
||||||
line
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{/key}
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount, onDestroy } from "svelte"
|
import { onMount, onDestroy } from "svelte"
|
||||||
import IndicatorSet from "./IndicatorSet.svelte"
|
import IndicatorSet from "./IndicatorSet.svelte"
|
||||||
import { builderStore } from "stores"
|
import { builderStore, dndIsDragging } from "stores"
|
||||||
|
|
||||||
let componentId
|
let componentId
|
||||||
$: zIndex = componentId === $builderStore.selectedComponentId ? 900 : 920
|
$: zIndex = componentId === $builderStore.selectedComponentId ? 900 : 920
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<IndicatorSet
|
<IndicatorSet
|
||||||
componentId={$builderStore.isDragging ? null : componentId}
|
componentId={$dndIsDragging ? null : componentId}
|
||||||
color="var(--spectrum-global-color-static-blue-200)"
|
color="var(--spectrum-global-color-static-blue-200)"
|
||||||
transition
|
transition
|
||||||
{zIndex}
|
{zIndex}
|
||||||
|
|
|
@ -19,8 +19,8 @@
|
||||||
|
|
||||||
<div
|
<div
|
||||||
in:fade={{
|
in:fade={{
|
||||||
delay: transition ? 130 : 0,
|
delay: transition ? 100 : 0,
|
||||||
duration: transition ? 130 : 0,
|
duration: transition ? 100 : 0,
|
||||||
}}
|
}}
|
||||||
class="indicator"
|
class="indicator"
|
||||||
class:flipped
|
class:flipped
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { builderStore } from "stores"
|
import { builderStore, dndIsDragging } from "stores"
|
||||||
import IndicatorSet from "./IndicatorSet.svelte"
|
import IndicatorSet from "./IndicatorSet.svelte"
|
||||||
|
|
||||||
$: color = $builderStore.editMode
|
$: color = $builderStore.editMode
|
||||||
|
@ -8,7 +8,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<IndicatorSet
|
<IndicatorSet
|
||||||
componentId={$builderStore.selectedComponentId}
|
componentId={$dndIsDragging ? null : $builderStore.selectedComponentId}
|
||||||
{color}
|
{color}
|
||||||
zIndex="910"
|
zIndex="910"
|
||||||
transition
|
transition
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import SettingsButton from "./SettingsButton.svelte"
|
import SettingsButton from "./SettingsButton.svelte"
|
||||||
import SettingsColorPicker from "./SettingsColorPicker.svelte"
|
import SettingsColorPicker from "./SettingsColorPicker.svelte"
|
||||||
import SettingsPicker from "./SettingsPicker.svelte"
|
import SettingsPicker from "./SettingsPicker.svelte"
|
||||||
import { builderStore, componentStore } from "stores"
|
import { builderStore, componentStore, dndIsDragging } from "stores"
|
||||||
import { domDebounce } from "utils/domDebounce"
|
import { domDebounce } from "utils/domDebounce"
|
||||||
|
|
||||||
const verticalOffset = 36
|
const verticalOffset = 36
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
let measured = false
|
let measured = false
|
||||||
|
|
||||||
$: definition = $componentStore.selectedComponentDefinition
|
$: definition = $componentStore.selectedComponentDefinition
|
||||||
$: showBar = definition?.showSettingsBar && !$builderStore.isDragging
|
$: showBar = definition?.showSettingsBar && !$dndIsDragging
|
||||||
$: settings = getBarSettings(definition)
|
$: settings = getBarSettings(definition)
|
||||||
|
|
||||||
const getBarSettings = definition => {
|
const getBarSettings = definition => {
|
||||||
|
|
|
@ -30,3 +30,7 @@ export const ActionTypes = {
|
||||||
ClearForm: "ClearForm",
|
ClearForm: "ClearForm",
|
||||||
ChangeFormStep: "ChangeFormStep",
|
ChangeFormStep: "ChangeFormStep",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const DNDPlaceholderID = "dnd-placeholder"
|
||||||
|
export const DNDPlaceholderType = "dnd-placeholder"
|
||||||
|
export const ScreenslotType = "screenslot"
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
blockStore,
|
blockStore,
|
||||||
componentStore,
|
componentStore,
|
||||||
environmentStore,
|
environmentStore,
|
||||||
|
dndStore,
|
||||||
} from "./stores"
|
} from "./stores"
|
||||||
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-rollup.js"
|
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-rollup.js"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
|
@ -25,6 +26,7 @@ let app
|
||||||
const loadBudibase = async () => {
|
const loadBudibase = async () => {
|
||||||
// Update builder store with any builder flags
|
// Update builder store with any builder flags
|
||||||
builderStore.set({
|
builderStore.set({
|
||||||
|
...get(builderStore),
|
||||||
inBuilder: !!window["##BUDIBASE_IN_BUILDER##"],
|
inBuilder: !!window["##BUDIBASE_IN_BUILDER##"],
|
||||||
layout: window["##BUDIBASE_PREVIEW_LAYOUT##"],
|
layout: window["##BUDIBASE_PREVIEW_LAYOUT##"],
|
||||||
screen: window["##BUDIBASE_PREVIEW_SCREEN##"],
|
screen: window["##BUDIBASE_PREVIEW_SCREEN##"],
|
||||||
|
@ -59,6 +61,15 @@ const loadBudibase = async () => {
|
||||||
if (name === "eject-block") {
|
if (name === "eject-block") {
|
||||||
const block = blockStore.actions.getBlock(payload)
|
const block = blockStore.actions.getBlock(payload)
|
||||||
block?.eject()
|
block?.eject()
|
||||||
|
} else if (name === "dragging-new-component") {
|
||||||
|
const { dragging, component } = payload
|
||||||
|
if (dragging) {
|
||||||
|
const definition =
|
||||||
|
componentStore.actions.getComponentDefinition(component)
|
||||||
|
dndStore.actions.startDraggingNewComponent({ component, definition })
|
||||||
|
} else {
|
||||||
|
dndStore.actions.reset()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ const createBuilderStore = () => {
|
||||||
theme: null,
|
theme: null,
|
||||||
customTheme: null,
|
customTheme: null,
|
||||||
previewDevice: "desktop",
|
previewDevice: "desktop",
|
||||||
isDragging: false,
|
|
||||||
navigation: null,
|
navigation: null,
|
||||||
hiddenComponentIds: [],
|
hiddenComponentIds: [],
|
||||||
usedPlugins: null,
|
usedPlugins: null,
|
||||||
|
@ -67,11 +66,12 @@ const createBuilderStore = () => {
|
||||||
mode,
|
mode,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setDragging: dragging => {
|
dropNewComponent: (component, parent, index) => {
|
||||||
if (dragging === get(store).isDragging) {
|
dispatchEvent("drop-new-component", {
|
||||||
return
|
component,
|
||||||
}
|
parent,
|
||||||
store.update(state => ({ ...state, isDragging: dragging }))
|
index,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
setEditMode: enabled => {
|
setEditMode: enabled => {
|
||||||
if (enabled === get(store).editMode) {
|
if (enabled === get(store).editMode) {
|
||||||
|
|
|
@ -5,7 +5,9 @@ import { devToolsStore } from "./devTools"
|
||||||
import { screenStore } from "./screens"
|
import { screenStore } from "./screens"
|
||||||
import { builderStore } from "./builder"
|
import { builderStore } from "./builder"
|
||||||
import Router from "../components/Router.svelte"
|
import Router from "../components/Router.svelte"
|
||||||
|
import DNDPlaceholder from "../components/preview/DNDPlaceholder.svelte"
|
||||||
import * as AppComponents from "../components/app/index.js"
|
import * as AppComponents from "../components/app/index.js"
|
||||||
|
import { DNDPlaceholderType, ScreenslotType } from "../constants.js"
|
||||||
|
|
||||||
const budibasePrefix = "@budibase/standard-components/"
|
const budibasePrefix = "@budibase/standard-components/"
|
||||||
|
|
||||||
|
@ -18,26 +20,21 @@ const createComponentStore = () => {
|
||||||
|
|
||||||
const derivedStore = derived(
|
const derivedStore = derived(
|
||||||
[store, builderStore, devToolsStore, screenStore],
|
[store, builderStore, devToolsStore, screenStore],
|
||||||
([$store, $builderState, $devToolsState, $screenState]) => {
|
([$store, $builderStore, $devToolsStore, $screenStore]) => {
|
||||||
|
const { inBuilder, selectedComponentId } = $builderStore
|
||||||
|
|
||||||
// Avoid any of this logic if we aren't in the builder preview
|
// Avoid any of this logic if we aren't in the builder preview
|
||||||
if (!$builderState.inBuilder && !$devToolsState.visible) {
|
if (!inBuilder && !$devToolsStore.visible) {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Derive the selected component instance and definition
|
const root = $screenStore.activeScreen?.props
|
||||||
let asset
|
const component = findComponentById(root, selectedComponentId)
|
||||||
const { screen, selectedComponentId } = $builderState
|
|
||||||
if ($builderState.inBuilder) {
|
|
||||||
asset = screen
|
|
||||||
} else {
|
|
||||||
asset = $screenState.activeScreen
|
|
||||||
}
|
|
||||||
const component = findComponentById(asset?.props, selectedComponentId)
|
|
||||||
const definition = getComponentDefinition(component?._component)
|
const definition = getComponentDefinition(component?._component)
|
||||||
|
|
||||||
// Derive the selected component path
|
// Derive the selected component path
|
||||||
const path =
|
const selectedPath =
|
||||||
findComponentPathById(asset?.props, selectedComponentId) || []
|
findComponentPathById(root, selectedComponentId) || []
|
||||||
|
|
||||||
return {
|
return {
|
||||||
customComponentManifest: $store.customComponentManifest,
|
customComponentManifest: $store.customComponentManifest,
|
||||||
|
@ -45,9 +42,8 @@ const createComponentStore = () => {
|
||||||
$store.mountedComponents[selectedComponentId],
|
$store.mountedComponents[selectedComponentId],
|
||||||
selectedComponent: component,
|
selectedComponent: component,
|
||||||
selectedComponentDefinition: definition,
|
selectedComponentDefinition: definition,
|
||||||
selectedComponentPath: path?.map(component => component._id),
|
selectedComponentPath: selectedPath?.map(component => component._id),
|
||||||
mountedComponentCount: Object.keys($store.mountedComponents).length,
|
mountedComponentCount: Object.keys($store.mountedComponents).length,
|
||||||
currentAsset: asset,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -95,8 +91,8 @@ const createComponentStore = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const getComponentById = id => {
|
const getComponentById = id => {
|
||||||
const asset = get(derivedStore).currentAsset
|
const root = get(screenStore).activeScreen?.props
|
||||||
return findComponentById(asset?.props, id)
|
return findComponentById(root, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getComponentDefinition = type => {
|
const getComponentDefinition = type => {
|
||||||
|
@ -105,8 +101,10 @@ const createComponentStore = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Screenslot is an edge case
|
// Screenslot is an edge case
|
||||||
if (type === "screenslot") {
|
if (type === ScreenslotType) {
|
||||||
type = `${budibasePrefix}${type}`
|
type = `${budibasePrefix}${type}`
|
||||||
|
} else if (type === DNDPlaceholderType) {
|
||||||
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle built-in components
|
// Handle built-in components
|
||||||
|
@ -124,8 +122,10 @@ const createComponentStore = () => {
|
||||||
if (!type) {
|
if (!type) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
if (type === "screenslot") {
|
if (type === ScreenslotType) {
|
||||||
return Router
|
return Router
|
||||||
|
} else if (type === DNDPlaceholderType) {
|
||||||
|
return DNDPlaceholder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle budibase components
|
// Handle budibase components
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { derived } from "svelte/store"
|
||||||
|
import { devToolsStore } from "../devTools.js"
|
||||||
|
import { authStore } from "../auth.js"
|
||||||
|
|
||||||
|
// Derive the current role of the logged-in user
|
||||||
|
export const currentRole = derived(
|
||||||
|
[devToolsStore, authStore],
|
||||||
|
([$devToolsStore, $authStore]) => {
|
||||||
|
return ($devToolsStore.enabled && $devToolsStore.role) || $authStore?.roleId
|
||||||
|
}
|
||||||
|
)
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { derived } from "svelte/store"
|
||||||
|
import { findComponentPathById } from "utils/components.js"
|
||||||
|
import { dndParent } from "../dnd.js"
|
||||||
|
import { screenStore } from "../screens.js"
|
||||||
|
|
||||||
|
export const dndComponentPath = derived(
|
||||||
|
[dndParent, screenStore],
|
||||||
|
([$dndParent, $screenStore]) => {
|
||||||
|
const root = $screenStore.activeScreen?.props
|
||||||
|
const path = findComponentPathById(root, $dndParent) || []
|
||||||
|
return path?.map(component => component._id)
|
||||||
|
}
|
||||||
|
)
|
|
@ -0,0 +1,5 @@
|
||||||
|
// These derived stores are pulled out from their parent stores to avoid
|
||||||
|
// dependency loops. By inverting store dependencies and extracting them
|
||||||
|
// separately we can keep our actual stores lean and performant.
|
||||||
|
export { currentRole } from "./currentRole.js"
|
||||||
|
export { dndComponentPath } from "./dndComponentPath.js"
|
|
@ -0,0 +1,90 @@
|
||||||
|
import { writable, derived } from "svelte/store"
|
||||||
|
|
||||||
|
const createDndStore = () => {
|
||||||
|
const initialState = {
|
||||||
|
// Info about the dragged component
|
||||||
|
source: null,
|
||||||
|
|
||||||
|
// Info about the target component being hovered over
|
||||||
|
target: null,
|
||||||
|
|
||||||
|
// Info about where the component would be dropped
|
||||||
|
drop: null,
|
||||||
|
}
|
||||||
|
const store = writable(initialState)
|
||||||
|
|
||||||
|
const startDraggingExistingComponent = ({ id, parent, bounds, index }) => {
|
||||||
|
store.set({
|
||||||
|
...initialState,
|
||||||
|
source: { id, parent, bounds, index },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const startDraggingNewComponent = ({ component, definition }) => {
|
||||||
|
if (!component) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get size of new component so we can show a properly sized placeholder
|
||||||
|
const width = definition?.size?.width || 128
|
||||||
|
const height = definition?.size?.height || 64
|
||||||
|
|
||||||
|
store.set({
|
||||||
|
...initialState,
|
||||||
|
source: {
|
||||||
|
id: null,
|
||||||
|
parent: null,
|
||||||
|
bounds: { height, width },
|
||||||
|
index: null,
|
||||||
|
newComponentType: component,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateTarget = ({ id, parent, node, empty, acceptsChildren }) => {
|
||||||
|
store.update(state => {
|
||||||
|
state.target = { id, parent, node, empty, acceptsChildren }
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateDrop = ({ parent, index }) => {
|
||||||
|
store.update(state => {
|
||||||
|
state.drop = { parent, index }
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
store.set(initialState)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe: store.subscribe,
|
||||||
|
actions: {
|
||||||
|
startDraggingExistingComponent,
|
||||||
|
startDraggingNewComponent,
|
||||||
|
updateTarget,
|
||||||
|
updateDrop,
|
||||||
|
reset,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dndStore = createDndStore()
|
||||||
|
|
||||||
|
// The DND store is updated extremely frequently, so we can greatly improve
|
||||||
|
// performance by deriving any state that needs to be externally observed.
|
||||||
|
// By doing this and using primitives, we can avoid invalidating other stores
|
||||||
|
// or components which depend on DND state unless values actually change.
|
||||||
|
export const dndIsDragging = derived(dndStore, $dndStore => !!$dndStore.source)
|
||||||
|
export const dndParent = derived(dndStore, $dndStore => $dndStore.drop?.parent)
|
||||||
|
export const dndIndex = derived(dndStore, $dndStore => $dndStore.drop?.index)
|
||||||
|
export const dndBounds = derived(
|
||||||
|
dndStore,
|
||||||
|
$dndStore => $dndStore.source?.bounds
|
||||||
|
)
|
||||||
|
export const dndIsNewComponent = derived(
|
||||||
|
dndStore,
|
||||||
|
$dndStore => $dndStore.source?.newComponentType != null
|
||||||
|
)
|
|
@ -1,7 +1,3 @@
|
||||||
import { derived } from "svelte/store"
|
|
||||||
import { devToolsStore } from "./devTools.js"
|
|
||||||
import { authStore } from "./auth.js"
|
|
||||||
|
|
||||||
export { authStore } from "./auth"
|
export { authStore } from "./auth"
|
||||||
export { appStore } from "./app"
|
export { appStore } from "./app"
|
||||||
export { notificationStore } from "./notification"
|
export { notificationStore } from "./notification"
|
||||||
|
@ -19,6 +15,14 @@ export { uploadStore } from "./uploads.js"
|
||||||
export { rowSelectionStore } from "./rowSelection.js"
|
export { rowSelectionStore } from "./rowSelection.js"
|
||||||
export { blockStore } from "./blocks.js"
|
export { blockStore } from "./blocks.js"
|
||||||
export { environmentStore } from "./environment"
|
export { environmentStore } from "./environment"
|
||||||
|
export {
|
||||||
|
dndStore,
|
||||||
|
dndIndex,
|
||||||
|
dndParent,
|
||||||
|
dndBounds,
|
||||||
|
dndIsNewComponent,
|
||||||
|
dndIsDragging,
|
||||||
|
} from "./dnd"
|
||||||
|
|
||||||
// Context stores are layered and duplicated, so it is not a singleton
|
// Context stores are layered and duplicated, so it is not a singleton
|
||||||
export { createContextStore } from "./context"
|
export { createContextStore } from "./context"
|
||||||
|
@ -26,10 +30,5 @@ export { createContextStore } from "./context"
|
||||||
// Initialises an app by loading screens and routes
|
// Initialises an app by loading screens and routes
|
||||||
export { initialise } from "./initialise"
|
export { initialise } from "./initialise"
|
||||||
|
|
||||||
// Derive the current role of the logged-in user
|
// Derived state
|
||||||
export const currentRole = derived(
|
export * from "./derived"
|
||||||
[devToolsStore, authStore],
|
|
||||||
([$devToolsStore, $authStore]) => {
|
|
||||||
return ($devToolsStore.enabled && $devToolsStore.role) || $authStore?.roleId
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
|
@ -2,18 +2,36 @@ import { derived } from "svelte/store"
|
||||||
import { routeStore } from "./routes"
|
import { routeStore } from "./routes"
|
||||||
import { builderStore } from "./builder"
|
import { builderStore } from "./builder"
|
||||||
import { appStore } from "./app"
|
import { appStore } from "./app"
|
||||||
|
import { dndIndex, dndParent, dndIsNewComponent } from "./dnd.js"
|
||||||
import { RoleUtils } from "@budibase/frontend-core"
|
import { RoleUtils } from "@budibase/frontend-core"
|
||||||
|
import { findComponentById, findComponentParent } from "../utils/components.js"
|
||||||
|
import { Helpers } from "@budibase/bbui"
|
||||||
|
import { DNDPlaceholderID, DNDPlaceholderType } from "constants"
|
||||||
|
|
||||||
const createScreenStore = () => {
|
const createScreenStore = () => {
|
||||||
const store = derived(
|
const store = derived(
|
||||||
[appStore, routeStore, builderStore],
|
[
|
||||||
([$appStore, $routeStore, $builderStore]) => {
|
appStore,
|
||||||
|
routeStore,
|
||||||
|
builderStore,
|
||||||
|
dndParent,
|
||||||
|
dndIndex,
|
||||||
|
dndIsNewComponent,
|
||||||
|
],
|
||||||
|
([
|
||||||
|
$appStore,
|
||||||
|
$routeStore,
|
||||||
|
$builderStore,
|
||||||
|
$dndParent,
|
||||||
|
$dndIndex,
|
||||||
|
$dndIsNewComponent,
|
||||||
|
]) => {
|
||||||
let activeLayout, activeScreen
|
let activeLayout, activeScreen
|
||||||
let screens
|
let screens
|
||||||
|
|
||||||
if ($builderStore.inBuilder) {
|
if ($builderStore.inBuilder) {
|
||||||
// Use builder defined definitions if inside the builder preview
|
// Use builder defined definitions if inside the builder preview
|
||||||
activeScreen = $builderStore.screen
|
activeScreen = Helpers.cloneDeep($builderStore.screen)
|
||||||
screens = [activeScreen]
|
screens = [activeScreen]
|
||||||
|
|
||||||
// Legacy - allow the builder to specify a layout
|
// Legacy - allow the builder to specify a layout
|
||||||
|
@ -24,8 +42,10 @@ const createScreenStore = () => {
|
||||||
// Find the correct screen by matching the current route
|
// Find the correct screen by matching the current route
|
||||||
screens = $appStore.screens || []
|
screens = $appStore.screens || []
|
||||||
if ($routeStore.activeRoute) {
|
if ($routeStore.activeRoute) {
|
||||||
activeScreen = screens.find(
|
activeScreen = Helpers.cloneDeep(
|
||||||
screen => screen._id === $routeStore.activeRoute.screenId
|
screens.find(
|
||||||
|
screen => screen._id === $routeStore.activeRoute.screenId
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,6 +60,37 @@ const createScreenStore = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Insert DND placeholder if required
|
||||||
|
if (activeScreen && $dndParent && $dndIndex != null) {
|
||||||
|
// Remove selected component from tree if we are moving an existing
|
||||||
|
// component
|
||||||
|
const { selectedComponentId } = $builderStore
|
||||||
|
if (!$dndIsNewComponent) {
|
||||||
|
let selectedParent = findComponentParent(
|
||||||
|
activeScreen.props,
|
||||||
|
selectedComponentId
|
||||||
|
)
|
||||||
|
if (selectedParent) {
|
||||||
|
selectedParent._children = selectedParent._children?.filter(
|
||||||
|
x => x._id !== selectedComponentId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert placeholder component
|
||||||
|
const placeholder = {
|
||||||
|
_component: DNDPlaceholderID,
|
||||||
|
_id: DNDPlaceholderType,
|
||||||
|
static: true,
|
||||||
|
}
|
||||||
|
let parent = findComponentById(activeScreen.props, $dndParent)
|
||||||
|
if (!parent._children?.length) {
|
||||||
|
parent._children = [placeholder]
|
||||||
|
} else {
|
||||||
|
parent._children.splice($dndIndex, 0, placeholder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Assign ranks to screens, preferring higher roles and home screens
|
// Assign ranks to screens, preferring higher roles and home screens
|
||||||
screens.forEach(screen => {
|
screens.forEach(screen => {
|
||||||
const roleId = screen.routing.roleId
|
const roleId = screen.routing.roleId
|
||||||
|
|
|
@ -224,10 +224,11 @@ const changeFormStepHandler = async (action, context) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeScreenModalHandler = () => {
|
const closeScreenModalHandler = action => {
|
||||||
|
let { url } = action.parameters
|
||||||
// Emit this as a window event, so parent screens which are iframing us in
|
// Emit this as a window event, so parent screens which are iframing us in
|
||||||
// can close the modal
|
// can close the modal
|
||||||
window.parent.postMessage({ type: "close-screen-modal" })
|
window.parent.postMessage({ type: "close-screen-modal", url })
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateStateHandler = action => {
|
const updateStateHandler = action => {
|
||||||
|
|
|
@ -60,3 +60,25 @@ export const findChildrenByType = (component, type, children = []) => {
|
||||||
findChildrenByType(child, type, children)
|
findChildrenByType(child, type, children)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively searches for the parent component of a specific component ID
|
||||||
|
*/
|
||||||
|
export const findComponentParent = (rootComponent, id, parentComponent) => {
|
||||||
|
if (!rootComponent || !id) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (rootComponent._id === id) {
|
||||||
|
return parentComponent
|
||||||
|
}
|
||||||
|
if (!rootComponent._children) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
for (const child of rootComponent._children) {
|
||||||
|
const childResult = findComponentParent(child, id, rootComponent)
|
||||||
|
if (childResult) {
|
||||||
|
return childResult
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ export const styleable = (node, styles = {}) => {
|
||||||
const setupStyles = (newStyles = {}) => {
|
const setupStyles = (newStyles = {}) => {
|
||||||
let baseStyles = {}
|
let baseStyles = {}
|
||||||
if (newStyles.empty) {
|
if (newStyles.empty) {
|
||||||
baseStyles.border = "2px dashed var(--spectrum-global-color-gray-600)"
|
baseStyles.border = "2px dashed var(--spectrum-global-color-gray-400)"
|
||||||
baseStyles.padding = "var(--spacing-l)"
|
baseStyles.padding = "var(--spacing-l)"
|
||||||
baseStyles.overflow = "hidden"
|
baseStyles.overflow = "hidden"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/frontend-core",
|
"name": "@budibase/frontend-core",
|
||||||
"version": "2.0.30-alpha.7",
|
"version": "2.0.30-alpha.12",
|
||||||
"description": "Budibase frontend core libraries used in builder and client",
|
"description": "Budibase frontend core libraries used in builder and client",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "2.0.30-alpha.7",
|
"@budibase/bbui": "2.0.30-alpha.12",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"svelte": "^3.46.2"
|
"svelte": "^3.46.2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,3 +40,37 @@ export const debounce = (callback, minDelay = 1000) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility to throttle invocations of a synchronous function. This is better
|
||||||
|
* than a simple debounce invocation for a number of reasons. Features include:
|
||||||
|
* - First invocation is immediate (no initial delay)
|
||||||
|
* - Every invocation has the latest params (no stale params)
|
||||||
|
* - There will always be a final invocation with the last params (no missing
|
||||||
|
* final update)
|
||||||
|
* @param callback
|
||||||
|
* @param minDelay
|
||||||
|
* @returns {Function} a throttled version function
|
||||||
|
*/
|
||||||
|
export const throttle = (callback, minDelay = 1000) => {
|
||||||
|
let lastParams
|
||||||
|
let stalled = false
|
||||||
|
let pending = false
|
||||||
|
const invoke = (...params) => {
|
||||||
|
lastParams = params
|
||||||
|
if (stalled) {
|
||||||
|
pending = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
callback(...lastParams)
|
||||||
|
stalled = true
|
||||||
|
setTimeout(() => {
|
||||||
|
stalled = false
|
||||||
|
if (pending) {
|
||||||
|
pending = false
|
||||||
|
invoke(...lastParams)
|
||||||
|
}
|
||||||
|
}, minDelay)
|
||||||
|
}
|
||||||
|
return invoke
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/sdk",
|
"name": "@budibase/sdk",
|
||||||
"version": "2.0.30-alpha.7",
|
"version": "2.0.30-alpha.12",
|
||||||
"description": "Budibase Public API SDK",
|
"description": "Budibase Public API SDK",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "2.0.30-alpha.7",
|
"version": "2.0.30-alpha.12",
|
||||||
"description": "Budibase Web Server",
|
"description": "Budibase Web Server",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -77,11 +77,11 @@
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apidevtools/swagger-parser": "10.0.3",
|
"@apidevtools/swagger-parser": "10.0.3",
|
||||||
"@budibase/backend-core": "2.0.30-alpha.7",
|
"@budibase/backend-core": "2.0.30-alpha.12",
|
||||||
"@budibase/client": "2.0.30-alpha.7",
|
"@budibase/client": "2.0.30-alpha.12",
|
||||||
"@budibase/pro": "2.0.30-alpha.7",
|
"@budibase/pro": "2.0.30-alpha.12",
|
||||||
"@budibase/string-templates": "2.0.30-alpha.7",
|
"@budibase/string-templates": "2.0.30-alpha.12",
|
||||||
"@budibase/types": "2.0.30-alpha.7",
|
"@budibase/types": "2.0.30-alpha.12",
|
||||||
"@bull-board/api": "3.7.0",
|
"@bull-board/api": "3.7.0",
|
||||||
"@bull-board/koa": "3.9.4",
|
"@bull-board/koa": "3.9.4",
|
||||||
"@elastic/elasticsearch": "7.10.0",
|
"@elastic/elasticsearch": "7.10.0",
|
||||||
|
@ -163,7 +163,7 @@
|
||||||
"@types/google-spreadsheet": "3.1.5",
|
"@types/google-spreadsheet": "3.1.5",
|
||||||
"@types/ioredis": "4.28.10",
|
"@types/ioredis": "4.28.10",
|
||||||
"@types/jest": "27.5.1",
|
"@types/jest": "27.5.1",
|
||||||
"@types/koa": "2.13.5",
|
"@types/koa": "2.13.4",
|
||||||
"@types/koa__router": "8.0.11",
|
"@types/koa__router": "8.0.11",
|
||||||
"@types/lodash": "4.14.180",
|
"@types/lodash": "4.14.180",
|
||||||
"@types/node": "14.18.20",
|
"@types/node": "14.18.20",
|
||||||
|
|
|
@ -783,6 +783,7 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"string",
|
"string",
|
||||||
|
"barcodeqr",
|
||||||
"longform",
|
"longform",
|
||||||
"options",
|
"options",
|
||||||
"number",
|
"number",
|
||||||
|
@ -986,6 +987,7 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"string",
|
"string",
|
||||||
|
"barcodeqr",
|
||||||
"longform",
|
"longform",
|
||||||
"options",
|
"options",
|
||||||
"number",
|
"number",
|
||||||
|
@ -1200,6 +1202,7 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"string",
|
"string",
|
||||||
|
"barcodeqr",
|
||||||
"longform",
|
"longform",
|
||||||
"options",
|
"options",
|
||||||
"number",
|
"number",
|
||||||
|
|
|
@ -579,6 +579,7 @@ components:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
enum:
|
||||||
- string
|
- string
|
||||||
|
- barcodeqr
|
||||||
- longform
|
- longform
|
||||||
- options
|
- options
|
||||||
- number
|
- number
|
||||||
|
@ -741,6 +742,7 @@ components:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
enum:
|
||||||
- string
|
- string
|
||||||
|
- barcodeqr
|
||||||
- longform
|
- longform
|
||||||
- options
|
- options
|
||||||
- number
|
- number
|
||||||
|
@ -910,6 +912,7 @@ components:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
enum:
|
||||||
- string
|
- string
|
||||||
|
- barcodeqr
|
||||||
- longform
|
- longform
|
||||||
- options
|
- options
|
||||||
- number
|
- number
|
||||||
|
|
|
@ -35,9 +35,9 @@ async function getAllDocType(db, docType) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.exportApps = async ctx => {
|
exports.exportApps = async ctx => {
|
||||||
// if (env.SELF_HOSTED || !env.MULTI_TENANCY) {
|
if (env.SELF_HOSTED || !env.MULTI_TENANCY) {
|
||||||
// ctx.throw(400, "Exporting only allowed in multi-tenant cloud environments.")
|
ctx.throw(400, "Exporting only allowed in multi-tenant cloud environments.")
|
||||||
// }
|
}
|
||||||
const apps = await getAllApps({ all: true })
|
const apps = await getAllApps({ all: true })
|
||||||
const globalDBString = await sdk.backups.exportDB(getGlobalDBName(), {
|
const globalDBString = await sdk.backups.exportDB(getGlobalDBName(), {
|
||||||
filter: doc => !doc._id.startsWith(DocumentType.USER),
|
filter: doc => !doc._id.startsWith(DocumentType.USER),
|
||||||
|
|
|
@ -20,7 +20,6 @@ import {
|
||||||
import { events } from "@budibase/backend-core"
|
import { events } from "@budibase/backend-core"
|
||||||
import { backups } from "@budibase/pro"
|
import { backups } from "@budibase/pro"
|
||||||
import { AppBackupTrigger } from "@budibase/types"
|
import { AppBackupTrigger } from "@budibase/types"
|
||||||
import env from "../../../environment"
|
|
||||||
|
|
||||||
// the max time we can wait for an invalidation to complete before considering it failed
|
// the max time we can wait for an invalidation to complete before considering it failed
|
||||||
const MAX_PENDING_TIME_MS = 30 * 60000
|
const MAX_PENDING_TIME_MS = 30 * 60000
|
||||||
|
@ -108,8 +107,8 @@ async function deployApp(deployment: any, userId: string) {
|
||||||
const devAppId = getDevelopmentAppID(appId)
|
const devAppId = getDevelopmentAppID(appId)
|
||||||
const productionAppId = getProdAppID(appId)
|
const productionAppId = getProdAppID(appId)
|
||||||
|
|
||||||
// can't do this in test
|
// don't try this if feature isn't allowed, will error
|
||||||
if (!env.isTest()) {
|
if (await backups.isEnabled()) {
|
||||||
// trigger backup initially
|
// trigger backup initially
|
||||||
await backups.triggerAppBackup(
|
await backups.triggerAppBackup(
|
||||||
productionAppId,
|
productionAppId,
|
||||||
|
@ -119,7 +118,6 @@ async function deployApp(deployment: any, userId: string) {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const config: any = {
|
const config: any = {
|
||||||
source: devAppId,
|
source: devAppId,
|
||||||
target: productionAppId,
|
target: productionAppId,
|
||||||
|
|
|
@ -21,7 +21,7 @@ describe("/backups", () => {
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.expect(200)
|
.expect(200)
|
||||||
expect(res.text).toBeDefined()
|
expect(res.text).toBeDefined()
|
||||||
expect(res.text.includes(`"db_name":"${config.getAppId()}"`)).toEqual(true)
|
expect(res.headers["content-type"]).toEqual("application/gzip")
|
||||||
expect(events.app.exported.mock.calls.length).toBe(1)
|
expect(events.app.exported.mock.calls.length).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const setup = require("./utilities")
|
import setup from "./utilities"
|
||||||
const { events } = require("@budibase/backend-core")
|
import { events } from "@budibase/backend-core"
|
||||||
|
|
||||||
describe("/deployments", () => {
|
describe("/deployments", () => {
|
||||||
let request = setup.getRequest()
|
let request = setup.getRequest()
|
||||||
|
@ -19,7 +19,7 @@ describe("/deployments", () => {
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
expect(events.app.published.mock.calls.length).toBe(1)
|
expect((events.app.published as jest.Mock).mock.calls.length).toBe(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
|
@ -62,6 +62,8 @@ class ArangoDBIntegration implements IntegrationBase {
|
||||||
|
|
||||||
constructor(config: ArangodbConfig) {
|
constructor(config: ArangodbConfig) {
|
||||||
const newConfig = {
|
const newConfig = {
|
||||||
|
url: config.url,
|
||||||
|
databaseName: config.databaseName,
|
||||||
auth: {
|
auth: {
|
||||||
username: config.username,
|
username: config.username,
|
||||||
password: config.password,
|
password: config.password,
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { exportApp } from "./exports"
|
import { exportApp } from "./exports"
|
||||||
import { importApp } from "./imports"
|
import { importApp } from "./imports"
|
||||||
import { calculateBackupStats } from "../statistics"
|
import { calculateBackupStats } from "./statistics"
|
||||||
import { Job } from "bull"
|
import { Job } from "bull"
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import env from "../../../environment"
|
import env from "../../../environment"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { db as dbCore } from "@budibase/backend-core"
|
import { db as dbCore } from "@budibase/backend-core"
|
||||||
import { budibaseTempDir } from "../../../utilities/budibaseDir"
|
import { budibaseTempDir } from "../../../utilities/budibaseDir"
|
||||||
import { retrieveDirectory } from "../../../utilities/fileSystem/utilities"
|
import { retrieveDirectory } from "../../../utilities/fileSystem/utilities"
|
||||||
import { streamFile } from "../../../utilities/fileSystem"
|
import { streamFile, createTempFolder } from "../../../utilities/fileSystem"
|
||||||
import { ObjectStoreBuckets } from "../../../constants"
|
import { ObjectStoreBuckets } from "../../../constants"
|
||||||
import {
|
import {
|
||||||
LINK_USER_METADATA_PREFIX,
|
LINK_USER_METADATA_PREFIX,
|
||||||
|
@ -11,6 +11,7 @@ import {
|
||||||
import { DB_EXPORT_FILE, GLOBAL_DB_EXPORT_FILE } from "./constants"
|
import { DB_EXPORT_FILE, GLOBAL_DB_EXPORT_FILE } from "./constants"
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import { join } from "path"
|
import { join } from "path"
|
||||||
|
import env from "../../../environment"
|
||||||
const uuid = require("uuid/v4")
|
const uuid = require("uuid/v4")
|
||||||
const tar = require("tar")
|
const tar = require("tar")
|
||||||
const MemoryStream = require("memorystream")
|
const MemoryStream = require("memorystream")
|
||||||
|
@ -85,7 +86,12 @@ export async function exportApp(appId: string, config?: ExportOpts) {
|
||||||
const prodAppId = dbCore.getProdAppID(appId)
|
const prodAppId = dbCore.getProdAppID(appId)
|
||||||
const appPath = `${prodAppId}/`
|
const appPath = `${prodAppId}/`
|
||||||
// export bucket contents
|
// export bucket contents
|
||||||
const tmpPath = await retrieveDirectory(ObjectStoreBuckets.APPS, appPath)
|
let tmpPath
|
||||||
|
if (!env.isTest()) {
|
||||||
|
tmpPath = await retrieveDirectory(ObjectStoreBuckets.APPS, appPath)
|
||||||
|
} else {
|
||||||
|
tmpPath = createTempFolder(uuid())
|
||||||
|
}
|
||||||
const downloadedPath = join(tmpPath, appPath)
|
const downloadedPath = join(tmpPath, appPath)
|
||||||
if (fs.existsSync(downloadedPath)) {
|
if (fs.existsSync(downloadedPath)) {
|
||||||
const allFiles = fs.readdirSync(downloadedPath)
|
const allFiles = fs.readdirSync(downloadedPath)
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import * as exportApps from "./exports"
|
import * as exportApps from "./exports"
|
||||||
import * as importApps from "./imports"
|
import * as importApps from "./imports"
|
||||||
import * as backup from "./backup"
|
import * as backup from "./backup"
|
||||||
|
import * as statistics from "./statistics"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
...exportApps,
|
...exportApps,
|
||||||
...importApps,
|
...importApps,
|
||||||
...backup,
|
...backup,
|
||||||
|
...statistics,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { default as backups } from "./app/backups"
|
import { default as backups } from "./app/backups"
|
||||||
import { default as tables } from "./app/tables"
|
import { default as tables } from "./app/tables"
|
||||||
|
|
||||||
const toExport = {
|
const sdk = {
|
||||||
backups,
|
backups,
|
||||||
tables,
|
tables,
|
||||||
}
|
}
|
||||||
|
|
||||||
// default export for TS
|
// default export for TS
|
||||||
export default toExport
|
export default sdk
|
||||||
|
|
||||||
// default export for JS
|
// default export for JS
|
||||||
module.exports = toExport
|
module.exports = sdk
|
||||||
|
|
|
@ -25,7 +25,7 @@ const newid = require("../../db/newid")
|
||||||
const context = require("@budibase/backend-core/context")
|
const context = require("@budibase/backend-core/context")
|
||||||
const { generateDevInfoID, SEPARATOR } = require("@budibase/backend-core/db")
|
const { generateDevInfoID, SEPARATOR } = require("@budibase/backend-core/db")
|
||||||
const { encrypt } = require("@budibase/backend-core/encryption")
|
const { encrypt } = require("@budibase/backend-core/encryption")
|
||||||
const { DocumentType } = require("../../db/utils")
|
const { DocumentType, generateUserMetadataID } = require("../../db/utils")
|
||||||
|
|
||||||
const GLOBAL_USER_ID = "us_uuid1"
|
const GLOBAL_USER_ID = "us_uuid1"
|
||||||
const EMAIL = "babs@babs.com"
|
const EMAIL = "babs@babs.com"
|
||||||
|
@ -95,7 +95,10 @@ class TestConfiguration {
|
||||||
|
|
||||||
// use a new id as the name to avoid name collisions
|
// use a new id as the name to avoid name collisions
|
||||||
async init(appName = newid()) {
|
async init(appName = newid()) {
|
||||||
await this.globalUser()
|
this.user = await this.globalUser()
|
||||||
|
this.globalUserId = this.user._id
|
||||||
|
this.userMetadataId = generateUserMetadataID(this.globalUserId)
|
||||||
|
|
||||||
return this.createApp(appName)
|
return this.createApp(appName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/string-templates",
|
"name": "@budibase/string-templates",
|
||||||
"version": "2.0.30-alpha.7",
|
"version": "2.0.30-alpha.12",
|
||||||
"description": "Handlebars wrapper for Budibase templating.",
|
"description": "Handlebars wrapper for Budibase templating.",
|
||||||
"main": "src/index.cjs",
|
"main": "src/index.cjs",
|
||||||
"module": "dist/bundle.mjs",
|
"module": "dist/bundle.mjs",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/types",
|
"name": "@budibase/types",
|
||||||
"version": "2.0.30-alpha.7",
|
"version": "2.0.30-alpha.12",
|
||||||
"description": "Budibase types",
|
"description": "Budibase types",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
export * from "./db"
|
|
|
@ -1,4 +1,3 @@
|
||||||
export * from "./documents"
|
export * from "./documents"
|
||||||
export * from "./sdk"
|
export * from "./sdk"
|
||||||
export * from "./api"
|
export * from "./api"
|
||||||
export * from "./core"
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { BaseEvent } from "./event"
|
import { BaseEvent } from "./event"
|
||||||
|
|
||||||
export interface AppBackupRevertEvent extends BaseEvent {
|
export interface AppBackupRestoreEvent extends BaseEvent {
|
||||||
appId: string
|
appId: string
|
||||||
backupName: string
|
backupName: string
|
||||||
backupCreatedAt: string
|
backupCreatedAt: string
|
||||||
|
|
|
@ -8,3 +8,4 @@ export * from "./search"
|
||||||
export * from "./koa"
|
export * from "./koa"
|
||||||
export * from "./auth"
|
export * from "./auth"
|
||||||
export * from "./locks"
|
export * from "./locks"
|
||||||
|
export * from "./db"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Context } from "koa"
|
import { Context, Request } from "koa"
|
||||||
import { User } from "../documents"
|
import { User } from "../documents"
|
||||||
import { License } from "../sdk"
|
import { License } from "../sdk"
|
||||||
|
|
||||||
|
@ -7,15 +7,11 @@ export interface ContextUser extends User {
|
||||||
license: License
|
license: License
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BBContext {
|
export interface BBRequest extends Request {
|
||||||
user?: ContextUser
|
body: any
|
||||||
status?: number
|
}
|
||||||
request: {
|
|
||||||
body: any
|
export interface BBContext extends Context {
|
||||||
}
|
request: BBRequest
|
||||||
params: any
|
user?: ContextUser
|
||||||
body?: any
|
|
||||||
redirect?: any
|
|
||||||
attachment: any
|
|
||||||
throw: any
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ export enum MonthlyQuotaName {
|
||||||
|
|
||||||
export enum ConstantQuotaName {
|
export enum ConstantQuotaName {
|
||||||
AUTOMATION_LOG_RETENTION_DAYS = "automationLogRetentionDays",
|
AUTOMATION_LOG_RETENTION_DAYS = "automationLogRetentionDays",
|
||||||
|
APP_BACKUPS_RETENTION_DAYS = "appBackupRetentionDays",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MeteredQuotaName = StaticQuotaName | MonthlyQuotaName
|
export type MeteredQuotaName = StaticQuotaName | MonthlyQuotaName
|
||||||
|
@ -76,6 +77,7 @@ export type StaticQuotas = {
|
||||||
|
|
||||||
export type ConstantQuotas = {
|
export type ConstantQuotas = {
|
||||||
[ConstantQuotaName.AUTOMATION_LOG_RETENTION_DAYS]: Quota
|
[ConstantQuotaName.AUTOMATION_LOG_RETENTION_DAYS]: Quota
|
||||||
|
[ConstantQuotaName.APP_BACKUPS_RETENTION_DAYS]: Quota
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Quotas = {
|
export type Quotas = {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/worker",
|
"name": "@budibase/worker",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "2.0.30-alpha.7",
|
"version": "2.0.30-alpha.12",
|
||||||
"description": "Budibase background service",
|
"description": "Budibase background service",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -36,10 +36,10 @@
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/backend-core": "2.0.30-alpha.7",
|
"@budibase/backend-core": "2.0.30-alpha.12",
|
||||||
"@budibase/pro": "2.0.30-alpha.7",
|
"@budibase/pro": "2.0.30-alpha.12",
|
||||||
"@budibase/string-templates": "2.0.30-alpha.7",
|
"@budibase/string-templates": "2.0.30-alpha.12",
|
||||||
"@budibase/types": "2.0.30-alpha.7",
|
"@budibase/types": "2.0.30-alpha.12",
|
||||||
"@koa/router": "8.0.8",
|
"@koa/router": "8.0.8",
|
||||||
"@sentry/node": "6.17.7",
|
"@sentry/node": "6.17.7",
|
||||||
"@techpass/passport-openidconnect": "0.3.2",
|
"@techpass/passport-openidconnect": "0.3.2",
|
||||||
|
|
|
@ -79,9 +79,6 @@ export const paginatedUsers = async ({
|
||||||
} else if (email) {
|
} else if (email) {
|
||||||
userList = await usersCore.searchGlobalUsersByEmail(email, opts)
|
userList = await usersCore.searchGlobalUsersByEmail(email, opts)
|
||||||
property = "email"
|
property = "email"
|
||||||
}
|
|
||||||
if (userIds) {
|
|
||||||
// TODO: search users by userIds
|
|
||||||
} else {
|
} else {
|
||||||
// no search, query allDocs
|
// no search, query allDocs
|
||||||
const response = await db.allDocs(dbUtils.getGlobalUserParams(null, opts))
|
const response = await db.allDocs(dbUtils.getGlobalUserParams(null, opts))
|
||||||
|
|
|
@ -291,12 +291,12 @@
|
||||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||||
|
|
||||||
"@budibase/backend-core@2.0.30-alpha.7":
|
"@budibase/backend-core@2.0.30-alpha.12":
|
||||||
version "2.0.30-alpha.7"
|
version "2.0.30-alpha.12"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.0.30-alpha.7.tgz#a46ddcda96c6ecead6574cb0e2751e4ed858371d"
|
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.0.30-alpha.12.tgz#adeb3e3f043c7b85a1a4aab3d3e33832d1ab8dfd"
|
||||||
integrity sha512-aX41o4QE7OlS+JeKIDt3bJGGsbddI4eRAram+2bYlHONbupkbTAVs9DFBkV9BkMCQ8zk9FSDY90Le0GEuiI+sw==
|
integrity sha512-e/+tvvn1rcLTw/D/OlL9HYV1hw87x2mgofCV5Y1th6r1Tvum7Nr7revcU8CQIpVOc+iz6Eg1vAbMjqDsGr5YUw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/types" "2.0.30-alpha.7"
|
"@budibase/types" "2.0.30-alpha.12"
|
||||||
"@shopify/jest-koa-mocks" "5.0.1"
|
"@shopify/jest-koa-mocks" "5.0.1"
|
||||||
"@techpass/passport-openidconnect" "0.3.2"
|
"@techpass/passport-openidconnect" "0.3.2"
|
||||||
aws-sdk "2.1030.0"
|
aws-sdk "2.1030.0"
|
||||||
|
@ -327,21 +327,21 @@
|
||||||
uuid "8.3.2"
|
uuid "8.3.2"
|
||||||
zlib "1.0.5"
|
zlib "1.0.5"
|
||||||
|
|
||||||
"@budibase/pro@2.0.30-alpha.7":
|
"@budibase/pro@2.0.30-alpha.12":
|
||||||
version "2.0.30-alpha.7"
|
version "2.0.30-alpha.12"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.0.30-alpha.7.tgz#1432b47141b305666dc005c5fc5a02d9cd38cf6a"
|
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.0.30-alpha.12.tgz#52a26671aad4248fa2a6482a262f07c3f8154b7b"
|
||||||
integrity sha512-F8qkj+Uy2Pqi/FeJYDBEdNAT7zEx/dw19x8o+3vHmTnQhwLbuSOHGyy9C6PgSW5Gmm0GgxjOycDVOq7ewnwWDA==
|
integrity sha512-qp/plMQCpCabfDGeKZlOL74epNFzH1leZX7K/cqGwos0kYSElg6zPv/BayMRgID0oxllvuDq5M/fllXtF1QMig==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/backend-core" "2.0.30-alpha.7"
|
"@budibase/backend-core" "2.0.30-alpha.12"
|
||||||
"@budibase/types" "2.0.30-alpha.7"
|
"@budibase/types" "2.0.30-alpha.12"
|
||||||
"@koa/router" "8.0.8"
|
"@koa/router" "8.0.8"
|
||||||
joi "17.6.0"
|
joi "17.6.0"
|
||||||
node-fetch "^2.6.1"
|
node-fetch "^2.6.1"
|
||||||
|
|
||||||
"@budibase/types@2.0.30-alpha.7":
|
"@budibase/types@2.0.30-alpha.12":
|
||||||
version "2.0.30-alpha.7"
|
version "2.0.30-alpha.12"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.0.30-alpha.7.tgz#fe85563eb42ce01869e653a790a7f213ea2d6308"
|
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.0.30-alpha.12.tgz#90912b6f6731d67f135787bc5cdeec562b30e336"
|
||||||
integrity sha512-WL+2LPQyYq1BwPGgkM7wRnIv4/QXMX1ivXpzoy9lzi1yCJw0rxg8MBpfUuVU0cmDOAnmHkTh2nBQNUhLYPyvVA==
|
integrity sha512-PFO8BgScyaesA060ickUhiJTlKvOVIn6mUtc2rr3jrWZ5OTGGi31+eq+QhVcUS45BWuoEoPc0AhQPn7WtrvRQw==
|
||||||
|
|
||||||
"@cspotcode/source-map-consumer@0.8.0":
|
"@cspotcode/source-map-consumer@0.8.0":
|
||||||
version "0.8.0"
|
version "0.8.0"
|
||||||
|
@ -1026,13 +1026,6 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/koa" "*"
|
"@types/koa" "*"
|
||||||
|
|
||||||
"@types/koa-router@7.4.4":
|
|
||||||
version "7.4.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/koa-router/-/koa-router-7.4.4.tgz#db72bde3616365d74f00178d5f243c4fce7da572"
|
|
||||||
integrity sha512-3dHlZ6CkhgcWeF6wafEUvyyqjWYfKmev3vy1PtOmr0mBc3wpXPU5E8fBBd4YQo5bRpHPfmwC5yDaX7s4jhIN6A==
|
|
||||||
dependencies:
|
|
||||||
"@types/koa" "*"
|
|
||||||
|
|
||||||
"@types/koa@*", "@types/koa@2.13.4":
|
"@types/koa@*", "@types/koa@2.13.4":
|
||||||
version "2.13.4"
|
version "2.13.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.13.4.tgz#10620b3f24a8027ef5cbae88b393d1b31205726b"
|
resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.13.4.tgz#10620b3f24a8027ef5cbae88b393d1b31205726b"
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
import ApplicationApi from "./applications"
|
import ApplicationApi from "./applications"
|
||||||
import AuthApi from "./auth"
|
import AuthApi from "./auth"
|
||||||
import InternalAPIClient from "./InternalAPIClient"
|
import InternalAPIClient from "./InternalAPIClient"
|
||||||
|
import ScreenApi from "./screens"
|
||||||
|
|
||||||
export default class TestConfiguration<T> {
|
export default class TestConfiguration<T> {
|
||||||
applications: ApplicationApi
|
applications: ApplicationApi
|
||||||
auth: AuthApi
|
auth: AuthApi
|
||||||
|
screen: ScreenApi
|
||||||
context: T
|
context: T
|
||||||
|
|
||||||
constructor(apiClient: InternalAPIClient) {
|
constructor(apiClient: InternalAPIClient) {
|
||||||
this.applications = new ApplicationApi(apiClient)
|
this.applications = new ApplicationApi(apiClient)
|
||||||
this.auth = new AuthApi(apiClient)
|
this.auth = new AuthApi(apiClient)
|
||||||
|
this.screen = new ScreenApi(apiClient)
|
||||||
this.context = <T>{}
|
this.context = <T>{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { Screen } from "@budibase/types"
|
||||||
|
import { Response } from "node-fetch"
|
||||||
|
import InternalAPIClient from "./InternalAPIClient"
|
||||||
|
|
||||||
|
export default class ScreenApi {
|
||||||
|
api: InternalAPIClient
|
||||||
|
|
||||||
|
constructor(apiClient: InternalAPIClient) {
|
||||||
|
this.api = apiClient
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(body: any): Promise<[Response, Screen]> {
|
||||||
|
const response = await this.api.post(`/screens`, { body })
|
||||||
|
const json = await response.json()
|
||||||
|
return [response, json]
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(screenId: string, rev: string): Promise<[Response, Screen]> {
|
||||||
|
const response = await this.api.del(`/screens/${screenId}/${rev}`)
|
||||||
|
const json = await response.json()
|
||||||
|
return [response, json]
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,9 +2,11 @@ import generator from "../../generator"
|
||||||
|
|
||||||
const randomId = generator.guid()
|
const randomId = generator.guid()
|
||||||
|
|
||||||
const generateScreen = (): any => ({
|
const generateScreen = (roleId: string): any => ({
|
||||||
showNavigation: true,
|
showNavigation: true,
|
||||||
width: "Large",
|
width: "Large",
|
||||||
|
name: randomId,
|
||||||
|
template: "createFromScratch",
|
||||||
props: {
|
props: {
|
||||||
_id: randomId,
|
_id: randomId,
|
||||||
_component: "@budibase/standard-components/container",
|
_component: "@budibase/standard-components/container",
|
||||||
|
@ -24,11 +26,9 @@ const generateScreen = (): any => ({
|
||||||
},
|
},
|
||||||
routing: {
|
routing: {
|
||||||
route: "/test",
|
route: "/test",
|
||||||
roleId: "BASIC",
|
roleId: roleId,
|
||||||
homeScreen: false,
|
homeScreen: false,
|
||||||
},
|
},
|
||||||
name: randomId,
|
|
||||||
template: "createFromScratch",
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export default generateScreen
|
export default generateScreen
|
||||||
|
|
|
@ -164,7 +164,7 @@ describe("Internal API - /applications endpoints", () => {
|
||||||
|
|
||||||
// Change/add component to the app
|
// Change/add component to the app
|
||||||
const [screenResponse, screen] = await config.applications.addScreentoApp(
|
const [screenResponse, screen] = await config.applications.addScreentoApp(
|
||||||
generateScreen()
|
generateScreen("BASIC")
|
||||||
)
|
)
|
||||||
expect(screenResponse).toHaveStatusCode(200)
|
expect(screenResponse).toHaveStatusCode(200)
|
||||||
expect(screen._id).toBeDefined()
|
expect(screen._id).toBeDefined()
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
import TestConfiguration from "../../../config/internal-api/TestConfiguration"
|
||||||
|
import { App } from "@budibase/types"
|
||||||
|
import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient"
|
||||||
|
import generateApp from "../../../config/internal-api/fixtures/applications"
|
||||||
|
import { Screen } from "@budibase/types"
|
||||||
|
import generateScreen from "../../../config/internal-api/fixtures/screens"
|
||||||
|
|
||||||
|
describe("Internal API - /screens endpoints", () => {
|
||||||
|
const api = new InternalAPIClient()
|
||||||
|
const config = new TestConfiguration<Screen>(api)
|
||||||
|
const appConfig = new TestConfiguration<App>(api)
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.beforeAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await config.afterAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("POST - Create a screen with each role type", async () => {
|
||||||
|
// Create app
|
||||||
|
const [appResponse, app] = await appConfig.applications.create(
|
||||||
|
generateApp()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create Screen
|
||||||
|
const roleArray = ["BASIC", "POWER", "ADMIN", "PUBLIC"]
|
||||||
|
appConfig.applications.api.appId = app.appId
|
||||||
|
for (let role in roleArray) {
|
||||||
|
const [response, screen] = await config.screen.create(
|
||||||
|
generateScreen(roleArray[role])
|
||||||
|
)
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
expect(screen.routing.roleId).toEqual(roleArray[role])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it("GET - Fetch screens", async () => {
|
||||||
|
// Create app
|
||||||
|
const [appResponse, app] = await appConfig.applications.create(
|
||||||
|
generateApp()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create Screen
|
||||||
|
appConfig.applications.api.appId = app.appId
|
||||||
|
const [response, screen] = await config.screen.create(
|
||||||
|
generateScreen("BASIC")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check screen exists
|
||||||
|
const [routesResponse, routes] = await appConfig.applications.getRoutes()
|
||||||
|
expect(routesResponse).toHaveStatusCode(200)
|
||||||
|
expect(routes.routes["/test"]).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("DELETE - Delete a screen", async () => {
|
||||||
|
// Create app
|
||||||
|
const [appResponse, app] = await appConfig.applications.create(
|
||||||
|
generateApp()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create Screen
|
||||||
|
appConfig.applications.api.appId = app.appId
|
||||||
|
const [screenResponse, screen] = await config.screen.create(
|
||||||
|
generateScreen("BASIC")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Delete Screen
|
||||||
|
const [response] = await config.screen.delete(screen._id!, screen._rev!)
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,16 +1,5 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
dir=$(pwd)
|
KEEP="dist|package.json|yarn.lock|client|builder|build|pm2.config.js|docker_run.sh"
|
||||||
declare -a keep=("dist" "package.json" "yarn.lock" "client" "builder" "build" "pm2.config.js" "docker_run.sh")
|
echo "Removing unneeded build files:"
|
||||||
for moveDir in "${keep[@]}"
|
ls | egrep -v $KEEP | xargs rm -rfv
|
||||||
do
|
|
||||||
mv $moveDir / 2>/dev/null
|
|
||||||
done
|
|
||||||
cd /
|
|
||||||
rm -r $dir
|
|
||||||
mkdir $dir
|
|
||||||
for keepDir in "${keep[@]}"
|
|
||||||
do
|
|
||||||
mv /$keepDir $dir/ 2>/dev/null
|
|
||||||
done
|
|
||||||
cd $dir
|
|
||||||
NODE_ENV=production yarn
|
NODE_ENV=production yarn
|
||||||
|
|
Loading…
Reference in New Issue