commit
3991c4b8d0
|
@ -18,7 +18,7 @@ jobs:
|
|||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x]
|
||||
node-version: [14.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
|
|
@ -18,7 +18,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 14.x
|
||||
- run: yarn
|
||||
- run: yarn bootstrap
|
||||
- run: yarn lint
|
||||
|
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 14.x
|
||||
- run: yarn
|
||||
- run: yarn bootstrap
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 14.x
|
||||
- run: yarn
|
||||
- run: yarn bootstrap
|
||||
- run: yarn lint
|
||||
|
@ -55,4 +55,4 @@ jobs:
|
|||
env:
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}
|
||||
BUDIBASE_RELEASE_VERSION: ${{ steps.previoustag.outputs.tag }}
|
||||
BUDIBASE_RELEASE_VERSION: ${{ steps.previoustag.outputs.tag }}
|
||||
|
|
|
@ -3,6 +3,7 @@ builder/*
|
|||
.temp/
|
||||
packages/server/runtime_apps/
|
||||
.idea/
|
||||
bb-airgapped.tar.gz
|
||||
|
||||
# Logs
|
||||
logs
|
||||
|
|
46
README.md
46
README.md
|
@ -54,17 +54,51 @@
|
|||
<br /><br />
|
||||
## ✨ Features
|
||||
|
||||
- **Build and ship real software.** Unlike other platforms, with Budibase you build and ship single page applications. Budibase applications have performance baked in and can be designed responsively, providing your users with a great experience.
|
||||
### Build and ship real software
|
||||
Unlike other platforms, with Budibase you build and ship single page applications. Budibase applications have performance baked in and can be designed responsively, providing your users with a great experience.
|
||||
<br /><br />
|
||||
|
||||
- **Open source and extensible.** Budibase is open-source - licensed as GPL v3. This should fill you with confidence that Budibase will always be around. You can also code against Budibase or fork it and make changes as you please, providing a developer-friendly experience.
|
||||
### Open source and extensible
|
||||
Budibase is open-source - licensed as GPL v3. This should fill you with confidence that Budibase will always be around. You can also code against Budibase or fork it and make changes as you please, providing a developer-friendly experience.
|
||||
<br /><br />
|
||||
|
||||
- **Load data or start from scratch.** Budibase pulls in data from multiple sources, including MongoDB, CouchDB, PostgreSQL, MySQL, Airtable, S3, DynamoDB, or a REST API. And unlike other platforms, with Budibase you can start from scratch and create business apps with no data sources. [Request new data sources](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
|
||||
### Load data or start from scratch
|
||||
Budibase pulls in data from multiple sources, including MongoDB, CouchDB, PostgreSQL, MySQL, Airtable, S3, DynamoDB, or a REST API. And unlike other platforms, with Budibase you can start from scratch and create business apps with no data sources. [Request new data sources](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
|
||||
|
||||
- **Design and build apps with powerful pre-made components.** Budibase comes out of the box with beautifully designed, powerful components which you can use like building blocks to build your UI. We also expose a lot of your favourite CSS styling options so you can go that extra creative mile. [Request new component](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
|
||||
<p align="center">
|
||||
<img alt="Budibase data" src="https://res.cloudinary.com/daog6scxm/image/upload/v1636970242/Out%20of%20beta%20launch/data_n1tlhf.png">
|
||||
</p>
|
||||
<br /><br />
|
||||
|
||||
- **Automate processes, integrate with other tools, and connect to webhooks.** Save time by automating manual processes and workflows. From connecting to webhooks, to automating emails, simply tell Budibase what to do and let it work for you. You can easily [create new automations for Budibase here](https://github.com/Budibase/automations) or [Request new automation](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
|
||||
### Design and build apps with powerful pre-made components
|
||||
|
||||
- **Admin paradise.** Budibase is made to scale. With Budibase, you can self-host on your own infrastructure and globally manage users, onboarding, SMTP, apps, groups, theming and more. You can also provide users/groups with an app portal and disseminate user-management to the group manager.
|
||||
Budibase comes out of the box with beautifully designed, powerful components which you can use like building blocks to build your UI. We also expose a lot of your favourite CSS styling options so you can go that extra creative mile. [Request new component](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
|
||||
|
||||
<p align="center">
|
||||
<img alt="Budibase design" src="https://res.cloudinary.com/daog6scxm/image/upload/v1636970243/Out%20of%20beta%20launch/design-like-a-pro_qhlfeu.gif">
|
||||
</p>
|
||||
<br /><br />
|
||||
|
||||
### Automate processes, integrate with other tools, and connect to webhooks
|
||||
Save time by automating manual processes and workflows. From connecting to webhooks, to automating emails, simply tell Budibase what to do and let it work for you. You can easily [create new automations for Budibase here](https://github.com/Budibase/automations) or [Request new automation](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
|
||||
|
||||
<p align="center">
|
||||
<img alt="Budibase automations" src="https://res.cloudinary.com/daog6scxm/image/upload/v1636970486/Out%20of%20beta%20launch/automation_riro7u.png">
|
||||
</p>
|
||||
<br /><br />
|
||||
|
||||
### Integrate with your favorite tools
|
||||
Budibase integrates with a number of popular tools allowing you to build apps that perfectly fit your stack.
|
||||
|
||||
<p align="center">
|
||||
<img alt="Budibase integrations" src="https://res.cloudinary.com/daog6scxm/image/upload/v1636970242/Out%20of%20beta%20launch/integrations_kc7dqt.png">
|
||||
</p>
|
||||
<br /><br />
|
||||
|
||||
### Admin paradise
|
||||
Budibase is made to scale. With Budibase, you can self-host on your own infrastructure and globally manage users, onboarding, SMTP, apps, groups, theming and more. You can also provide users/groups with an app portal and disseminate user-management to the group manager.
|
||||
|
||||
- Checkout the promo video: https://youtu.be/xoljVpty_Kw
|
||||
|
||||
<br /><br /><br />
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ services:
|
|||
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
|
||||
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
|
||||
MINIO_URL: http://minio-service:9000
|
||||
APPS_URL: http://app-service:4002
|
||||
COUCH_DB_USERNAME: ${COUCH_DB_USER}
|
||||
COUCH_DB_PASSWORD: ${COUCH_DB_PASSWORD}
|
||||
COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984
|
||||
|
|
|
@ -113,6 +113,8 @@ spec:
|
|||
value: {{ .Values.globals.smtp.port | quote }}
|
||||
- name: SMTP_FROM_ADDRESS
|
||||
value: {{ .Values.globals.smtp.from | quote }}
|
||||
- name: APPS_URL
|
||||
value: http://app-service:{{ .Values.services.apps.port }}
|
||||
image: budibase/worker
|
||||
imagePullPolicy: Always
|
||||
name: bbworker
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
const fs = require("fs")
|
||||
const { execSync } = require("child_process")
|
||||
const path = require("path")
|
||||
|
||||
const IMAGES = {
|
||||
worker: "budibase/worker",
|
||||
apps: "budibase/apps",
|
||||
proxy: "envoyproxy/envoy:v1.16-latest",
|
||||
minio: "minio/minio",
|
||||
couch: "ibmcom/couchdb3",
|
||||
curl: "curlimages/curl",
|
||||
redis: "redis",
|
||||
watchtower: "containrrr/watchtower"
|
||||
}
|
||||
|
||||
const FILES = {
|
||||
COMPOSE: "docker-compose.yaml",
|
||||
ENVOY: "envoy.yaml",
|
||||
PROPERTIES: "hosting.properties"
|
||||
}
|
||||
|
||||
const OUTPUT_DIR = path.join(__dirname, "../", "bb-airgapped")
|
||||
|
||||
function copyFile(file) {
|
||||
fs.copyFileSync(
|
||||
path.join(__dirname, "../", "../", file),
|
||||
path.join(OUTPUT_DIR, file)
|
||||
)
|
||||
}
|
||||
|
||||
// create output dir
|
||||
console.log(`Creating ${OUTPUT_DIR} for build..`)
|
||||
fs.rmdirSync(OUTPUT_DIR, { recursive: true })
|
||||
fs.mkdirSync(OUTPUT_DIR)
|
||||
|
||||
// package images into tar files
|
||||
for (let image in IMAGES) {
|
||||
console.log(`Creating tar for ${image}..`)
|
||||
execSync(`docker save ${IMAGES[image]} -o ${OUTPUT_DIR}/${image}.tar`)
|
||||
}
|
||||
|
||||
// copy config files
|
||||
copyFile(FILES.COMPOSE)
|
||||
copyFile(FILES.ENVOY)
|
||||
copyFile(FILES.PROPERTIES)
|
||||
|
||||
// compress
|
||||
execSync(`tar -czf bb-airgapped.tar.gz hosting/scripts/bb-airgapped`)
|
||||
|
||||
// clean up
|
||||
fs.rmdirSync(OUTPUT_DIR, { recursive: true })
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "0.9.189",
|
||||
"version": "0.9.190-alpha.12",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
|
|
|
@ -46,6 +46,8 @@
|
|||
"build:docker": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh $BUDIBASE_RELEASE_VERSION && cd -",
|
||||
"build:docker:selfhost": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh latest && cd -",
|
||||
"build:docker:develop": "node scripts/pinVersions && lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh develop && cd -",
|
||||
"build:docker:airgap": "node hosting/scripts/airgapped/airgappedDockerBuild",
|
||||
"build:docs": "lerna run build:docs",
|
||||
"release:helm": "./scripts/release_helm_chart.sh",
|
||||
"env:multi:enable": "lerna run env:multi:enable",
|
||||
"env:multi:disable": "lerna run env:multi:disable",
|
||||
|
@ -58,6 +60,7 @@
|
|||
"mode:self": "yarn env:selfhost:enable && yarn env:multi:disable && yarn env:account:disable",
|
||||
"mode:cloud": "yarn env:selfhost:disable && yarn env:multi:enable && yarn env:account:disable",
|
||||
"mode:account": "yarn mode:cloud && yarn env:account:enable",
|
||||
"security:audit": "node scripts/audit.js",
|
||||
"postinstall": "husky install"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
module.exports = {
|
||||
user: require("./src/cache/user"),
|
||||
app: require("./src/cache/appMetadata"),
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/auth",
|
||||
"version": "0.9.189",
|
||||
"version": "0.9.190-alpha.12",
|
||||
"description": "Authentication middlewares for budibase builder and apps",
|
||||
"main": "src/index.js",
|
||||
"author": "Budibase",
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
const redis = require("../redis/authRedis")
|
||||
const { getCouch } = require("../db")
|
||||
const { DocumentTypes } = require("../db/constants")
|
||||
|
||||
const AppState = {
|
||||
INVALID: "invalid",
|
||||
}
|
||||
const EXPIRY_SECONDS = 3600
|
||||
|
||||
/**
|
||||
* The default populate app metadata function
|
||||
*/
|
||||
const populateFromDB = async (appId, CouchDB = null) => {
|
||||
if (!CouchDB) {
|
||||
CouchDB = getCouch()
|
||||
}
|
||||
const db = new CouchDB(appId, { skip_setup: true })
|
||||
return db.get(DocumentTypes.APP_METADATA)
|
||||
}
|
||||
|
||||
const isInvalid = metadata => {
|
||||
return !metadata || metadata.state === AppState.INVALID
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the requested app metadata by id.
|
||||
* Use redis cache to first read the app metadata.
|
||||
* If not present fallback to loading the app metadata directly and re-caching.
|
||||
* @param {string} appId the id of the app to get metadata from.
|
||||
* @param {object} CouchDB the database being passed
|
||||
* @returns {object} the app metadata.
|
||||
*/
|
||||
exports.getAppMetadata = async (appId, CouchDB = null) => {
|
||||
const client = await redis.getAppClient()
|
||||
// try cache
|
||||
let metadata = await client.get(appId)
|
||||
if (!metadata) {
|
||||
let expiry = EXPIRY_SECONDS
|
||||
try {
|
||||
metadata = await populateFromDB(appId, CouchDB)
|
||||
} catch (err) {
|
||||
// app DB left around, but no metadata, it is invalid
|
||||
if (err && err.status === 404) {
|
||||
metadata = { state: AppState.INVALID }
|
||||
// don't expire the reference to an invalid app, it'll only be
|
||||
// updated if a metadata doc actually gets stored (app is remade/reverted)
|
||||
expiry = undefined
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
// needed for cypress/some scenarios where the caching happens
|
||||
// so quickly the requests can get slightly out of sync
|
||||
// might store its invalid just before it stores its valid
|
||||
if (isInvalid(metadata)) {
|
||||
const temp = await client.get(appId)
|
||||
if (temp) {
|
||||
metadata = temp
|
||||
}
|
||||
}
|
||||
await client.store(appId, metadata, expiry)
|
||||
}
|
||||
// we've stored in the cache an object to tell us that it is currently invalid
|
||||
if (isInvalid(metadata)) {
|
||||
throw { status: 404, message: "No app metadata found" }
|
||||
}
|
||||
return metadata
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate/reset the cached metadata when a change occurs in the db.
|
||||
* @param appId {string} the cache key to bust/update.
|
||||
* @param newMetadata {object|undefined} optional - can simply provide the new metadata to update with.
|
||||
* @return {Promise<void>} will respond with success when cache is updated.
|
||||
*/
|
||||
exports.invalidateAppMetadata = async (appId, newMetadata = null) => {
|
||||
if (!appId) {
|
||||
throw "Cannot invalidate if no app ID provided."
|
||||
}
|
||||
const client = await redis.getAppClient()
|
||||
await client.delete(appId)
|
||||
if (newMetadata) {
|
||||
await client.store(appId, newMetadata, EXPIRY_SECONDS)
|
||||
}
|
||||
}
|
|
@ -1,11 +1,14 @@
|
|||
const { newid } = require("../hashing")
|
||||
const Replication = require("./Replication")
|
||||
const { DEFAULT_TENANT_ID } = require("../constants")
|
||||
const { DEFAULT_TENANT_ID, Configs } = require("../constants")
|
||||
const env = require("../environment")
|
||||
const { StaticDatabases, SEPARATOR, DocumentTypes } = require("./constants")
|
||||
const { getTenantId, getTenantIDFromAppID } = require("../tenancy")
|
||||
const fetch = require("node-fetch")
|
||||
const { getCouch } = require("./index")
|
||||
const { getAppMetadata } = require("../cache/appMetadata")
|
||||
|
||||
const NO_APP_ERROR = "No app provided"
|
||||
|
||||
const UNICODE_MAX = "\ufff0"
|
||||
|
||||
|
@ -45,14 +48,23 @@ function getDocParams(docType, docId = null, otherProps = {}) {
|
|||
}
|
||||
|
||||
exports.isDevAppID = appId => {
|
||||
if (!appId) {
|
||||
throw NO_APP_ERROR
|
||||
}
|
||||
return appId.startsWith(exports.APP_DEV_PREFIX)
|
||||
}
|
||||
|
||||
exports.isProdAppID = appId => {
|
||||
if (!appId) {
|
||||
throw NO_APP_ERROR
|
||||
}
|
||||
return appId.startsWith(exports.APP_PREFIX) && !exports.isDevAppID(appId)
|
||||
}
|
||||
|
||||
function isDevApp(app) {
|
||||
if (!app) {
|
||||
throw NO_APP_ERROR
|
||||
}
|
||||
return exports.isDevAppID(app.appId)
|
||||
}
|
||||
|
||||
|
@ -152,6 +164,17 @@ exports.getDeployedAppID = appId => {
|
|||
return appId
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a deployed app ID to a development app ID.
|
||||
*/
|
||||
exports.getDevelopmentAppID = appId => {
|
||||
if (!appId.startsWith(exports.APP_DEV_PREFIX)) {
|
||||
const id = appId.split(exports.APP_PREFIX)[1]
|
||||
return `${exports.APP_DEV_PREFIX}${id}`
|
||||
}
|
||||
return appId
|
||||
}
|
||||
|
||||
exports.getCouchUrl = () => {
|
||||
if (!env.COUCH_DB_URL) return
|
||||
|
||||
|
@ -221,16 +244,16 @@ exports.getAllApps = async (CouchDB, { dev, all, idsOnly } = {}) => {
|
|||
if (idsOnly) {
|
||||
return appDbNames
|
||||
}
|
||||
const appPromises = appDbNames.map(db =>
|
||||
const appPromises = appDbNames.map(app =>
|
||||
// skip setup otherwise databases could be re-created
|
||||
new CouchDB(db, { skip_setup: true }).get(DocumentTypes.APP_METADATA)
|
||||
getAppMetadata(app, CouchDB)
|
||||
)
|
||||
if (appPromises.length === 0) {
|
||||
return []
|
||||
} else {
|
||||
const response = await Promise.allSettled(appPromises)
|
||||
const apps = response
|
||||
.filter(result => result.status === "fulfilled")
|
||||
.filter(result => result.status === "fulfilled" && result.value != null)
|
||||
.map(({ value }) => value)
|
||||
if (!all) {
|
||||
return apps.filter(app => {
|
||||
|
@ -248,6 +271,24 @@ exports.getAllApps = async (CouchDB, { dev, all, idsOnly } = {}) => {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function for getAllApps but filters to production apps only.
|
||||
*/
|
||||
exports.getDeployedAppIDs = async CouchDB => {
|
||||
return (await exports.getAllApps(CouchDB, { idsOnly: true })).filter(
|
||||
id => !exports.isDevAppID(id)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function for the inverse of above.
|
||||
*/
|
||||
exports.getDevAppIDs = async CouchDB => {
|
||||
return (await exports.getAllApps(CouchDB, { idsOnly: true })).filter(id =>
|
||||
exports.isDevAppID(id)
|
||||
)
|
||||
}
|
||||
|
||||
exports.dbExists = async (CouchDB, dbName) => {
|
||||
let exists = false
|
||||
try {
|
||||
|
@ -322,13 +363,50 @@ const getScopedFullConfig = async function (db, { type, user, workspace }) {
|
|||
}
|
||||
|
||||
// Find the config with the most granular scope based on context
|
||||
const scopedConfig = response.rows.sort(
|
||||
let scopedConfig = response.rows.sort(
|
||||
(a, b) => determineScore(a) - determineScore(b)
|
||||
)[0]
|
||||
|
||||
// custom logic for settings doc
|
||||
// always provide the platform URL
|
||||
if (type === Configs.SETTINGS) {
|
||||
if (scopedConfig && scopedConfig.doc) {
|
||||
scopedConfig.doc.config.platformUrl = await getPlatformUrl(
|
||||
scopedConfig.doc.config
|
||||
)
|
||||
} else {
|
||||
scopedConfig = {
|
||||
doc: {
|
||||
config: {
|
||||
platformUrl: await getPlatformUrl(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return scopedConfig && scopedConfig.doc
|
||||
}
|
||||
|
||||
const getPlatformUrl = async settings => {
|
||||
let platformUrl = env.PLATFORM_URL
|
||||
|
||||
if (!env.SELF_HOSTED && env.MULTI_TENANCY) {
|
||||
// cloud and multi tenant - add the tenant to the default platform url
|
||||
const tenantId = getTenantId()
|
||||
if (!platformUrl.includes("localhost:")) {
|
||||
platformUrl = platformUrl.replace("://", `://${tenantId}.`)
|
||||
}
|
||||
} else {
|
||||
// self hosted - check for platform url override
|
||||
if (settings && settings.platformUrl) {
|
||||
platformUrl = settings.platformUrl
|
||||
}
|
||||
}
|
||||
|
||||
return platformUrl ? platformUrl : "http://localhost:10000"
|
||||
}
|
||||
|
||||
async function getScopedConfig(db, params) {
|
||||
const configDoc = await getScopedFullConfig(db, params)
|
||||
return configDoc && configDoc.config ? configDoc.config : configDoc
|
||||
|
|
|
@ -25,6 +25,7 @@ module.exports = {
|
|||
DISABLE_ACCOUNT_PORTAL: process.env.DISABLE_ACCOUNT_PORTAL,
|
||||
SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED),
|
||||
COOKIE_DOMAIN: process.env.COOKIE_DOMAIN,
|
||||
PLATFORM_URL: process.env.PLATFORM_URL,
|
||||
isTest,
|
||||
_set(key, value) {
|
||||
process.env[key] = value
|
||||
|
|
|
@ -92,6 +92,10 @@ module.exports = (
|
|||
finalise(ctx, { authenticated, user, internal, version, publicEndpoint })
|
||||
return next()
|
||||
} catch (err) {
|
||||
// invalid token, clear the cookie
|
||||
if (err && err.name === "JsonWebTokenError") {
|
||||
clearCookie(ctx, Cookies.Auth)
|
||||
}
|
||||
// allow configuring for public access
|
||||
if ((opts && opts.publicAllowed) || publicEndpoint) {
|
||||
finalise(ctx, { authenticated: false, version, publicEndpoint })
|
||||
|
|
|
@ -6,6 +6,7 @@ exports.ObjectStoreBuckets = {
|
|||
APPS: "prod-budi-app-assets",
|
||||
TEMPLATES: "templates",
|
||||
GLOBAL: "global",
|
||||
GLOBAL_CLOUD: "prod-budi-tenant-uploads",
|
||||
}
|
||||
|
||||
exports.budibaseTempDir = function () {
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
const Client = require("./index")
|
||||
const utils = require("./utils")
|
||||
|
||||
let userClient, sessionClient
|
||||
let userClient, sessionClient, appClient
|
||||
|
||||
async function init() {
|
||||
userClient = await new Client(utils.Databases.USER_CACHE).init()
|
||||
sessionClient = await new Client(utils.Databases.SESSIONS).init()
|
||||
appClient = await new Client(utils.Databases.APP_METADATA).init()
|
||||
}
|
||||
|
||||
process.on("exit", async () => {
|
||||
if (userClient) await userClient.finish()
|
||||
if (sessionClient) await sessionClient.finish()
|
||||
if (appClient) await appClient.finish()
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
|
@ -26,4 +28,10 @@ module.exports = {
|
|||
}
|
||||
return sessionClient
|
||||
},
|
||||
getAppClient: async () => {
|
||||
if (!appClient) {
|
||||
await init()
|
||||
}
|
||||
return appClient
|
||||
},
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ exports.Databases = {
|
|||
SESSIONS: "session",
|
||||
USER_CACHE: "users",
|
||||
FLAGS: "flags",
|
||||
APP_METADATA: "appMetadata",
|
||||
}
|
||||
|
||||
exports.SEPARATOR = SEPARATOR
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/bbui",
|
||||
"description": "A UI solution used in the different Budibase projects.",
|
||||
"version": "0.9.189",
|
||||
"version": "0.9.190-alpha.12",
|
||||
"license": "MPL-2.0",
|
||||
"svelte": "src/index.js",
|
||||
"module": "dist/bbui.es.js",
|
||||
|
@ -51,6 +51,7 @@
|
|||
"@spectrum-css/fieldlabel": "^3.0.1",
|
||||
"@spectrum-css/icon": "^3.0.1",
|
||||
"@spectrum-css/illustratedmessage": "^3.0.2",
|
||||
"@spectrum-css/inlinealert": "^2.0.1",
|
||||
"@spectrum-css/inputgroup": "^3.0.2",
|
||||
"@spectrum-css/label": "^2.0.10",
|
||||
"@spectrum-css/link": "^3.1.1",
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
export let value = null
|
||||
export let label = undefined
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let labelPosition = "above"
|
||||
export let error = null
|
||||
export let placeholder = "Choose an option or type"
|
||||
|
@ -33,8 +34,11 @@
|
|||
{value}
|
||||
{options}
|
||||
{placeholder}
|
||||
{readonly}
|
||||
{getOptionLabel}
|
||||
{getOptionValue}
|
||||
on:change={onChange}
|
||||
on:pick
|
||||
on:type
|
||||
/>
|
||||
</Field>
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
<label
|
||||
class="spectrum-Checkbox spectrum-Checkbox--emphasized {sizeClass}"
|
||||
class:is-invalid={!!error}
|
||||
class:checked={value}
|
||||
>
|
||||
<input
|
||||
checked={value}
|
||||
|
@ -50,6 +51,16 @@
|
|||
</label>
|
||||
|
||||
<style>
|
||||
.spectrum-Checkbox--sizeL .spectrum-Checkbox-checkmark {
|
||||
transform: scale(1.1);
|
||||
left: 55%;
|
||||
top: 55%;
|
||||
}
|
||||
.spectrum-Checkbox--sizeXL .spectrum-Checkbox-checkmark {
|
||||
transform: scale(1.2);
|
||||
left: 60%;
|
||||
top: 60%;
|
||||
}
|
||||
.spectrum-Checkbox-input {
|
||||
opacity: 0;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
export let id = null
|
||||
export let placeholder = "Choose an option or type"
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let error = null
|
||||
export let options = []
|
||||
export let getOptionLabel = option => option
|
||||
|
@ -40,8 +41,15 @@
|
|||
open = false
|
||||
}
|
||||
|
||||
const onChange = e => {
|
||||
selectOption(e.target.value)
|
||||
const onType = e => {
|
||||
const value = e.target.value
|
||||
dispatch("type", value)
|
||||
selectOption(value)
|
||||
}
|
||||
|
||||
const onPick = value => {
|
||||
dispatch("pick", value)
|
||||
selectOption(value)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -62,10 +70,11 @@
|
|||
type="text"
|
||||
on:focus={() => (focus = true)}
|
||||
on:blur={() => (focus = false)}
|
||||
on:change={onChange}
|
||||
on:change={onType}
|
||||
value={value || ""}
|
||||
placeholder={placeholder || ""}
|
||||
{disabled}
|
||||
{readonly}
|
||||
class="spectrum-Textfield-input spectrum-InputGroup-input"
|
||||
/>
|
||||
</div>
|
||||
|
@ -99,7 +108,7 @@
|
|||
role="option"
|
||||
aria-selected="true"
|
||||
tabindex="0"
|
||||
on:click={() => selectOption(getOptionValue(option))}
|
||||
on:click={() => onPick(getOptionValue(option))}
|
||||
>
|
||||
<span class="spectrum-Menu-itemLabel"
|
||||
>{getOptionLabel(option)}</span
|
||||
|
|
|
@ -11,19 +11,20 @@
|
|||
export let readonly = false
|
||||
export let updateOnChange = true
|
||||
export let quiet = false
|
||||
export let dataCy
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let focus = false
|
||||
|
||||
const updateValue = value => {
|
||||
const updateValue = newValue => {
|
||||
if (readonly) {
|
||||
return
|
||||
}
|
||||
if (type === "number") {
|
||||
const float = parseFloat(value)
|
||||
value = isNaN(float) ? null : float
|
||||
const float = parseFloat(newValue)
|
||||
newValue = isNaN(float) ? null : float
|
||||
}
|
||||
dispatch("change", value)
|
||||
dispatch("change", newValue)
|
||||
}
|
||||
|
||||
const onFocus = () => {
|
||||
|
@ -78,6 +79,7 @@
|
|||
{disabled}
|
||||
{readonly}
|
||||
{id}
|
||||
data-cy={dataCy}
|
||||
value={value || ""}
|
||||
placeholder={placeholder || ""}
|
||||
on:click
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
export let error = null
|
||||
export let updateOnChange = true
|
||||
export let quiet = false
|
||||
export let dataCy
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const onChange = e => {
|
||||
|
@ -23,6 +24,7 @@
|
|||
|
||||
<Field {label} {labelPosition} {error}>
|
||||
<TextField
|
||||
{dataCy}
|
||||
{updateOnChange}
|
||||
{error}
|
||||
{disabled}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
<script>
|
||||
import "@spectrum-css/inlinealert/dist/index-vars.css"
|
||||
import Button from "../Button/Button.svelte"
|
||||
|
||||
export let type = "info"
|
||||
export let header = ""
|
||||
export let message = ""
|
||||
export let onConfirm = undefined
|
||||
|
||||
$: icon = selectIcon(type)
|
||||
// if newlines used, convert them to different elements
|
||||
$: split = message.split("\n")
|
||||
|
||||
function selectIcon(alertType) {
|
||||
switch (alertType) {
|
||||
case "error":
|
||||
case "negative":
|
||||
return "Alert"
|
||||
case "success":
|
||||
return "CheckmarkCircle"
|
||||
case "help":
|
||||
return "Help"
|
||||
default:
|
||||
return "Info"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="spectrum-InLineAlert spectrum-InLineAlert--{type}">
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-InLineAlert-icon"
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<use xlink:href="#spectrum-icon-18-{icon}" />
|
||||
</svg>
|
||||
<div class="spectrum-InLineAlert-header">{header}</div>
|
||||
{#each split as splitMsg}
|
||||
<div class="spectrum-InLineAlert-content">{splitMsg}</div>
|
||||
{/each}
|
||||
{#if onConfirm}
|
||||
<div class="spectrum-InLineAlert-footer">
|
||||
<Button secondary on:click={onConfirm}>OK</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.spectrum-InLineAlert {
|
||||
--spectrum-semantic-negative-border-color: #e34850;
|
||||
--spectrum-semantic-positive-border-color: #2d9d78;
|
||||
--spectrum-semantic-positive-icon-color: #2d9d78;
|
||||
--spectrum-semantic-negative-icon-color: #e34850;
|
||||
min-width: 100px;
|
||||
}
|
||||
</style>
|
|
@ -1,16 +1,67 @@
|
|||
<script>
|
||||
import "@spectrum-css/fieldlabel/dist/index-vars.css"
|
||||
import Tooltip from "../Tooltip/Tooltip.svelte"
|
||||
import Icon from "../Icon/Icon.svelte"
|
||||
|
||||
export let size = "M"
|
||||
export let tooltip = ""
|
||||
export let showTooltip = false
|
||||
</script>
|
||||
|
||||
<label for="" class={`spectrum-FieldLabel spectrum-FieldLabel--size${size}`}>
|
||||
<slot />
|
||||
</label>
|
||||
{#if tooltip}
|
||||
<div class="container">
|
||||
<label
|
||||
for=""
|
||||
class={`spectrum-FieldLabel spectrum-FieldLabel--size${size}`}
|
||||
>
|
||||
<slot />
|
||||
</label>
|
||||
<div class="icon-container">
|
||||
<div
|
||||
class="icon"
|
||||
on:mouseover={() => (showTooltip = true)}
|
||||
on:mouseleave={() => (showTooltip = false)}
|
||||
>
|
||||
<Icon name="InfoOutline" size="S" disabled={true} />
|
||||
</div>
|
||||
{#if showTooltip}
|
||||
<div class="tooltip">
|
||||
<Tooltip textWrapping={true} direction={"bottom"} text={tooltip} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<label for="" class={`spectrum-FieldLabel spectrum-FieldLabel--size${size}`}>
|
||||
<slot />
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
label {
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
}
|
||||
.icon-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 1px;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.tooltip {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
top: 15px;
|
||||
z-index: 1;
|
||||
width: 160px;
|
||||
}
|
||||
.icon {
|
||||
transform: scale(0.75);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -3,12 +3,22 @@
|
|||
|
||||
export let direction = "top"
|
||||
export let text = ""
|
||||
export let textWrapping = false
|
||||
</script>
|
||||
|
||||
<span class="u-tooltip-showOnHover tooltip">
|
||||
<slot />
|
||||
<div class={`spectrum-Tooltip spectrum-Tooltip--${direction}`}>
|
||||
<!-- Showing / Hiding a text wrapped tooltip should be handled outside the component -->
|
||||
{#if textWrapping}
|
||||
<span class="spectrum-Tooltip spectrum-Tooltip--{direction} is-open">
|
||||
<span class="spectrum-Tooltip-label">{text}</span>
|
||||
<span class="spectrum-Tooltip-tip" />
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
{:else}
|
||||
<!-- The default show on hover tooltip does not support text wrapping -->
|
||||
<span class="u-tooltip-showOnHover tooltip">
|
||||
<slot />
|
||||
<div class={`spectrum-Tooltip spectrum-Tooltip--${direction}`}>
|
||||
<span class="spectrum-Tooltip-label">{text}</span>
|
||||
<span class="spectrum-Tooltip-tip" />
|
||||
</div>
|
||||
</span>
|
||||
{/if}
|
||||
|
|
|
@ -58,6 +58,7 @@ export { default as Pagination } from "./Pagination/Pagination.svelte"
|
|||
export { default as Badge } from "./Badge/Badge.svelte"
|
||||
export { default as StatusLight } from "./StatusLight/StatusLight.svelte"
|
||||
export { default as ColorPicker } from "./ColorPicker/ColorPicker.svelte"
|
||||
export { default as InlineAlert } from "./InlineAlert/InlineAlert.svelte"
|
||||
|
||||
// Typography
|
||||
export { default as Body } from "./Typography/Body.svelte"
|
||||
|
|
|
@ -136,6 +136,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@spectrum-css/illustratedmessage/-/illustratedmessage-3.0.2.tgz#6a480be98b027e050b086e7899e40d87adb0a8c0"
|
||||
integrity sha512-dqnE8X27bGcO0HN8+dYx8O4o0dNNIAqeivOzDHhe2El+V4dTzMrNIerF6G0NLm3GjVf6XliwmitsZK+K6FmbtA==
|
||||
|
||||
"@spectrum-css/inlinealert@^2.0.1":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@spectrum-css/inlinealert/-/inlinealert-2.0.1.tgz#7521f88f6c845806403cc7d925773c7414e204a2"
|
||||
integrity sha512-Xy5RCOwgurqUXuGQCsEDUduDd5408bmEpmFg+feynG7VFUgLFZWBeylSENB/OqjlFtO76PHXNVdHkhDscPIHTA==
|
||||
|
||||
"@spectrum-css/inputgroup@^3.0.2":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@spectrum-css/inputgroup/-/inputgroup-3.0.2.tgz#f1b13603832cbd22394f3d898af13203961f8691"
|
||||
|
|
|
@ -0,0 +1,290 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 26.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 300 300" style="enable-background:new 0 0 300 300;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:url(#Path_1317_00000103227440483474435190000016933188118935327121_);}
|
||||
.st1{fill:url(#Path_1318_00000101104841355089154840000006956526578009727927_);}
|
||||
.st2{fill:url(#Path_1319_00000075161440626185692110000014996044328070588571_);}
|
||||
.st3{fill:#C1CAE8;}
|
||||
.st4{fill:#7A8BB6;}
|
||||
.st5{fill:#030404;}
|
||||
</style>
|
||||
<g id="Group_143" transform="translate(-862 -389)">
|
||||
<g id="Component_4_1" transform="translate(862 389)">
|
||||
|
||||
<linearGradient id="Path_1317_00000031906008897315583580000011933716758006396041_" gradientUnits="userSpaceOnUse" x1="-47.7766" y1="297.132" x2="-45.9378" y2="297.132" gradientTransform="matrix(158.712 0 0 -159.1408 7586.6768 47435.5273)">
|
||||
<stop offset="0" style="stop-color:#7DC8DA"/>
|
||||
<stop offset="0.992" style="stop-color:#E67FB0"/>
|
||||
</linearGradient>
|
||||
<path id="Path_1317" style="fill:url(#Path_1317_00000031906008897315583580000011933716758006396041_);" d="M295.5,141
|
||||
C290.9,60.5,222-1,141.5,3.6C67.5,7.9,8.4,66.9,4.2,141l0,0v0.1C4,143.8,4,146.5,4,149.3c0,2.8,0.1,6,0.1,6L4.2,296h291.6V146.5
|
||||
L295.5,141z"/>
|
||||
|
||||
<linearGradient id="Path_1318_00000022543908329791846970000010277972945691778971_" gradientUnits="userSpaceOnUse" x1="553.9203" y1="-1379.7488" x2="555.7591" y2="-1379.7488" gradientTransform="matrix(0.6482 0 0 -0.1118 -97.2294 -70.4447)">
|
||||
<stop offset="6.000000e-03" style="stop-color:#E6A480"/>
|
||||
<stop offset="0.998" style="stop-color:#F5B463"/>
|
||||
</linearGradient>
|
||||
<path id="Path_1318" style="fill:url(#Path_1318_00000022543908329791846970000010277972945691778971_);" d="M263,83.9
|
||||
c-1.7-0.3-1.2-0.2-0.7-0.1c0.2,0,0.4,0.1,0.6,0.1L263,83.9z"/>
|
||||
|
||||
<linearGradient id="Path_1319_00000018948029474416481050000007780147372085156030_" gradientUnits="userSpaceOnUse" x1="530.262" y1="-1661.0725" x2="532.1008" y2="-1661.0725" gradientTransform="matrix(0.6484 0 0 -0.1118 -115.4 -32.9427)">
|
||||
<stop offset="6.000000e-03" style="stop-color:#E6A480"/>
|
||||
<stop offset="0.998" style="stop-color:#F5B463"/>
|
||||
</linearGradient>
|
||||
<path id="Path_1319" style="fill:url(#Path_1319_00000018948029474416481050000007780147372085156030_);" d="M229.6,152.9
|
||||
c-1.7-0.3-1.2-0.2-0.7-0.1c0.2,0,0.4,0.1,0.6,0.1L229.6,152.9z"/>
|
||||
<path id="Path_1321" class="st3" d="M239,272.3c-0.1-2.1-0.1-4.8-0.5-7.4c0.1-0.2,0.1-0.4,0.2-0.7c-0.1,0-0.2-0.1-0.3-0.1
|
||||
c-0.1-1-0.2-1.9-0.4-2.9v-95.7c-0.2-4-0.4-8-0.6-12.1c0.3-0.2,0.5-0.6,0.5-0.9l0.6-13.1c-5.8,0.3-11.6-0.1-17.3,0
|
||||
c-3.8,0.1-7.6,0-11.5-0.2c-3.5-0.4-7-0.3-10.5,0.3c-1,0.2-2-0.5-2.2-1.5c0-0.2,0-0.5,0-0.7c0-0.4,0-0.8,0-1.2
|
||||
c0.4-8.2-0.6-16.5-2.7-24.4c-1.9-6.6-5.2-12.8-9.6-18c-1.2-0.9-2.5-1.8-3.8-2.6c-8.3-5.4-17.9-8.3-27.8-8.3
|
||||
c-0.5,0-0.9-0.1-1.3-0.4c-3.9,0.4-7.8,1.3-11.5,2.6c-4.1,1.4-7.9,3.3-11.5,5.6c-4.5,3.6-8.2,8.2-10.7,13.4
|
||||
c-2.5,5.6-2.9,11.9-4.4,17.8c-0.1,0.3-0.2,0.6-0.5,0.8c-1,8.6-1.2,17.2-0.7,25.8c0.1,0.1,0.1,0.1,0.2,0.2c0.1,0.1,0.2,0.3,0.3,0.4
|
||||
c0,0,0,0.1,0.1,0.1c0,0.1,0.1,0.1,0.1,0.2c0.1,0.2,0.2,0.3,0.2,0.5l0.1,0.5c0,0.3-0.1,0.6-0.2,0.9l-0.3,0.4
|
||||
c-0.2,0.2-0.4,0.3-0.6,0.4c-0.2,0.1-0.5,0.2-0.7,0.2c-0.2,0-0.4,0-0.5-0.1c-1.8,2.6-0.3,6.7-2.3,9.6c-0.8,1.2-1.7,2.2-2.5,3.3
|
||||
c0,0.4-0.1,0.8-0.3,1.1c-0.9,1.3-1.8,2.6-2.7,4c-0.4,0.7-0.9,1.4-1.3,2.1c-7.5,12-13,25.7-17.8,38.8c0,0,0,0.1,0,0.1
|
||||
c0.1,0.1,0.2,0.1,0.3,0.2c-0.1,0.2-0.3,0.4-0.4,0.6c-0.2,0.3-0.3,0.6-0.4,1c-1.4-0.3-2.9-0.6-4.3-0.9c-6.5-2.1-13-4-19.6-5.7
|
||||
c-0.2,0-0.3-0.1-0.5-0.1c-3.1-1-6.2-1.9-9.3-2.7l-6.7-2c0-0.1-0.1-0.1-0.1-0.2c-0.3-0.6-1.1-0.8-1.7-0.4c-0.6,0.3-0.8,1.1-0.4,1.7
|
||||
l0.1,0.1c0,0.4,0.3,0.8,0.7,1c1,1.4,1.9,2.9,2.8,4.4c3.9,6.5,6.8,13.5,8.6,20.8c0.2,0.6,0.9,1,1.5,0.8c0.3-0.1,0.6-0.3,0.7-0.6
|
||||
c2,4.6,3.9,9.1,5.9,13.7c0.2,0.4,0.6,0.6,1,0.6c0.2,0.4,0.4,0.9,0.7,1.2c1,1,2.3,1.6,3.7,1.9l8,2.4c0.3,0.5,0.7,0.8,1.2,1
|
||||
c0.4,0.1,0.8,0.3,1.1,0.4l-0.6,1.2c-0.6,1-0.2,2.4,0.8,3c0,0,0.1,0,0.1,0.1c0.2,0.2,0.5,0.4,0.7,0.6c0.9,0.4,1.9,0.7,2.9,0.9
|
||||
l9.5,3.8c0.6,0.3,1.2,0.2,1.7-0.1c1.8,0.6,3.6,0.8,4.5-1c0.2-0.4,0.3-0.8,0.3-1.2c2.9,1,5.8,2,8.7,3c0.1,5.3,0.2,10.6,0.2,15.8
|
||||
c0,1.3,0.4,2.6,1.2,3.7c0,1.1-0.1,2.3-0.1,3.4c0,0.3-0.6,9.5-0.7,12.1H129c0.2,0,0.5,0,0.7,0.1h37.5c0.1,0,0.1-0.1,0.2-0.1
|
||||
c0.9,0,1.8-0.1,2.8-0.1c0.2-0.1,0.4-0.1,0.7-0.1h49.3c0.2,0.1,0.5,0.1,0.7,0.1h16.1c0.2,0.1,0.4,0.1,0.6,0.1
|
||||
c0.3,0,0.5-0.1,0.7-0.2c0.9-0.3,1.4-1.3,1.1-2.2c0-0.1,0-0.1-0.1-0.2v-4.4c0-0.1,0-0.2,0-0.3c0.1-0.2,0.2-0.4,0.2-0.6
|
||||
C240.7,282.7,239.2,277.1,239,272.3z"/>
|
||||
<path id="Path_1322" class="st4" d="M212,159.8c-0.8-3.4-3.7-5.3-4.6-8.4c-0.6-2,0.2-4.2,0.1-6.2c-0.1-1-0.4-1.9-0.9-2.7
|
||||
c-0.4-0.6-0.9-1.3-0.6-2.1c0.4-1.2-1.5-1.7-1.9-0.5c-0.1,0.2-0.1,0.3-0.1,0.5c-0.5-0.3-1.1-0.2-1.4,0.3c0,0,0,0,0,0
|
||||
c0,0-0.1,0-0.1,0c-0.1-0.7-0.7-1.3-1.4-1.5c-0.4-0.1-0.9,0.1-1.1,0.4l-0.1,0.2c-0.1-0.1-0.3-0.1-0.5,0c-0.5,0.1-0.9,0.4-1.3,0.8
|
||||
c-0.2,0.4-0.3,0.9-0.3,1.4c0,0.2,0,0.3,0,0.5c0,0.9,0.5,1.7,1.4,2c0.2,0.1,0.5,0.1,0.7,0.1c0,0.1,0.1,0.2,0.1,0.3c0,0,0,0.1,0,0.1
|
||||
c-0.1,0.1-0.1,0.2-0.2,0.4c-0.1,0.3-0.1,0.7-0.1,1c-0.1-0.1-0.3-0.1-0.4-0.2c0-0.2-0.1-0.4-0.2-0.6c-0.3-0.4-0.8-0.6-1.3-0.4
|
||||
l-0.1,0c0-0.1,0-0.1,0-0.2c-0.2-2-0.2-3.9,0.1-5.9c0.2-1.3-1.4-1.7-2.3-1.1c0.1-1.5,0.3-3,0.4-4.4c0.2-1.9,0.3-3.8,0.4-5.7
|
||||
c0-0.8,0-1.6-0.1-2.4c0-1,0-2-0.1-3c-0.1-1.3-0.4-2.5-0.6-3.8c-0.1-0.5-0.8-0.3-0.9,0.1c-0.2,1.3-0.4,2.6-0.5,4
|
||||
c-0.1,1.2-0.2,2.4-0.3,3.7c-0.1,2.5-0.4,5.1-0.8,7.6c-0.8,5-2,10-3.5,14.8c-0.1,0.2-0.1,0.4,0,0.6c0.4-0.3,0.9-0.6,1.3-0.9
|
||||
c0.6-0.5,1.6-0.4,2.1,0.2c0,0,0,0,0,0.1h0.3c0,0,0.1,0,0.1,0c0.2,0,0.4,0.1,0.6,0.1l0.1,0l0.1,0l0.2,0.1l0.1,0
|
||||
c-0.1-0.1-0.1,0,0,0.1c0,0,0,0,0,0.1c0,0.1,0,0.1,0,0l0,0.1l0,0.1c0-0.1,0-0.1,0,0c0,1.1,0,2.1-0.2,3.2c-0.2,1.2-0.4,2.4-0.6,3.5
|
||||
c-0.1,0.3,0,0.6,0,0.8c-2,0.9-4,1.8-6,2.6c-12.4,4.8-25.6,7.5-39,8c-2.7,0.4-5.4,0.7-8.1,0.9c-7.5,0.4-15.8,0.1-22.8-3.1h-0.2
|
||||
c-0.3,0-0.6,0.3-0.6,0.6c0,0.2,0.1,0.4,0.3,0.5c2.7,1.2,5.5,2,8.4,2.4c2.9,0.5,5.8,1,8.7,1.3c5.9,0.6,11.7,0.7,17.6,0.3
|
||||
c11.8-0.7,23.4-3.1,34.6-7.1c3.2-1.2,6.3-2.4,9.4-3.9c1.5-0.7,3-1.4,4.4-2.2c0.5-0.3,1-0.5,1.5-0.8c1.5,0.4,2.9,0.9,4.2,1.7
|
||||
c0.4,0.5,0.9,0.9,1.5,1.2c0.5,0.2,1,0.4,1.4,0.7c0.1,0,0.1,0.1,0.2,0.1l0,0c0.1,0.1,0.2,0.2,0.3,0.2l0,0c0,0,0.1,0.1,0.1,0.1
|
||||
c0,0,0,0.1,0.1,0.2c0.1,0.4,0.4,0.7,0.8,0.7c0,0.2,0.2,0.4,0.4,0.6c0.2,0,0.5,0,0.7,0.1c0.8,0.3,1.5,0.7,2.1,1.3
|
||||
C213.2,162.2,212.6,161,212,159.8z M199.8,153.9c-0.1,0.1-0.3,0.1-0.4,0.2c-0.6,0.3-1.1,0.6-1.7,0.9c0.1-0.3,0.1-0.6,0.2-0.9
|
||||
c0.5,0.4,1.3,0.3,1.7-0.2c0.1-0.1,0.2-0.2,0.2-0.4c0.1-0.3,0.1-0.6,0.1-0.9c0-0.2-0.1-0.4-0.1-0.6c0-0.1,0-0.1,0-0.2
|
||||
c0-0.5-0.1-0.9-0.1-1.4c0-0.1,0-0.2,0-0.4c0,0,0-0.1,0-0.1c0-0.2,0.1-0.5,0.1-0.7c0.1-0.2,0.1-0.5,0.2-0.7c0,0,0.1-0.1,0.1-0.2
|
||||
c0.1,0.6,0.2,1.1,0.3,1.7c0,0.1-0.1,0.3-0.1,0.4c-0.1,0.3-0.1,0.6,0.1,0.8C200.4,152.1,200.1,153,199.8,153.9L199.8,153.9z"/>
|
||||
<path id="Path_1323" class="st4" d="M220.2,168.3c-13.1,0.1-25.2,7.6-32.8,18c-3.9,5.6-6.9,11.7-9,18.2c-1.1,3.2-2,6.4-2.7,9.7
|
||||
c-0.7,3.8-1.2,7.7-1.4,11.5c-0.3,4.3-0.4,8.7-0.3,13c0,2.2,0.1,4.4,0.2,6.6c0.1,1.1,0.1,2.2,0.2,3.3c0,0.5,0.1,1.1,0.2,1.6
|
||||
c0.1,0.6,0.3,1.1,0.6,1.7c0.1,0.3,0.4,0.4,0.7,0.4c1,0.4,2.2,0.3,3.1-0.2l11.6-4.4c0.4,0.9,1.1,1.9,2.1,1.9c1.3-0.1,1.7-1.7,1.8-3
|
||||
c0-0.8,0.1-1.5,0.1-2.3c-0.1-4.7-0.6-9.3-1.4-13.9c-0.3-1.9-0.6-3.9-1.1-5.8c0.1-1,0.1-2.1,0.1-3.1c0-1.9,0.3-3.7,0.2-5.6
|
||||
c0.1-3.6-0.5-7.3-1.6-10.7c-0.1-0.2-0.3-0.3-0.5-0.3c-0.1,0-0.2,0.1-0.3,0.3c-0.7,3.5-0.7,7.1-0.2,10.6c0.1,1.1,0.3,2.1,0.4,3.2
|
||||
c-0.2-0.6-0.5-1.2-0.7-1.8c-0.3-0.6-0.5-1.3-0.8-1.9c-0.3-0.8-0.9-1.5-1.2-2.3c0-0.1-0.2-0.2-0.3-0.1c-0.1,0-0.2,0.2-0.2,0.3
|
||||
c0.1,0.8,0.2,1.6,0.2,2.5c0.1,0.7,0.2,1.4,0.3,2.2c0.2,1.5,0.3,3,0.5,4.5c0.3,2.9,0.9,5.8,1.3,8.7s0.6,5.9,0.7,8.8
|
||||
c0,0.4,0,0.9,0.1,1.3c0,0.1,0,0.1,0,0.2c-0.2,1-0.6,1.9-1.1,2.8c-0.3,0.4-0.5,0.8-0.9,1.2c-3.4,1.5-6.9,2.8-10.4,4.2
|
||||
c-0.3,0.1-0.5,0.2-0.8,0.4c0-0.2,0-0.5,0-0.7c0-0.9-0.1-1.9-0.1-2.8c-0.1-1.9-0.2-3.9-0.2-5.8c-0.1-3.8-0.1-7.5,0-11.3
|
||||
c0.1-3.8,0.4-7.6,1-11.3c0.4-3.3,1-6.5,1.8-9.8c3.2-12.3,10.5-23.8,21.4-30.7c5.7-3.7,12.4-5.7,19.2-5.9c0.8-0.1,1.5-0.8,1.4-1.6
|
||||
C221.6,169,221,168.4,220.2,168.3z"/>
|
||||
<path id="Path_1324" class="st4" d="M190,262.5c-0.7-0.3-1.5-0.4-2.3-0.1l-2.2,0.5c-1.4,0.3-2.7,0.6-4.1,0.8
|
||||
c-1.4,0.2-2.8,0.5-4.1,0.7c-0.7,0.1-1.4,0.3-2,0.5c-0.4,0.1-0.8,0.2-1.1,0.4c-0.3,0.2-0.5,0.4-0.8,0.7c-0.3,0.3-0.2,0.7,0,1
|
||||
c0.1,0.1,0.2,0.1,0.2,0.1c0.3,0.1-0.7,0.7-0.4,0.7s-0.3,0.6,0.1,0.6c0.8,0,3.6-1,4.4-1.1c1.4-0.2,2.7-0.5,4.1-0.8
|
||||
c1.4-0.3,2.7-0.6,4.1-0.8c0.7-0.1,1.4-0.3,2.1-0.4c0.8,0,1.6-0.4,2.2-0.9c0.5-0.5,0.4-1.3-0.1-1.8
|
||||
C190.1,262.6,190.1,262.5,190,262.5z"/>
|
||||
<path id="Path_1325" class="st4" d="M111,162.9c0-0.1-0.1-0.1-0.2-0.1c-1.5,0.5-2,2.6-2.6,3.9c-0.7,1.7-1.5,3.4-2.1,5.1
|
||||
c-1.2,3.4-2.3,6.9-3.3,10.4c-1.9,7-3.2,14.2-3.9,21.5c-0.4,4.1-0.5,8.2-0.5,12.3c0,1,0.9,1.8,1.9,1.7c1,0,1.7-0.8,1.7-1.7
|
||||
c-0.2-7.1,0.3-14.2,1.3-21.2c1-7.1,2.5-14.1,4.7-20.9c0.6-1.9,1.2-3.8,1.8-5.7c0.3-1,0.7-2,1.1-2.9
|
||||
C111.3,164.6,111.3,163.7,111,162.9z"/>
|
||||
<path id="Path_1326" class="st4" d="M97.9,257.2c-0.7-0.6-1.7-1-2.6-1.2c-0.9-0.2-1.7-0.5-2.5-0.7c-1.8-0.5-3.6-1.1-5.3-1.6
|
||||
c-1.7-0.6-3.3-1.1-5-1.7c-1.3-0.4-3.5-1.3-4.6-0.2c-0.1,0.1-0.2,0.3-0.1,0.5c0.5,0.5,1,0.8,1.7,0.9c0.7,0.2,1.5,0.5,2.2,0.8
|
||||
c1.6,0.7,3.3,1.3,5,2c1.7,0.6,3.3,1.2,5,1.8c0.8,0.3,1.7,0.5,2.5,0.8c0.9,0.4,1.9,0.6,2.9,0.5C97.9,258.6,98.5,257.8,97.9,257.2z"
|
||||
/>
|
||||
<path id="Path_1327" class="st4" d="M219.5,272.7c-1.6,0.4-3.3,0.7-4.9,0.9c-1.6,0.3-3.2,0.7-4.8,1.1c-3.2,0.8-6.4,1.7-9.6,2.7
|
||||
c-3.2,0.9-6.5,1.8-9.8,2.6c-3.3,1.1-6.6,1.9-10,2.4c-0.5,0.1-1.1,0.1-1.6,0.2c-1-0.1-2,0-3-0.1c-1.7-0.1-3.4-0.1-5,0.1
|
||||
c-0.4,0-0.7-0.1-1.1-0.1c0.4-0.2,0.7-0.4,1-0.6c0.2-0.2,0.5-0.3,0.7-0.5l0,0c0.5-0.1,0.9-0.4,1.3-0.7c0.3-0.3,0.5-0.7,0.5-1
|
||||
c0.1-0.1,0.1-0.3,0.1-0.5v0l0,0c0.2-0.4,0.1-0.9-0.3-1.2c-0.1,0-0.2-0.1-0.3-0.1c0,0-0.1-0.1-0.1-0.1l-0.3-0.1
|
||||
c-0.3-0.3-0.7-0.4-1.2-0.4c-0.4,0-0.8,0.1-1.2,0.3c-0.7,0.3-1.5,0.6-2.2,0.8c-0.4,0.1-0.8,0.2-1.2,0.4c0,0-0.1-0.1-0.1-0.1
|
||||
c0,0-0.1,0-0.1-0.1c0,0,0,0-0.1-0.1c-0.3-0.3-0.7-0.4-1.2-0.3c-0.2,0.1-0.4,0.2-0.5,0.3c-0.1,0.1-0.3,0.3-0.3,0.5
|
||||
c-0.1,0.2-0.1,0.4-0.1,0.7c0,0.1,0,0.1,0,0.2c0,0.3,0.1,0.6,0.3,0.9c0.2,0.3,0.4,0.6,0.8,0.8c0,0,0,0,0,0.1c0,0.1,0,0.2,0.1,0.3
|
||||
l-0.5-0.2c-0.4-1.1-1.2-1.9-2.1-2.5c-0.2-0.1-0.5-0.3-0.8-0.4c-2.5,0.3-5,0.3-7.5-0.2c-1.4-0.3-2.7-0.7-4.1-0.9
|
||||
c-0.8-0.1-1.5-0.4-2.1-0.7c-0.6-0.4-1-0.9-1.6-1.3c-0.1-0.1-0.3-0.2-0.4-0.3c0,0-0.1,0-0.1,0c-0.1,0-0.3,0-0.4,0
|
||||
c0,0-0.1,0-0.1-0.1c-0.3-0.2-0.6-0.4-1-0.4c-0.2,0-0.3,0-0.5,0c0.1,0,0.3,0.1-0.1,0c-0.4-0.1-0.7-0.2-1-0.4
|
||||
c-0.8-0.5-1.5-1.1-2.1-1.7c-0.8-0.8-1.8-1.4-2.8-1.9c-0.6-0.3-1.3-0.6-1.9-0.7c-0.7-0.1-1.4-0.2-2.1-0.4c-2.8-0.7-5.5-1.6-8.1-2.7
|
||||
c-2.8-1-5.7-2.1-8.5-3.2c-1.5-0.6-3.1-1.2-4.6-1.7c-0.8-0.3-1.6-0.6-2.4-0.8c-0.4-0.1-0.9-0.2-1.3-0.2c-0.1,0-0.3,0-0.4,0
|
||||
c-0.7-0.5-1.6-0.3-2.1,0.4c-0.1,0.1-0.2,0.3-0.2,0.4c-0.4,1.9-0.5,3.8-0.3,5.8c0.1,2,0.2,3.9,0.3,5.9c0.1,1.9,0.5,3.8,0.6,5.7
|
||||
c0,1,0.1,2,0.3,3c0,0.2,0.1,0.3,0.1,0.5c-0.1,0.2-0.3,0.4-0.4,0.6c-1,0.4-1.7,1.4-1.7,2.6c0.1,0.6,0,1.3-0.3,1.9
|
||||
c0.3,0.9,1.2,1.4,2.1,1.2l41.1-8.1c0.3-0.1,0.5-0.2,0.7-0.4c1.1,0.6,2.4,1,3.6,1.3c1.4,0.2,2.8,0.3,4.2,0.3c1.3-0.1,2.6,0,3.8,0.3
|
||||
c0.4,0.2,0.8,0.4,1.1,0.7c0.3,0.7,0.8,1.4,1.4,1.8c1.7,1.1,3.6,1.7,5.6,1.8c2,0.1,4.1,0.1,6.1-0.1c1.1-0.1,2.1-0.4,3.2-0.5
|
||||
c0.4,0,0.7-0.1,1.1-0.1c2.2-0.1,4.3-0.3,6.4-0.6c8.4-1,16.7-2.7,24.9-4.9c2.3-0.6,4.6-1.2,6.9-1.8c1-0.2,1.9-0.5,2.8-1
|
||||
c1-0.7,1.6-1.8,1.6-3c0-0.9-0.8-1.7-1.7-1.7C219.8,272.6,219.7,272.7,219.5,272.7z"/>
|
||||
<path id="Path_1328" class="st4" d="M183,121.5c-0.8-0.7-1.7-1.2-2.7-1.4c-2-0.5-4.1,0.5-5,2.4c-0.6,1.3-0.6,2.8,0,4.1
|
||||
c0.7,1.2,1.8,2.2,3.2,2.5c1.4,0.5,2.9,0.4,4.3-0.2c1.4-0.7,2.2-2.1,2.1-3.7C184.8,123.8,184.1,122.4,183,121.5z"/>
|
||||
<path id="Path_1329" class="st4" d="M210.9,160.3c-0.5-0.8-1.2-1.5-2-2.1c-0.7-0.4-1.4-0.8-2.2-1c-1.5-0.2-2.9-0.8-4.1-1.8
|
||||
c-0.7-0.7-1.7,0.4-1.1,1.1c0.5,0.5,1.1,0.9,1.7,1.1c0,0.1,0,0.1,0,0.2c0.3,1.2,1.6,1.6,2.6,2l4.1,1.5c0.4,0.1,0.8-0.1,0.9-0.6
|
||||
C211,160.6,211,160.5,210.9,160.3z"/>
|
||||
<g id="Group_12">
|
||||
<path id="Path_1357" class="st5" d="M186.6,124.7c0-3.7-3-6.8-6.8-6.8s-6.8,3-6.8,6.8c0,3.7,3,6.8,6.8,6.8
|
||||
C183.6,131.4,186.6,128.4,186.6,124.7z M175.8,124.7c0-2.2,1.8-4,4-4c2.2,0,4,1.8,4,4s-1.8,4-4,4c0,0,0,0,0,0
|
||||
C177.6,128.7,175.8,126.9,175.8,124.7z"/>
|
||||
<path id="Path_1358" class="st5" d="M203.2,229c0.1,0.5,0.7,0.8,1.2,0.7c0,0,0.1,0,0.1,0c0.7,0,1.5,0,2.2-0.1
|
||||
c0.9-0.1,1.8-0.1,2.7-0.2c1.9-0.2,3.8-0.3,5.7-0.5c1.8-0.2,3.7-0.4,5.5-0.6c0.9-0.1,1.7-0.2,2.6-0.3c0.9-0.1,1.8-0.1,2.3-0.9
|
||||
c0.1-0.1,0.1-0.3,0.1-0.5c0.2-0.9,0.2-1.8,0.1-2.7c0-0.9-0.1-1.9-0.1-2.8c-0.1-2-0.4-4-0.6-6c-0.4-3.7-1.1-7.4-2.1-11
|
||||
c0.1-0.2,0.1-0.5,0-0.7c-0.3-0.7-1.3-0.5-2-0.4c-0.9,0.1-1.9,0.1-2.8,0.1c-2.1,0-4.1,0-6.2,0c-2.1,0-4.2,0.1-6.4,0.3
|
||||
c-1,0.1-2.1,0.1-3.2,0.2c-0.9,0.1-2.1-0.1-2.6,0.8c-0.1,0.2-0.1,0.5,0,0.8c0.1,0.1,0.1,0.1,0.2,0.2c0,0.9,0,1.8,0.1,2.7
|
||||
c0.1,1,0.3,2,0.5,3c0.3,2,0.7,4.1,1,6.1c0.3,2,0.6,4,0.8,6c0.1,1,0.2,2,0.4,3C202.8,226.9,203,227.9,203.2,229z M209.3,227.3
|
||||
c-0.9,0.1-1.8,0.1-2.7,0.2c-0.4,0-0.9,0-1.3,0.1c-0.1-0.6-0.2-1.3-0.2-1.9c-0.1-1-0.2-2-0.4-3c-0.3-2-0.6-4-0.9-6.1
|
||||
c-0.2-1.2-0.4-2.4-0.7-3.6c0.3-0.1,0.7-0.2,1-0.2c1.1-0.2,2.2-0.4,3.3-0.6c0.5-0.1,1-0.2,1.5-0.2c0,0.2,0,0.5,0.1,0.7
|
||||
c0,0.7,0.1,1.3,0.1,2c0.1,1.5,0.2,2.9,0.3,4.4c0.2,2.7,0.5,5.4,0.8,8.1C209.9,227.2,209.6,227.3,209.3,227.3L209.3,227.3z
|
||||
M214.8,226.8c-1,0.1-1.9,0.2-2.9,0.3c-0.3-2.7-0.6-5.4-0.8-8.1c-0.1-1.5-0.3-2.9-0.5-4.4c-0.1-0.7-0.1-1.5-0.2-2.2
|
||||
c0-0.2,0-0.4,0-0.6c1.3-0.1,2.5-0.2,3.8-0.2c0.8,0,1.6,0,2.4,0c0,0.3,0.1,0.5,0.1,0.8c0,0.7,0.1,1.3,0.1,2
|
||||
c0.1,1.5,0.2,2.9,0.3,4.4c0.2,2.6,0.5,5.1,0.7,7.7C216.8,226.6,215.8,226.7,214.8,226.8L214.8,226.8z M224,223.9
|
||||
c0.1,0.6,0.2,1.2,0.3,1.9c-0.4,0-0.8,0.1-1.1,0.1c-0.9,0.1-1.8,0.2-2.7,0.3c-0.3,0-0.7,0.1-1,0.1c-0.3-2.6-0.5-5.1-0.8-7.7
|
||||
c-0.1-1.5-0.3-2.9-0.5-4.4c-0.1-0.7-0.1-1.5-0.2-2.2c0-0.2,0-0.3,0-0.5c0.6,0,1.3-0.1,1.9-0.2c0.9-0.2,1.8-0.4,2.6-0.8
|
||||
c0.1,1.6,0.3,3.1,0.5,4.7c0.2,1.9,0.3,3.9,0.6,5.8C223.8,222,223.9,223,224,223.9L224,223.9z M205.8,205.2
|
||||
c2.3-0.1,4.6-0.2,6.9-0.3c2.1,0,4.2,0.1,6.3-0.1c1.1,0,2.2-0.2,3.3-0.5c0,1.5,0,2.9,0.1,4.4c-0.4,0.1-0.7,0.2-1,0.3
|
||||
c-0.9,0.2-1.8,0.4-2.7,0.4c-1.9,0.1-3.9-0.1-5.8,0c-2.1,0.1-4.1,0.3-6.2,0.7c-1,0.2-2.1,0.4-3.1,0.6c-0.3,0.1-0.6,0.1-0.8,0.1
|
||||
c0-0.1,0-0.1,0-0.2c-0.2-1-0.4-2-0.6-2.9c-0.2-0.8-0.5-1.6-0.8-2.3c0.4,0,0.8-0.1,1.2-0.1C203.6,205.4,204.7,205.3,205.8,205.2z"
|
||||
/>
|
||||
<path id="Path_1359" class="st5" d="M113.6,202.6c4.9,1.4,9.9,2.2,14.9,2.3c0.6,0,1-0.5,1-1l0.3-6.9c0.3-2.2,0.1-4.5-0.4-6.6
|
||||
c-0.1-0.3-0.5-0.4-0.6-0.1c0,0,0,0,0,0c-0.1-0.1-0.2-0.2-0.4-0.3c-1.2-0.3-2.4-0.4-3.6-0.4c-1.3-0.1-2.6-0.2-3.8-0.4
|
||||
c-2.5-0.3-5-0.7-7.5-1.2c-0.5-0.1-1.1,0.2-1.3,0.7c0,0.1,0,0.2,0,0.3c0.2,4.2,0.5,8.3,0.7,12.5
|
||||
C112.8,202.1,113.1,202.5,113.6,202.6z M124.5,191.6c1.3,0.2,2.6,0.2,3.8,0c-0.4,1.7-0.6,3.5-0.6,5.2c-0.1,2-0.2,3.9-0.3,5.9
|
||||
c-4.3-0.2-8.5-0.9-12.7-2c-0.2-3.5-0.4-7-0.6-10.5c2.1,0.4,4.2,0.7,6.4,0.9C121.9,191.4,123.2,191.5,124.5,191.6L124.5,191.6z"/>
|
||||
<path id="Path_1360" class="st5" d="M297.3,140.9L297.3,140.9c-4.6-81.4-74.4-143.6-155.8-139C67.2,6.1,7.6,65,2.6,139.3H2.5v1.7
|
||||
c-0.2,2.9-0.2,5.7-0.2,8.3c0,2.8,0.1,6,0.1,6l0.1,142.4h295V146.5L297.3,140.9z M107,267.3c0-1.7-0.1-3.5-0.1-5.2
|
||||
c4.3,1.5,8.5,2.9,12.8,4.4c2.7,0.9,5.4,1.9,8.1,2.8c1.3,0.4,2.6,0.9,3.9,1.3c0.6,0.2,1.3,0.4,1.9,0.5c0.2,0.4,0.6,0.6,1,0.6
|
||||
l2.5-0.3c0,0.2,0.1,0.4,0.3,0.5c0.7,0.6,1.4,1.1,2.2,1.5c0.8,0.5,1.5,0.9,2.3,1.4c1.4,0.8,2.7,1.7,4.1,2.5
|
||||
c2.2,1.6,4.7,2.7,7.4,3.3c1.6,0.2,3.2,0.1,4.8-0.1c1.2-0.1,2.4-0.2,3.6-0.4c0,0,0,0.1,0,0.1c1.7,3.8,6.1,5,10,5.1
|
||||
c2.5,0.1,5-0.1,7.4-0.5c-0.1,3.1-0.3,6.3-0.4,9.4h-71.2C107.3,285.3,107.2,276.3,107,267.3z M78,253.5c0.1-0.4,0.2-0.8,0.2-1.2
|
||||
l17.8,6.1c-0.1,0.2-0.2,0.5-0.3,0.8s-0.2,1.2-0.5,1.3c-0.3,0.1-1.3-0.4-1.6-0.4c-0.6-0.2-1.2-0.4-1.8-0.5c-2.3-0.7-4.6-1.3-6.9-2
|
||||
l-3.4-1c-1.2-0.3-2.3-0.7-3.5-1.1C77.4,255,77.9,254.2,78,253.5z M79,250.3L63.3,245c-5.8-11.7-11.6-23.3-17.4-35
|
||||
c-0.8-1.6-1.6-3.3-2.4-4.9c-0.3-0.6-0.7-1.5-1.1-2.2c0.3,0.1,0.6,0.2,0.8,0.2l3.6,1c2.3,0.7,4.6,1.3,6.9,1.9l13.9,3.8l27.4,7.5
|
||||
c2,0.5,4,1,5.9,1.6c0.2,0.2,0.5,0.3,0.8,0.2l0.8,0.2c2.4,0.7,4.8,1.2,7.2,1.6c0.2,1.5,0.7,2.9,1.6,4.2c0.8,1.6,1.7,3.2,2.5,4.9
|
||||
l5.1,9.9c3.4,6.6,6.8,13.1,10.3,19.7l5.2,10c-0.1,0-0.1,0-0.2,0.1c-1.6-0.7-3.2-1.3-4.9-1.8c-2.4-0.8-4.7-1.6-7.1-2.5l-14.3-4.9
|
||||
C98.2,257,88.6,253.7,79,250.3L79,250.3z M110.5,160l-0.1-1.7c0-1.1-0.1-2.3-0.1-3.4c0-0.7,0.2-1.4,0.9-1.5c0.2,0,0.4,0,0.6,0
|
||||
c0.1,0.2,0.3,0.3,0.5,0.4c3.8,3.9,9.4,5.7,14.6,6.6c5.8,0.8,11.7,1,17.6,0.7c12.2-0.3,24.4-2.4,36-6.4c3.1-1.1,6.2-2.4,9.2-3.8
|
||||
c1.5-0.7,3-1.5,4.4-2.3c1.4-0.7,2.7-1.6,3.8-2.6l0.1,0c-0.2,0,0.3,0.1,0.1,0c0.1,0,0.2,0.1,0.2,0.1c0,0,0,0,0,0l0.1,0.1
|
||||
c0,0.1,0.1,0.1,0.1,0.2c0.1,0.1,0.1,0.3,0.1,0.4c0.1,0.3,0.1,0.7,0.1,1c0,1.9,0,3.7,0.1,5.6c-1,0.4-2,0.9-2.9,1.5
|
||||
c-1.3,0.7-2.7,1.4-4.1,2.1c-2.7,1.3-5.5,2.5-8.4,3.5c-5.9,2.1-11.9,3.7-18,4.8c-6.2,1.1-12.5,1.8-18.8,2.1
|
||||
c-6.1,0.4-12.3,0.3-18.4-0.1c-6.3-0.3-12.3-2.4-17.3-6.3c-0.1-0.1-0.2-0.2-0.4-0.2C110.5,160.5,110.5,160.2,110.5,160z
|
||||
M237.3,167.7c0,10.5,0.1,21,0.1,31.5c0,10.8,0.1,21.6,0.1,32.4c0,10.5,0.1,21.1,0.1,31.6c0,9.6,0,19.3,0.1,28.9
|
||||
c0,0.7,0.1,1.4,0.1,2.2h-20.2c0,0,0-0.1,0-0.1c-0.4-6.5-0.4-13-0.7-19.4c0.8-0.3,1.6-0.5,2.4-0.8c5.2-1.9,10.4-4.7,13.1-9.6
|
||||
c1.4-2.7,2.1-5.6,2.1-8.6c0-4.4-0.3-8.8-0.5-13.1c-0.4-9-0.7-18.1-1.1-27.1c-0.2-4.4-0.3-8.8-0.6-13.2c0-0.4,0-0.8-0.1-1.2
|
||||
c0.1-0.2,0.2-0.4,0.2-0.6v-43.6c-0.1-0.6-0.5-1.1-1-1.3h-9.3c-0.6,0.2-1,0.7-1,1.3v11.7c-0.4-0.4-0.8-0.7-1.2-1.1
|
||||
c0,0-0.1-0.1-0.1-0.1c-0.9-0.7-1.8-1.4-2.7-2.1c0-0.6,0-1.2,0-1.8c0-1.1,0-2.3,0-3.4c0-2.2,0-4.4,0-6.6c0-2.2,0-4.4,0-6.6
|
||||
c0-1.1,0-2.3,0-3.4c0-0.9-0.1-1.9-0.3-2.8h0.5h10.4c3.3,0,6.6,0.1,9.8-0.2C237.1,149.6,237.3,158.7,237.3,167.7L237.3,167.7z
|
||||
M110.6,220c-0.4-0.2-0.9-0.3-1.3-0.5c0.1-0.2,0.2-0.4,0.2-0.6c0.1-3.7,0.3-7.4,0.6-11c0.3-3.6,0.7-7.3,1-10.9
|
||||
c0.5-7.4,0.7-14.8,1.1-22.1c0.1-2,0.2-4.1,0.3-6.1c0.1-1.1,0.1-2.1,0.1-3.2c0-0.1,0-0.3,0-0.4c3.8,2.2,8,3.5,12.3,4
|
||||
c6.2,0.8,12.4,1,18.7,0.7c13-0.3,25.9-2.4,38.3-6.4c3.4-1.1,6.6-2.4,9.9-3.9c1.6-0.7,3.1-1.5,4.6-2.3c0.7-0.4,1.4-0.8,2.1-1.2
|
||||
c0.6,0.6,1.3,1.1,2.1,1.5l0.3,0.1c-0.8,0.2-1.6,0.6-2.3,0.9c-1.6,0.6-3.1,1.3-4.6,2.1c-2.9,1.5-5.6,3.2-8.2,5.2
|
||||
c-5,3.9-9.4,8.5-12.9,13.8c-8.3,12.4-11.6,27.3-12.7,41.9c-0.7,9.1-0.6,18.2-0.9,27.3l-22.9-5.1c-2.6-0.6-5.1-1.1-7.7-1.7
|
||||
c-1.2-0.3-2.5-0.5-3.7-0.8c-1-0.3-2.1-0.5-3.2-0.6l-5.5-10.5c-0.9-1.8-1.9-3.7-2.9-5.5c-0.5-0.9-1-1.8-1.5-2.7
|
||||
c-0.3-0.6-0.7-1.1-1.2-1.6C110.9,220.4,110.9,220.2,110.6,220C110.6,220,110.6,220,110.6,220z M108.5,195.5
|
||||
c-0.3,3.7-0.7,7.4-1,11.1c-0.4,4-0.6,8-0.7,12.1c-1.4-0.5-2.8-0.9-4.2-1.3c0.3-14.4,1.3-29,5.4-42.9c0.5-1.7,1.1-3.4,1.7-5
|
||||
c-0.1,1.4-0.2,2.7-0.3,4.1c-0.2,3.7-0.3,7.3-0.4,11C109,188.2,108.8,191.8,108.5,195.5z M184.9,176.4l0.5-0.4c1-0.8,2-1.6,3-2.4
|
||||
c2-1.6,4.1-3.1,6.3-4.5c1.1-0.7,2-1.6,3.1-2.2c1.1-0.7,2.2-1.3,3.4-1.9c1.2-0.6,2.4-1.1,3.6-1.6c1.1-0.4,2.2-0.9,3.3-1.4
|
||||
c0.3,0.1,0.5,0.3,0.8,0.4l0,0c-0.5,0.2-1,0.5-1.4,0.7c-1.2,0.6-2.5,1.2-3.7,1.8c-2.3,1.2-4.6,2.6-6.8,4.1c-2.2,1.5-4.3,3-6.4,4.7
|
||||
c-1.1,0.9-2.1,1.8-3.1,2.7c-0.4,0.4-0.7,0.8-1.1,1.2C186.1,177.1,185.6,176.7,184.9,176.4z M183.3,178.8c0.7,0,1.3,0.6,1.3,1.3
|
||||
c0,0.7-0.6,1.3-1.3,1.3c-0.7,0-1.3-0.6-1.3-1.3C182,179.4,182.6,178.8,183.3,178.8z M203.9,161.8c-1.1,0.6-2.3,1.2-3.4,1.8
|
||||
c-1.1,0.6-2.2,1.2-3.4,1.9c-1.1,0.7-2.4,1.2-3.5,1.9c-2.2,1.4-4.3,2.9-6.3,4.6c-1,0.8-2,1.6-2.9,2.5c-0.5,0.5-1,1-1.5,1.6
|
||||
c-2.2,0.3-3.8,2.3-3.6,4.5c0.3,2.2,2.3,3.8,4.5,3.6c2-0.2,3.6-2,3.6-4c0-0.4-0.1-0.8-0.2-1.2c0.6-0.4,1.1-0.8,1.7-1.2
|
||||
c1-0.8,1.9-1.7,2.9-2.5c2-1.6,4.2-3.2,6.4-4.6s4.4-2.8,6.7-4c1.1-0.6,2.3-1.2,3.4-1.7c0.6-0.3,1.2-0.6,1.9-0.9
|
||||
c0.3-0.1,0.5-0.2,0.8-0.4c1.3,0.8,2.6,1.7,3.9,2.5c0,0.2,0.1,0.3,0.2,0.5c-2.3,0.2-4.5,0.7-6.6,1.5c-3.4,1.2-6.6,2.9-9.5,5
|
||||
c-5.5,3.8-10.2,8.6-13.9,14.1c-3.7,5.5-6.6,11.5-8.5,17.9c-2.1,7.4-3.3,14.9-3.6,22.6c-0.2,4.3-0.4,8.6-0.2,12.8
|
||||
c0.1,2,0.1,4,0.2,5.9c0,1.9,0.1,3.8,0.3,5.7c-3.3-0.7-6.6-1.5-9.9-2.2l-1.9-0.4c0-0.1,0.1-0.2,0.1-0.3c0.1-15.4-0.2-30.9,3.5-46
|
||||
c3.2-13.1,9.6-25.2,20-34c2.7-2.2,5.5-4.2,8.6-5.9c1.6-0.9,3.2-1.7,4.9-2.4c1.5-0.7,3.1-1.1,4.5-1.9l0.3,0.2
|
||||
c0.9,0.5,1.7,1,2.6,1.5C205.2,161.1,204.5,161.5,203.9,161.8L203.9,161.8z M123.8,243.2c1.2,0.3,2.4,0.6,3.7,0.9
|
||||
c2.2,0.5,4.4,1,6.7,1.5l13.7,3l27,6c2.2,0.5,4.5,1,6.7,1.4l-0.9,0.1c-3.1,0.3-6.2,0.6-9.3,0.9c-0.6-1.4-1.8-2.5-3.1-3.1
|
||||
c-0.7-0.3-1.5-0.4-2.2-0.5c-1-0.2-2-0.3-3-0.4c-0.9,0-1.8-0.1-2.6-0.3c-0.9-0.2-1.7-0.3-2.6-0.2c-1.7,0.4-1.7,2.3-1.3,3.7
|
||||
c0.2,0.7,0.6,1.4,1.2,1.9c0.5,0.4,1,0.8,1.5,1.2c-8.8,0.9-17.6,1.8-26.4,2.6c-3.3-6.3-6.6-12.6-9.8-18.8
|
||||
C123.2,243.1,123.5,243.2,123.8,243.2z M189.2,257.3c0,0.2-0.1,0.3-0.1,0.5c-0.1,0.6-0.1,1.3-0.1,1.9c0,0.6,0,1.2,0,1.8
|
||||
c0,0.4,0,0.7,0,1.1c0,0,0,0-0.1,0c-0.3,0-0.6,0-1,0c-0.7,0.1-1.4,0.2-2.1,0.2l-4,0.5l-8.2,1c-0.1,0-0.1,0-0.2,0
|
||||
c-0.1-0.9-0.2-1.7-0.3-2.6c-0.2-0.9-0.4-1.9-0.8-2.8c2.8-0.3,5.7-0.6,8.5-0.9l4.7-0.5c0.8-0.1,1.5-0.1,2.3-0.2
|
||||
c0.4,0,0.8-0.1,1.1-0.2c0,0,0,0,0,0L189.2,257.3z M165.2,279.8C165.3,279.9,165.3,279.8,165.2,279.8z M168.5,281.7
|
||||
c-0.2-0.1-0.4-0.1-0.6-0.2c-0.1,0-0.4-0.2-0.5-0.2c-0.3-0.1-0.6-0.3-0.9-0.5c-0.1-0.1-0.3-0.2-0.4-0.3c0,0-0.3-0.2-0.3-0.3
|
||||
c-0.1-0.1-0.3-0.3-0.4-0.4c0,0-0.1-0.1-0.1-0.1c1.1-0.2,2.2-0.3,3.3-0.5c1.4-0.2,2.8-0.5,4.2-1c0,0.7-0.1,1.4-0.5,2
|
||||
c-0.5,0.9-1.5,1.5-2.5,1.6C169.3,281.9,168.9,281.8,168.5,281.7z M160.3,261.4c0,0.5,0,1.1-0.1,1.6c-0.1,1-0.3,2.1-0.6,3.2
|
||||
c0,0-0.1,0-0.1,0c-7.7,1-15.3,2-23,3c-0.9-1.7-1.8-3.4-2.6-5.1C142.7,263.2,151.5,262.3,160.3,261.4z M155.2,268.9
|
||||
c1.4,0.4,2.5,1.5,4.1,1.3c1.6-0.2,2.3-1.5,2.6-2.9c0.9-2.4,1-5,0.4-7.5c-0.6-1.3-1.5-2.3-2.7-3c-0.6-0.4-1-0.9-1.2-1.6
|
||||
c0-0.2-0.1-0.7-0.1-0.7c0,0,0.9,0.1,1.1,0.2c1.8,0.2,3.6,0.7,5.4,1c1.5,0,2.9,0.6,3.8,1.7c0.9,1.4,1.6,3,2.1,4.6
|
||||
c0.3,0.9,0.5,1.9,0.7,2.8c0.1,0.5,0.3,1,0.6,1.5c-0.1,0.4-0.1,0.8-0.1,1.2c0,0.6,0.1,1.3,0.2,1.9c0.2,1.3,0.3,2.6,0.4,3.9
|
||||
c0.1,1,0.1,2.1,0.2,3.1c-1.4,0-2.8,0.1-4.2,0.3c-1.7,0.2-3.3,0.4-5,0.6s-3.3,0.4-5,0.5c-1.6,0.2-3.1,0.3-4.7,0.3
|
||||
c-2.5-0.5-4.8-1.6-6.8-3.2c-1.4-0.9-2.7-1.8-4.1-2.7c-0.7-0.5-1.4-1-2.2-1.5L155.2,268.9z M174.8,275c-0.1-1.6-0.3-3.2-0.5-4.8
|
||||
c-0.1-0.8-0.2-1.5-0.4-2.2c-0.1-0.5-0.3-0.9-0.5-1.3c0.1,0,0.1,0,0.2,0l8.3-1.1l4.2-0.6c0.6-0.1,1.3-0.2,1.9-0.3
|
||||
c0.3,0,0.6-0.1,0.9-0.2c0.4-0.2,0.8-0.4,1.2-0.6h0c0.2-0.1,0.4-0.2,0.5-0.3c0.3-0.6,0.4-1.4,0.4-2.1c0-0.6,0-1.2,0-1.8
|
||||
c0-0.6,0-1.2-0.1-1.8c0-0.3-0.1-0.7-0.2-1c-0.1-0.2-0.2-0.4-0.3-0.6c0-0.3-0.2-0.6-0.5-0.6c-0.5-0.1-0.9-0.3-1.3-0.3
|
||||
c-0.3,0-0.7,0-1,0c-0.2,0-0.3,0-0.5,0c-2.4-0.7-4.9-1.3-7.3-1.8l2.9-0.9c2.3-0.7,4.7-1.2,7-1.9c1.9-0.9,4.1-1,6.2-0.5
|
||||
c1,0.4,2,0.8,3,1.3c0.5,0.2,1,0.4,1.4,0.6c0.6,0.2,1.2,0.3,1.9,0.4c0.2,0.1,0.4-0.1,0.5-0.3c0-0.1,0-0.3-0.1-0.4
|
||||
c-0.5-0.4-0.8-0.9-1.3-1.3c-0.5-0.4-1-0.7-1.5-1c-0.9-0.7-1.9-1.2-3-1.7c-0.5-0.2-1-0.4-1.6-0.5c0.1-1.2,0.1-2.4-0.1-3.6
|
||||
c-0.1-1.6-0.2-3.2-0.3-4.7c-0.2-3.1-0.4-6.2-0.6-9.3c-0.5-6.3-1.1-12.7-1.8-19c-0.2-1.7-0.4-3.4-0.7-5.1
|
||||
c-0.3-1.8-0.8-3.5-1.2-5.2c0-0.1-0.2-0.2-0.3-0.1c-0.1,0-0.2,0.1-0.2,0.2c-0.1,3.1-0.1,6.2,0.1,9.3c0.2,2.7,0.4,5.4,0.6,8.1
|
||||
c-0.1-0.3-0.2-0.6-0.3-0.9c-0.4-1.3-0.9-2.6-1.3-4c-0.2-0.7-0.4-1.4-0.7-2.1c-0.3-0.7-0.6-1.4-0.9-2.1c-0.1-0.2-0.3-0.3-0.5-0.2
|
||||
c-0.1,0.1-0.2,0.2-0.3,0.3c0,0.7-0.2,1.5-0.2,2.2c0,0.7,0.1,1.5,0.3,2.2c0.3,1.4,0.7,2.7,1.1,4c0.9,2.7,1.6,5.5,2.2,8.3
|
||||
c0.5,2.8,1,5.7,1.3,8.6c0.2,1.4,0.3,2.8,0.4,4.2c0.1,1,0.2,2,0.3,2.9c0,0.6,0.1,1.2,0.1,1.9c0,0.9,0.1,1.8,0.1,2.7
|
||||
c0,0.5,0,1,0.1,1.4c-0.9,0.1-1.8,0.3-2.7,0.5c-2.5,0.7-4.8,1.7-7.3,2.5l-3.5,1.1c-1.2,0.3-2.4,0.7-3.5,1.2l-0.4-0.1
|
||||
c0.1-0.1,0.2-0.3,0.2-0.5c0.3-3.3-0.1-6.7-0.1-10.1c0.1-3.6,0-7.3,0.2-10.9c0.2-7.4,1.1-14.8,2.8-22c2.8-12.6,9.8-23.8,19.7-32
|
||||
c2.8-2.2,6-4.1,9.3-5.6c1.9-0.9,4-1.5,6.1-2c1.7-0.3,3.5-0.5,5.2-0.7c0.1,0,0.1,0.1,0.2,0.1c5.2,4.7,8.6,11,9.8,17.8
|
||||
c0.6,3.5,1.1,6.9,1.2,10.5c0.2,3.7,0.3,7.5,0.5,11.3c0.3,8.8,0.6,17.5,1,26.3c0.2,4.4,0.3,8.9,0.5,13.3l0,0.3
|
||||
c-2.1-0.3-4.3,0-6.1,1c-2.2,1-4.2,2.5-5.8,4.4c-3.3,3.7-5.8,8-7.3,12.7c-0.8,2.3-1.3,4.6-1.6,7c-1.2,0.3-2.4,0.6-3.5,1
|
||||
c-5.2,1.5-10.3,3.1-15.5,4.5c-5.1,1.5-10.4,2.6-15.7,3.1c-0.8,0.1-1.6,0.1-2.3,0.1C175.3,280.3,175,277.4,174.8,275L174.8,275z
|
||||
M231.8,254.7c0.1,1.4,0,2.9-0.3,4.3c-1.1,5.2-4.8,8.6-9.4,10.8c-2.8,1.3-5.7,2.3-8.7,3c0.6-3.9,1.9-7.6,3.8-11
|
||||
c1.9-4.1,5.1-7.6,9-9.9c1.1-0.5,2.3-0.9,3.5-1.1c0.5-0.1,1.3-0.1,2-0.2C231.7,251.9,231.8,253.3,231.8,254.7L231.8,254.7z
|
||||
M230.5,186.1c-1-5.2-3.2-10.1-6.4-14.4c-0.3-0.3-0.6-0.7-0.9-1v-13.1h7.3L230.5,186.1z M187.5,283.2c5.6-1.4,11.1-3.1,16.6-4.8
|
||||
c2.3-0.7,4.7-1.4,7-2c1.3-0.3,2.6-0.7,3.9-1c0.3,6.3,0.4,12.6,0.7,18.8c0,0,0,0.1,0,0.1h-34.9c0.1-3.3,0.3-6.5,0.4-9.8
|
||||
C183.3,284.2,185.4,283.7,187.5,283.2L187.5,283.2z M214.6,153.5c0,2.2,0,4.4,0,6.6c0,1.1,0,2.3,0,3.4c0,0,0,0.1,0,0.1
|
||||
c-3.3-2.1-6.7-4.1-10.2-6c-1-0.5-1.9-1-2.9-1.5c-0.6-0.3-1.3-0.6-2-0.7c0.2-0.2,0.4-0.4,0.6-0.6c0.5-0.1,0.9-0.6,0.9-1.1
|
||||
c0-1.2-0.1-2.4-0.1-3.7c0-1.2,0-2.4-0.2-3.6c-0.1-1.1-0.9-2.1-2-2.4c-0.2,0-0.3-0.1-0.5-0.1c-0.1-1.1-0.1-2.3-0.2-3.4
|
||||
c1.3,0.1,2.5,0.2,3.8,0.2l5.1,0c2.6,0,5.2,0,7.8,0c-0.2,0.9-0.3,1.8-0.3,2.8c0,1.1,0,2.3,0,3.4
|
||||
C214.6,149.1,214.6,151.3,214.6,153.5L214.6,153.5z M113.5,142.3c0-0.3,0-0.7,0-1c3.2,3.5,10.3,4.5,14,4.9
|
||||
c11.3,1.4,22.7,0.7,33.8-1.9c3.2-0.8,13.7-3.7,14.1-8c0.2-3-2.6-4.8-4.3-7.1c-2-2.8-1.5-6-0.6-8.8c0.8-2.6,1.8-6-1.4-7.9
|
||||
c-1.2-0.6-2.5-1-3.9-0.9c-1.6-0.1-3.2-0.1-4.8-0.1c-3.4-0.1-6.8-0.3-10.2-0.6c-6.7-0.6-13.4-1.5-20.1-2.7
|
||||
c-0.8-0.1-8.1-1.4-11.6-1.8c1.6-3.9,4-7.4,6.9-10.5c3.4-3.5,7.5-6.2,12-8c9.4-3.8,19.7-4.3,29.4-1.3
|
||||
c10.8,3.1,19.7,10.8,24.3,21.1c4.4,10.5,4,22.1,4.5,33.2c0.1,1.3,0.1,2.6,0.2,3.9c0,0.1,0,0.2,0.1,0.2c-0.7,0.4-1.4,0.8-2.1,1.2
|
||||
c-1.2,0.7-2.5,1.4-3.8,2c-2.6,1.3-5.3,2.5-8,3.6c-5.4,2.1-11.1,3.7-16.8,4.8c-5.9,1.1-11.8,1.8-17.7,2.1
|
||||
c-5.7,0.4-11.5,0.3-17.2-0.1c-5.9-0.3-11.6-2.5-16.2-6.3c-0.1-0.1-0.3-0.2-0.5-0.3C113.7,148.7,113.7,145.5,113.5,142.3
|
||||
L113.5,142.3z M109,162.4c0,0.1,0.1,0.2,0.2,0.2c0.2,0.2,0.5,0.4,0.8,0.7c-6,12.9-8,27.5-8.9,41.5c-0.3,4-0.4,8-0.5,12
|
||||
c-1.2-0.3-2.3-0.6-3.5-1c-4.2-1.2-8.5-2.3-12.7-3.5c4.7-14.1,11-27.6,18.8-40.3c1.1-1.8,2.2-3.5,3.3-5.2
|
||||
C107.4,165.4,108.3,163.9,109,162.4L109,162.4z M294.1,294.3h-54c0.3-4.2,0.1-8.4,0.1-12.6c0-4.9,0-9.8,0-14.6
|
||||
c0-10.5,0-21-0.1-31.5c0-10.8-0.1-21.6-0.1-32.4c0-10.5-0.1-21.1-0.1-31.6c0-9.6,0.2-19.3-0.3-28.9c0-1.1-0.1-2.2-0.2-3.2
|
||||
c0-0.4-0.3-0.7-0.6-0.8c-0.2-0.1-0.3-0.2-0.5-0.2c-3.5-0.3-7-0.2-10.5-0.2h-10.4c-3.5,0-6.9,0-10.4,0l-5.1,0
|
||||
c-1.3,0-2.6,0-3.9,0.2c-0.3-9.5-0.3-19.2-3.3-28.3c-1.9-5.9-5.1-11.3-9.5-15.8c-4.1-4-9-7.2-14.3-9.3c-10-3.9-21-4.3-31.2-1
|
||||
c-10.1,3.1-18.5,10.2-23.1,19.6c-3.2,7.1-5.1,14.7-5.7,22.5c-0.5,7.3-0.4,14.6,0.2,21.9c0.1,1.3,0.2,2.5,0.3,3.7
|
||||
c-0.5-0.2-1.1-0.2-1.6,0.1c-1,0.6-1.6,1.6-1.6,2.8c0,0.6,0,1.1,0.1,1.7c0,0.6,0,1.2,0.1,1.8l0.1,1.9c0,0.4,0,0.8,0,1.2
|
||||
c-1.3,1.2-2.4,2.6-3.3,4c-1.1,1.6-2.1,3.2-3.1,4.8c-2,3.2-3.9,6.5-5.8,9.8c-3.6,6.6-6.8,13.3-9.6,20.3c-1.6,3.9-3.1,7.8-4.5,11.7
|
||||
l-26.4-7.2c-2.5-0.7-5.1-1.4-7.6-2.1c-1.3-0.4-2.6-0.7-4-1c-1.3-0.4-2.6-0.7-3.9-0.8c-0.4,0-0.7,0.4-0.7,0.8
|
||||
c0,0.3,0.1,0.5,0.4,0.6c0,0,0,0,0,0c0.2,1.2,0.6,2.3,1.2,3.3c0.7,1.5,1.4,3,2.2,4.4l4.4,8.8c2.9,5.9,5.8,11.8,8.8,17.7l5,10.1
|
||||
c0.1,0.3,0.4,0.5,0.7,0.5l13.5,4.6c-0.2,1.9-1.3,4.4,1,5.5c1.3,0.5,2.6,0.9,3.9,1.2l4.4,1.2c2.8,0.8,5.7,1.7,8.5,2.5
|
||||
c1.1,0.3,2.2,0.6,3-0.4c0.3-0.5,0.5-1,0.7-1.5c0.1-0.4,0.2-0.8,0.3-1.1l7.2,2.5c0.3,9.1,0.4,18.3,0.6,27.4c0,1.8,0.1,3.7,0.1,5.5
|
||||
H5.9L5.8,155.3c0,0-0.1-3.2-0.1-5.9c0-2.5,0.1-5.1,0.2-7.8l0-0.4C10.4,61.5,78.6,0.8,158.1,5.3c73.2,4.2,131.6,62.6,135.7,135.7
|
||||
l0.2,5.5L294.1,294.3z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 27 KiB |
Binary file not shown.
After Width: | Height: | Size: 8.3 KiB |
|
@ -1,41 +1,44 @@
|
|||
context("Add Multi-Option Datatype", () => {
|
||||
before(() => {
|
||||
cy.login()
|
||||
cy.createTestApp()
|
||||
})
|
||||
before(() => {
|
||||
cy.login()
|
||||
cy.createTestApp()
|
||||
})
|
||||
|
||||
it("should create a new table, with data", () => {
|
||||
cy.createTable("Multi Data")
|
||||
cy.addColumn("Multi Data", "Test Data", "Multi-select", "1\n2\n3\n4\n5")
|
||||
cy.addRowMultiValue(["1", "2", "3", "4", "5"])
|
||||
})
|
||||
it("should create a new table, with data", () => {
|
||||
cy.createTable("Multi Data")
|
||||
cy.addColumn("Multi Data", "Test Data", "Multi-select", "1\n2\n3\n4\n5")
|
||||
cy.addRowMultiValue(["1", "2", "3", "4", "5"])
|
||||
})
|
||||
|
||||
it ("should add form with multi select picker, containing 5 options", () => {
|
||||
cy.navigateToFrontend()
|
||||
cy.wait(500)
|
||||
// Add data provider
|
||||
cy.get(`[data-cy="category-Data Provider"]`).click()
|
||||
cy.get('[data-cy="dataSource-prop-control"]').click()
|
||||
cy.get(".dropdown").contains("Multi Data").click()
|
||||
cy.wait(500)
|
||||
// Add Form with schema to match table
|
||||
cy.addComponent("Form", "Form")
|
||||
cy.get('[data-cy="dataSource-prop-control"').click()
|
||||
cy.get(".dropdown").contains("Multi Data").click()
|
||||
cy.wait(500)
|
||||
// Add multi-select picker to form
|
||||
cy.addComponent("Form", "Multi-select Picker").then((componentId) => {
|
||||
cy.get('[data-cy="field-prop-control"]').type("Test Data").type('{enter}')
|
||||
cy.wait(1000)
|
||||
cy.getComponent(componentId).contains("Choose some options").click()
|
||||
// Check picker has 5 items
|
||||
cy.getComponent(componentId).find('li').should('have.length', 5)
|
||||
// Select all items
|
||||
for (let i = 1; i < 6; i++) {
|
||||
cy.getComponent(componentId).find('li').contains(i).click()
|
||||
}
|
||||
// Check items have been selected
|
||||
cy.getComponent(componentId).find('.spectrum-Picker-label').contains("(5)")
|
||||
})
|
||||
it("should add form with multi select picker, containing 5 options", () => {
|
||||
cy.navigateToFrontend()
|
||||
cy.wait(500)
|
||||
// Add data provider
|
||||
cy.get(`[data-cy="category-Data"]`).click()
|
||||
cy.get(`[data-cy="component-Data Provider"]`).click()
|
||||
cy.get('[data-cy="dataSource-prop-control"]').click()
|
||||
cy.get(".dropdown").contains("Multi Data").click()
|
||||
cy.wait(500)
|
||||
// Add Form with schema to match table
|
||||
cy.addComponent("Form", "Form")
|
||||
cy.get('[data-cy="dataSource-prop-control"').click()
|
||||
cy.get(".dropdown").contains("Multi Data").click()
|
||||
cy.wait(500)
|
||||
// Add multi-select picker to form
|
||||
cy.addComponent("Form", "Multi-select Picker").then(componentId => {
|
||||
cy.get('[data-cy="field-prop-control"]').type("Test Data").type("{enter}")
|
||||
cy.wait(1000)
|
||||
cy.getComponent(componentId).contains("Choose some options").click()
|
||||
// Check picker has 5 items
|
||||
cy.getComponent(componentId).find("li").should("have.length", 5)
|
||||
// Select all items
|
||||
for (let i = 1; i < 6; i++) {
|
||||
cy.getComponent(componentId).find("li").contains(i).click()
|
||||
}
|
||||
// Check items have been selected
|
||||
cy.getComponent(componentId)
|
||||
.find(".spectrum-Picker-label")
|
||||
.contains("(5)")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -12,7 +12,7 @@ context("Create a automation", () => {
|
|||
cy.get("[data-cy='new-screen'] > .spectrum-Icon").click()
|
||||
cy.get(".modal-inner-wrapper").within(() => {
|
||||
cy.get("input").type("Add Row")
|
||||
cy.contains("Row Created").click()
|
||||
cy.contains("Row Created").click({ force: true })
|
||||
cy.wait(500)
|
||||
cy.get(".spectrum-Button--cta").click()
|
||||
})
|
||||
|
@ -22,9 +22,9 @@ context("Create a automation", () => {
|
|||
cy.get(".spectrum-Picker-label").click()
|
||||
cy.wait(500)
|
||||
cy.contains("dog").click()
|
||||
|
||||
cy.wait(2000)
|
||||
// Create action
|
||||
cy.contains("Add Action").click()
|
||||
cy.get(".block > .spectrum-Icon").click()
|
||||
cy.get(".modal-inner-wrapper").within(() => {
|
||||
cy.wait(1000)
|
||||
cy.contains("Create Row").trigger('mouseover').click().click()
|
||||
|
|
|
@ -53,6 +53,7 @@ context("Create a Table", () => {
|
|||
cy.get(".spectrum-Table-editIcon > use").click()
|
||||
cy.contains("Delete").click()
|
||||
cy.wait(50)
|
||||
cy.get(`[data-cy="delete-column-confirm"]`).type("nameupdated")
|
||||
cy.contains("Delete Column").click()
|
||||
cy.contains("nameupdated").should("not.exist")
|
||||
})
|
||||
|
@ -66,6 +67,7 @@ context("Create a Table", () => {
|
|||
cy.get(".actions .spectrum-Icon").click({ force: true })
|
||||
})
|
||||
cy.get(".spectrum-Menu > :nth-child(2)").click()
|
||||
cy.get(`[data-cy="delete-table-confirm"]`).type("dog")
|
||||
cy.contains("Delete Table").click()
|
||||
cy.contains("dog").should("not.exist")
|
||||
})
|
||||
|
|
|
@ -17,6 +17,7 @@ process.env.JWT_SECRET = cypressConfig.env.JWT_SECRET
|
|||
process.env.COUCH_URL = `leveldb://${tmpdir}/.data/`
|
||||
process.env.SELF_HOSTED = 1
|
||||
process.env.WORKER_URL = "http://localhost:10002/"
|
||||
process.env.APPS_URL = `http://localhost:${MAIN_PORT}/`
|
||||
process.env.MINIO_URL = `http://localhost:${MAIN_PORT}/`
|
||||
process.env.MINIO_ACCESS_KEY = "budibase"
|
||||
process.env.MINIO_SECRET_KEY = "budibase"
|
||||
|
|
|
@ -182,16 +182,23 @@ Cypress.Commands.add("navigateToFrontend", () => {
|
|||
cy.wait(1000)
|
||||
cy.contains("Design").click()
|
||||
cy.get(".spectrum-Search").type("/")
|
||||
cy.get(".nav-item").contains("Home").click()
|
||||
cy.createScreen("home", "home")
|
||||
cy.addComponent("Elements", "Headline")
|
||||
cy.get(".nav-item").contains("home").click()
|
||||
})
|
||||
|
||||
Cypress.Commands.add("createScreen", (screenName, route) => {
|
||||
cy.get("[aria-label=AddCircle]").click()
|
||||
cy.get(".spectrum-Modal").within(() => {
|
||||
cy.get("input").first().type(screenName)
|
||||
cy.get("input").eq(1).type(route)
|
||||
cy.get(".item").first().click()
|
||||
cy.get(".spectrum-Button--cta").click()
|
||||
})
|
||||
cy.get(".spectrum-Modal").within(() => {
|
||||
cy.get("input").first().clear().type(screenName)
|
||||
cy.get("input").eq(1).clear().type(route)
|
||||
cy.get(".spectrum-Button--cta").click()
|
||||
cy.wait(2000)
|
||||
})
|
||||
})
|
||||
|
||||
Cypress.Commands.add("expandBudibaseConnection", () => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/builder",
|
||||
"version": "0.9.189",
|
||||
"version": "0.9.190-alpha.12",
|
||||
"license": "GPL-3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
@ -9,7 +9,7 @@
|
|||
"test": "jest",
|
||||
"test:watch": "jest --watchAll",
|
||||
"dev:builder": "routify -c dev:vite",
|
||||
"dev:vite": "vite",
|
||||
"dev:vite": "vite --host 0.0.0.0",
|
||||
"rollup": "rollup -c -w",
|
||||
"cy:setup": "node ./cypress/setup.js",
|
||||
"cy:run": "cypress run",
|
||||
|
@ -65,10 +65,10 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "^0.9.189",
|
||||
"@budibase/client": "^0.9.189",
|
||||
"@budibase/bbui": "^0.9.190-alpha.12",
|
||||
"@budibase/client": "^0.9.190-alpha.12",
|
||||
"@budibase/colorpicker": "1.1.2",
|
||||
"@budibase/string-templates": "^0.9.189",
|
||||
"@budibase/string-templates": "^0.9.190-alpha.12",
|
||||
"@sentry/browser": "5.19.1",
|
||||
"@spectrum-css/page": "^3.0.1",
|
||||
"@spectrum-css/vars": "^3.0.1",
|
||||
|
@ -91,7 +91,7 @@
|
|||
"@babel/runtime": "^7.13.10",
|
||||
"@rollup/plugin-replace": "^2.4.2",
|
||||
"@roxi/routify": "2.18.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.5",
|
||||
"@sveltejs/vite-plugin-svelte": "1.0.0-next.19",
|
||||
"@testing-library/jest-dom": "^5.11.10",
|
||||
"@testing-library/svelte": "^3.0.0",
|
||||
"babel-jest": "^26.6.3",
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
findAllMatchingComponents,
|
||||
findComponent,
|
||||
findComponentPath,
|
||||
getComponentSettings,
|
||||
} from "./storeUtils"
|
||||
import { store } from "builderStore"
|
||||
import { queries as queriesStores, tables as tablesStore } from "stores/backend"
|
||||
|
@ -30,14 +31,33 @@ export const getBindableProperties = (asset, componentId) => {
|
|||
const deviceBindings = getDeviceBindings()
|
||||
const stateBindings = getStateBindings()
|
||||
return [
|
||||
...stateBindings,
|
||||
...deviceBindings,
|
||||
...urlBindings,
|
||||
...contextBindings,
|
||||
...urlBindings,
|
||||
...stateBindings,
|
||||
...userBindings,
|
||||
...deviceBindings,
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the bindable properties exposed by a certain component.
|
||||
*/
|
||||
export const getComponentBindableProperties = (asset, componentId) => {
|
||||
if (!asset || !componentId) {
|
||||
return []
|
||||
}
|
||||
|
||||
// Ensure that the component exists and exposes context
|
||||
const component = findComponent(asset.props, componentId)
|
||||
const def = store.actions.components.getDefinition(component?._component)
|
||||
if (!def?.context) {
|
||||
return []
|
||||
}
|
||||
|
||||
// Get the bindings for the component
|
||||
return getProviderContextBindings(asset, component)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all data provider components above a component.
|
||||
*/
|
||||
|
@ -82,13 +102,10 @@ export const getActionProviderComponents = (asset, componentId, actionType) => {
|
|||
* Gets a datasource object for a certain data provider component
|
||||
*/
|
||||
export const getDatasourceForProvider = (asset, component) => {
|
||||
const def = store.actions.components.getDefinition(component?._component)
|
||||
if (!def) {
|
||||
return null
|
||||
}
|
||||
const settings = getComponentSettings(component?._component)
|
||||
|
||||
// If this component has a dataProvider setting, go up the stack and use it
|
||||
const dataProviderSetting = def.settings.find(setting => {
|
||||
const dataProviderSetting = settings.find(setting => {
|
||||
return setting.type === "dataProvider"
|
||||
})
|
||||
if (dataProviderSetting) {
|
||||
|
@ -100,7 +117,7 @@ export const getDatasourceForProvider = (asset, component) => {
|
|||
|
||||
// Extract datasource from component instance
|
||||
const validSettingTypes = ["dataSource", "table", "schema"]
|
||||
const datasourceSetting = def.settings.find(setting => {
|
||||
const datasourceSetting = settings.find(setting => {
|
||||
return validSettingTypes.includes(setting.type)
|
||||
})
|
||||
if (!datasourceSetting) {
|
||||
|
@ -127,9 +144,26 @@ export const getDatasourceForProvider = (asset, component) => {
|
|||
const getContextBindings = (asset, componentId) => {
|
||||
// Extract any components which provide data contexts
|
||||
const dataProviders = getDataProviderComponents(asset, componentId)
|
||||
let bindings = []
|
||||
|
||||
// Generate bindings for all matching components
|
||||
return getProviderContextBindings(asset, dataProviders)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the context bindings exposed by a set of data provider components.
|
||||
*/
|
||||
const getProviderContextBindings = (asset, dataProviders) => {
|
||||
if (!asset || !dataProviders) {
|
||||
return []
|
||||
}
|
||||
|
||||
// Ensure providers is an array
|
||||
if (!Array.isArray(dataProviders)) {
|
||||
dataProviders = [dataProviders]
|
||||
}
|
||||
|
||||
// Create bindings for each data provider
|
||||
let bindings = []
|
||||
dataProviders.forEach(component => {
|
||||
const def = store.actions.components.getDefinition(component._component)
|
||||
const contexts = Array.isArray(def.context) ? def.context : [def.context]
|
||||
|
@ -142,6 +176,7 @@ const getContextBindings = (asset, componentId) => {
|
|||
|
||||
let schema
|
||||
let readablePrefix
|
||||
let runtimeSuffix = context.suffix
|
||||
|
||||
if (context.type === "form") {
|
||||
// Forms do not need table schemas
|
||||
|
@ -171,22 +206,19 @@ const getContextBindings = (asset, componentId) => {
|
|||
|
||||
const keys = Object.keys(schema).sort()
|
||||
|
||||
// Generate safe unique runtime prefix
|
||||
let providerId = component._id
|
||||
if (runtimeSuffix) {
|
||||
providerId += `-${runtimeSuffix}`
|
||||
}
|
||||
const safeComponentId = makePropSafe(providerId)
|
||||
|
||||
// Create bindable properties for each schema field
|
||||
const safeComponentId = makePropSafe(component._id)
|
||||
keys.forEach(key => {
|
||||
const fieldSchema = schema[key]
|
||||
|
||||
// Make safe runtime binding and replace certain bindings with a
|
||||
// new property to help display components
|
||||
let runtimeBoundKey = key
|
||||
if (fieldSchema.type === "link") {
|
||||
runtimeBoundKey = `${key}_text`
|
||||
} else if (fieldSchema.type === "attachment") {
|
||||
runtimeBoundKey = `${key}_first`
|
||||
}
|
||||
const runtimeBinding = `${safeComponentId}.${makePropSafe(
|
||||
runtimeBoundKey
|
||||
)}`
|
||||
// Make safe runtime binding
|
||||
const runtimeBinding = `${safeComponentId}.${makePropSafe(key)}`
|
||||
|
||||
// Optionally use a prefix with readable bindings
|
||||
let readableBinding = component._instanceName
|
||||
|
@ -203,7 +235,7 @@ const getContextBindings = (asset, componentId) => {
|
|||
// Field schema and provider are required to construct relationship
|
||||
// datasource options, based on bindable properties
|
||||
fieldSchema,
|
||||
providerId: component._id,
|
||||
providerId,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -225,17 +257,9 @@ const getUserBindings = () => {
|
|||
const safeUser = makePropSafe("user")
|
||||
keys.forEach(key => {
|
||||
const fieldSchema = schema[key]
|
||||
// Replace certain bindings with a new property to help display components
|
||||
let runtimeBoundKey = key
|
||||
if (fieldSchema.type === "link") {
|
||||
runtimeBoundKey = `${key}_text`
|
||||
} else if (fieldSchema.type === "attachment") {
|
||||
runtimeBoundKey = `${key}_first`
|
||||
}
|
||||
|
||||
bindings.push({
|
||||
type: "context",
|
||||
runtimeBinding: `${safeUser}.${makePropSafe(runtimeBoundKey)}`,
|
||||
runtimeBinding: `${safeUser}.${makePropSafe(key)}`,
|
||||
readableBinding: `Current User.${key}`,
|
||||
// Field schema and provider are required to construct relationship
|
||||
// datasource options, based on bindable properties
|
||||
|
@ -309,8 +333,11 @@ const getUrlBindings = asset => {
|
|||
*/
|
||||
export const getSchemaForDatasource = (asset, datasource, isForm = false) => {
|
||||
let schema, table
|
||||
|
||||
if (datasource) {
|
||||
const { type } = datasource
|
||||
|
||||
// Determine the source table from the datasource type
|
||||
if (type === "provider") {
|
||||
const component = findComponent(asset.props, datasource.providerId)
|
||||
const source = getDatasourceForProvider(asset, component)
|
||||
|
@ -318,11 +345,32 @@ export const getSchemaForDatasource = (asset, datasource, isForm = false) => {
|
|||
} else if (type === "query") {
|
||||
const queries = get(queriesStores).list
|
||||
table = queries.find(query => query._id === datasource._id)
|
||||
} else if (type === "field") {
|
||||
table = { name: datasource.fieldName }
|
||||
const { fieldType } = datasource
|
||||
if (fieldType === "attachment") {
|
||||
schema = {
|
||||
url: {
|
||||
type: "string",
|
||||
},
|
||||
name: {
|
||||
type: "string",
|
||||
},
|
||||
}
|
||||
} else if (fieldType === "array") {
|
||||
schema = {
|
||||
value: {
|
||||
type: "string",
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const tables = get(tablesStore).list
|
||||
table = tables.find(table => table._id === datasource.tableId)
|
||||
}
|
||||
if (table) {
|
||||
|
||||
// Determine the schema from the table if not already determined
|
||||
if (table && !schema) {
|
||||
if (type === "view") {
|
||||
schema = cloneDeep(table.views?.[datasource.name]?.schema)
|
||||
} else if (type === "query" && isForm) {
|
||||
|
@ -374,8 +422,8 @@ const buildFormSchema = component => {
|
|||
if (!component) {
|
||||
return schema
|
||||
}
|
||||
const def = store.actions.components.getDefinition(component._component)
|
||||
const fieldSetting = def?.settings?.find(
|
||||
const settings = getComponentSettings(component._component)
|
||||
const fieldSetting = settings.find(
|
||||
setting => setting.key === "field" && setting.type.startsWith("field/")
|
||||
)
|
||||
if (fieldSetting && component.field) {
|
||||
|
@ -501,7 +549,7 @@ function bindingReplacement(bindableProperties, textWithBindings, convertTo) {
|
|||
* {{ literal [componentId] }}
|
||||
*/
|
||||
function extractLiteralHandlebarsID(value) {
|
||||
return value?.match(/{{\s*literal[\s[]+([a-fA-F0-9]+)[\s\]]*}}/)?.[1]
|
||||
return value?.match(/{{\s*literal\s*\[+([^\]]+)].*}}/)?.[1]
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -93,7 +93,9 @@ const automationActions = store => ({
|
|||
},
|
||||
select: automation => {
|
||||
store.update(state => {
|
||||
let testResults = state.selectedAutomation?.testResults
|
||||
state.selectedAutomation = new Automation(cloneDeep(automation))
|
||||
state.selectedAutomation.testResults = testResults
|
||||
state.selectedBlock = null
|
||||
return state
|
||||
})
|
||||
|
|
|
@ -25,6 +25,7 @@ import {
|
|||
findClosestMatchingComponent,
|
||||
findAllMatchingComponents,
|
||||
findComponent,
|
||||
getComponentSettings,
|
||||
} from "../storeUtils"
|
||||
import { uuid } from "../uuid"
|
||||
import { removeBindings } from "../dataBinding"
|
||||
|
@ -369,14 +370,13 @@ export const getFrontendStore = () => {
|
|||
}
|
||||
|
||||
// Generate default props
|
||||
const settings = getComponentSettings(componentName)
|
||||
let props = { ...presetProps }
|
||||
if (definition.settings) {
|
||||
definition.settings.forEach(setting => {
|
||||
if (setting.defaultValue !== undefined) {
|
||||
props[setting.key] = setting.defaultValue
|
||||
}
|
||||
})
|
||||
}
|
||||
settings.forEach(setting => {
|
||||
if (setting.defaultValue !== undefined) {
|
||||
props[setting.key] = setting.defaultValue
|
||||
}
|
||||
})
|
||||
|
||||
// Add any extra properties the component needs
|
||||
let extras = {}
|
||||
|
@ -524,7 +524,7 @@ export const getFrontendStore = () => {
|
|||
}
|
||||
}
|
||||
},
|
||||
paste: async (targetComponent, mode) => {
|
||||
paste: async (targetComponent, mode, preserveBindings = false) => {
|
||||
let promises = []
|
||||
store.update(state => {
|
||||
// Stop if we have nothing to paste
|
||||
|
@ -536,7 +536,7 @@ export const getFrontendStore = () => {
|
|||
const cut = state.componentToPaste.isCut
|
||||
|
||||
// immediately need to remove bindings, currently these aren't valid when pasted
|
||||
if (!cut) {
|
||||
if (!cut && !preserveBindings) {
|
||||
state.componentToPaste = removeBindings(state.componentToPaste)
|
||||
}
|
||||
|
||||
|
@ -620,6 +620,9 @@ export const getFrontendStore = () => {
|
|||
if (!name || !component) {
|
||||
return
|
||||
}
|
||||
if (component[name] === value) {
|
||||
return
|
||||
}
|
||||
component[name] = value
|
||||
store.update(state => {
|
||||
state.selectedComponentId = component._id
|
||||
|
|
|
@ -2,7 +2,9 @@ import { Screen } from "./utils/Screen"
|
|||
|
||||
export default {
|
||||
name: `Create from scratch`,
|
||||
id: `createFromScratch`,
|
||||
create: () => createScreen(),
|
||||
table: `Create from scratch`,
|
||||
}
|
||||
|
||||
const createScreen = () => {
|
||||
|
|
|
@ -15,6 +15,7 @@ export default function (tables) {
|
|||
name: `${table.name} - New`,
|
||||
create: () => createScreen(table),
|
||||
id: NEW_ROW_TEMPLATE,
|
||||
table: table.name,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ export default function (tables) {
|
|||
name: `${table.name} - Detail`,
|
||||
create: () => createScreen(table),
|
||||
id: ROW_DETAIL_TEMPLATE,
|
||||
table: table.name,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ export default function (tables) {
|
|||
name: `${table.name} - List`,
|
||||
create: () => createScreen(table),
|
||||
id: ROW_LIST_TEMPLATE,
|
||||
table: table.name,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { store } from "./index"
|
||||
|
||||
/**
|
||||
* Recursively searches for a specific component ID
|
||||
*/
|
||||
|
@ -123,3 +125,20 @@ const searchComponentTree = (rootComponent, matchComponent) => {
|
|||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches a component's definition for a setting matching a certin predicate.
|
||||
*/
|
||||
export const getComponentSettings = componentType => {
|
||||
const def = store.actions.components.getDefinition(componentType)
|
||||
if (!def) {
|
||||
return []
|
||||
}
|
||||
let settings = def.settings?.filter(setting => !setting.section) ?? []
|
||||
def.settings
|
||||
?.filter(setting => setting.section)
|
||||
.forEach(section => {
|
||||
settings = settings.concat(section.settings || [])
|
||||
})
|
||||
return settings
|
||||
}
|
||||
|
|
|
@ -84,7 +84,6 @@
|
|||
class="block"
|
||||
animate:flip={{ duration: 500 }}
|
||||
in:fly|local={{ x: 500, duration: 1500 }}
|
||||
out:fly|local={{ x: 500, duration: 800 }}
|
||||
>
|
||||
<FlowItem {testDataModal} {testAutomation} {onSelect} {block} />
|
||||
</div>
|
||||
|
|
|
@ -25,10 +25,10 @@
|
|||
let resultsModal
|
||||
let setupToggled
|
||||
let blockComplete
|
||||
|
||||
$: testResult = $automationStore.selectedAutomation.testResults?.steps.filter(
|
||||
step => step.stepId === block.stepId
|
||||
)
|
||||
|
||||
$: isTrigger = block.type === "TRIGGER"
|
||||
|
||||
$: selected = $automationStore.selectedBlock?.id === block.id
|
||||
|
@ -150,15 +150,6 @@
|
|||
>Finish and test automation</Button
|
||||
>
|
||||
{/if}
|
||||
<Button
|
||||
disabled={!hasCompletedInputs}
|
||||
on:click={() => {
|
||||
setupToggled = false
|
||||
actionModal.show()
|
||||
}}
|
||||
primary={!isTrigger}
|
||||
cta={isTrigger}>Add Action</Button
|
||||
>
|
||||
{/if}
|
||||
</Layout>
|
||||
</div>
|
||||
|
@ -202,7 +193,7 @@
|
|||
display: inline-block;
|
||||
}
|
||||
.block {
|
||||
width: 360px;
|
||||
width: 480px;
|
||||
font-size: 16px;
|
||||
background-color: var(--background);
|
||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
onMount(() => {
|
||||
automationStore.actions.fetch()
|
||||
})
|
||||
|
||||
function selectAutomation(automation) {
|
||||
automationStore.actions.select(automation)
|
||||
$goto(`./${automation._id}`)
|
||||
|
|
|
@ -3,7 +3,14 @@
|
|||
import { database } from "stores/backend"
|
||||
import { automationStore } from "builderStore"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import { Input, ModalContent, Layout, Body, Icon } from "@budibase/bbui"
|
||||
import {
|
||||
Input,
|
||||
InlineAlert,
|
||||
ModalContent,
|
||||
Layout,
|
||||
Body,
|
||||
Icon,
|
||||
} from "@budibase/bbui"
|
||||
import analytics, { Events } from "analytics"
|
||||
|
||||
let name
|
||||
|
@ -56,6 +63,10 @@
|
|||
onConfirm={createAutomation}
|
||||
disabled={!selectedTrigger || !name}
|
||||
>
|
||||
<InlineAlert
|
||||
header="You must publish your app to activate your automations."
|
||||
message="To test your automation before publishing, you can use the 'Run Test' functionality on the next screen."
|
||||
/>
|
||||
<Body size="XS"
|
||||
>Please name your automation, then select a trigger. Every automation must
|
||||
start with a trigger.
|
||||
|
|
|
@ -51,31 +51,31 @@
|
|||
: { schema: {} }
|
||||
$: schemaFields = table ? Object.values(table.schema) : []
|
||||
|
||||
const onChange = debounce(
|
||||
async function (e, key) {
|
||||
if (isTestModal) {
|
||||
// Special case for webhook, as it requires a body, but the schema already brings back the body's contents
|
||||
if (stepId === "WEBHOOK") {
|
||||
automationStore.actions.addTestDataToAutomation({
|
||||
body: {
|
||||
[key]: e.detail,
|
||||
...$automationStore.selectedAutomation.automation.testData.body,
|
||||
},
|
||||
})
|
||||
}
|
||||
const onChange = debounce(async function (e, key) {
|
||||
if (isTestModal) {
|
||||
// Special case for webhook, as it requires a body, but the schema already brings back the body's contents
|
||||
if (stepId === "WEBHOOK") {
|
||||
automationStore.actions.addTestDataToAutomation({
|
||||
[key]: e.detail,
|
||||
body: {
|
||||
[key]: e.detail,
|
||||
...$automationStore.selectedAutomation.automation.testData.body,
|
||||
},
|
||||
})
|
||||
testData[key] = e.detail
|
||||
} else {
|
||||
block.inputs[key] = e.detail
|
||||
await automationStore.actions.save(
|
||||
$automationStore.selectedAutomation?.automation
|
||||
)
|
||||
}
|
||||
},
|
||||
isTestModal ? 0 : 800
|
||||
)
|
||||
automationStore.actions.addTestDataToAutomation({
|
||||
[key]: e.detail,
|
||||
})
|
||||
testData[key] = e.detail
|
||||
await automationStore.actions.save(
|
||||
$automationStore.selectedAutomation?.automation
|
||||
)
|
||||
} else {
|
||||
block.inputs[key] = e.detail
|
||||
await automationStore.actions.save(
|
||||
$automationStore.selectedAutomation?.automation
|
||||
)
|
||||
}
|
||||
}, 800)
|
||||
|
||||
function getAvailableBindings(block, automation) {
|
||||
if (!block || !automation) {
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
import CreateRowButton from "./buttons/CreateRowButton.svelte"
|
||||
import CreateColumnButton from "./buttons/CreateColumnButton.svelte"
|
||||
import CreateViewButton from "./buttons/CreateViewButton.svelte"
|
||||
import ExistingRelationshipButton from "./buttons/ExistingRelationshipButton.svelte"
|
||||
import ExportButton from "./buttons/ExportButton.svelte"
|
||||
import ImportButton from "./buttons/ImportButton.svelte"
|
||||
import EditRolesButton from "./buttons/EditRolesButton.svelte"
|
||||
import ManageAccessButton from "./buttons/ManageAccessButton.svelte"
|
||||
import HideAutocolumnButton from "./buttons/HideAutocolumnButton.svelte"
|
||||
|
@ -98,9 +100,7 @@
|
|||
on:updatecolumns={onUpdateColumns}
|
||||
on:updaterows={onUpdateRows}
|
||||
>
|
||||
{#if isInternal}
|
||||
<CreateColumnButton on:updatecolumns={onUpdateColumns} />
|
||||
{/if}
|
||||
<CreateColumnButton on:updatecolumns={onUpdateColumns} />
|
||||
{#if schema && Object.keys(schema).length > 0}
|
||||
{#if !isUsersTable}
|
||||
<CreateRowButton
|
||||
|
@ -116,9 +116,19 @@
|
|||
{#if isUsersTable}
|
||||
<EditRolesButton />
|
||||
{/if}
|
||||
{#if !isInternal}
|
||||
<ExistingRelationshipButton
|
||||
table={$tables.selected}
|
||||
on:updatecolumns={onUpdateColumns}
|
||||
/>
|
||||
{/if}
|
||||
<HideAutocolumnButton bind:hideAutocolumns />
|
||||
<!-- always have the export last -->
|
||||
<ExportButton view={$tables.selected?._id} />
|
||||
<ImportButton
|
||||
tableId={$tables.selected?._id}
|
||||
on:updaterows={onUpdateRows}
|
||||
/>
|
||||
{#key id}
|
||||
<TableFilterButton {schema} on:change={onFilter} />
|
||||
{/key}
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
export let value = defaultValue || (meta.type === "boolean" ? false : "")
|
||||
export let readonly
|
||||
|
||||
$: type = meta.type
|
||||
$: label = capitalise(meta.name)
|
||||
$: type = meta?.type
|
||||
$: label = meta.name ? capitalise(meta.name) : ""
|
||||
</script>
|
||||
|
||||
{#if type === "options"}
|
||||
|
|
|
@ -8,7 +8,11 @@
|
|||
import CreateEditRow from "./modals/CreateEditRow.svelte"
|
||||
import CreateEditUser from "./modals/CreateEditUser.svelte"
|
||||
import CreateEditColumn from "./modals/CreateEditColumn.svelte"
|
||||
import { TableNames, UNEDITABLE_USER_FIELDS } from "constants"
|
||||
import {
|
||||
TableNames,
|
||||
UNEDITABLE_USER_FIELDS,
|
||||
UNSORTABLE_TYPES,
|
||||
} from "constants"
|
||||
import RoleCell from "./cells/RoleCell.svelte"
|
||||
|
||||
export let schema = {}
|
||||
|
@ -33,6 +37,15 @@
|
|||
$: isUsersTable = tableId === TableNames.USERS
|
||||
$: data && resetSelectedRows()
|
||||
$: editRowComponent = isUsersTable ? CreateEditUser : CreateEditRow
|
||||
$: {
|
||||
UNSORTABLE_TYPES.forEach(type => {
|
||||
Object.values(schema).forEach(col => {
|
||||
if (col.type === type) {
|
||||
col.sortable = false
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
$: {
|
||||
if (isUsersTable) {
|
||||
customRenderers = [
|
||||
|
@ -129,7 +142,7 @@
|
|||
bind:selectedRows
|
||||
allowSelectRows={allowEditing && !isUsersTable}
|
||||
allowEditRows={allowEditing}
|
||||
allowEditColumns={allowEditing && isInternal}
|
||||
allowEditColumns={allowEditing}
|
||||
showAutoColumns={!hideAutocolumns}
|
||||
on:editcolumn={e => editColumn(e.detail)}
|
||||
on:editrow={e => editRow(e.detail)}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
<script>
|
||||
import { ActionButton, Modal, notifications } from "@budibase/bbui"
|
||||
import CreateEditRelationship from "../../Datasources/CreateEditRelationship.svelte"
|
||||
import { datasources, tables } from "../../../../stores/backend"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
export let table
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: plusTables = datasource?.plus
|
||||
? Object.values(datasource?.entities || {})
|
||||
: []
|
||||
$: datasource = $datasources.list.find(
|
||||
source => source._id === table?.sourceId
|
||||
)
|
||||
|
||||
let modal
|
||||
|
||||
async function saveRelationship() {
|
||||
try {
|
||||
// Create datasource
|
||||
await datasources.save(datasource)
|
||||
notifications.success(`Relationship information saved.`)
|
||||
const tableList = await tables.fetch()
|
||||
await tables.select(tableList.find(tbl => tbl._id === table._id))
|
||||
dispatch("updatecolumns")
|
||||
} catch (err) {
|
||||
notifications.error(`Error saving relationship info: ${err}`)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if table.sourceId}
|
||||
<div>
|
||||
<ActionButton
|
||||
icon="DataCorrelated"
|
||||
primary
|
||||
size="S"
|
||||
quiet
|
||||
on:click={modal.show}
|
||||
>
|
||||
Define existing relationship
|
||||
</ActionButton>
|
||||
</div>
|
||||
<Modal bind:this={modal}>
|
||||
<CreateEditRelationship
|
||||
{datasource}
|
||||
save={saveRelationship}
|
||||
close={modal.hide}
|
||||
{plusTables}
|
||||
selectedFromTable={table}
|
||||
/>
|
||||
</Modal>
|
||||
{/if}
|
|
@ -7,7 +7,7 @@
|
|||
let modal
|
||||
</script>
|
||||
|
||||
<ActionButton icon="Download" size="S" quiet on:click={modal.show}>
|
||||
<ActionButton icon="DataDownload" size="S" quiet on:click={modal.show}>
|
||||
Export
|
||||
</ActionButton>
|
||||
<Modal bind:this={modal}>
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<script>
|
||||
import { ActionButton, Modal } from "@budibase/bbui"
|
||||
import ImportModal from "../modals/ImportModal.svelte"
|
||||
|
||||
export let tableId
|
||||
|
||||
let modal
|
||||
</script>
|
||||
|
||||
<ActionButton icon="DataUpload" size="S" quiet on:click={modal.show}>
|
||||
Import
|
||||
</ActionButton>
|
||||
<Modal bind:this={modal}>
|
||||
<ImportModal {tableId} on:updaterows />
|
||||
</Modal>
|
|
@ -9,6 +9,7 @@
|
|||
DatePicker,
|
||||
ModalContent,
|
||||
Context,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { cloneDeep } from "lodash/fp"
|
||||
|
@ -18,9 +19,13 @@
|
|||
FIELDS,
|
||||
AUTO_COLUMN_SUB_TYPES,
|
||||
RelationshipTypes,
|
||||
ALLOWABLE_STRING_OPTIONS,
|
||||
ALLOWABLE_NUMBER_OPTIONS,
|
||||
ALLOWABLE_STRING_TYPES,
|
||||
ALLOWABLE_NUMBER_TYPES,
|
||||
SWITCHABLE_TYPES,
|
||||
} from "constants/backend"
|
||||
import { getAutoColumnInformation, buildAutoColumn } from "builderStore/utils"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import ValuesList from "components/common/ValuesList.svelte"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import { truncate } from "lodash"
|
||||
|
@ -31,6 +36,10 @@
|
|||
const AUTO_TYPE = "auto"
|
||||
const FORMULA_TYPE = FIELDS.FORMULA.type
|
||||
const LINK_TYPE = FIELDS.LINK.type
|
||||
const STRING_TYPE = FIELDS.STRING.type
|
||||
const NUMBER_TYPE = FIELDS.NUMBER.type
|
||||
const DATE_TYPE = FIELDS.DATETIME.type
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const PROHIBITED_COLUMN_NAMES = ["type", "_id", "_rev", "tableId"]
|
||||
const { hide } = getContext(Context.Modal)
|
||||
|
@ -54,10 +63,9 @@
|
|||
let indexes = [...($tables.selected.indexes || [])]
|
||||
let confirmDeleteDialog
|
||||
let deletion
|
||||
let deleteColName
|
||||
|
||||
$: tableOptions = $tables.list.filter(
|
||||
table => table._id !== $tables.draft._id && table.type !== "external"
|
||||
)
|
||||
$: checkConstraints(field)
|
||||
$: required = !!field?.constraints?.presence || primaryDisplay
|
||||
$: uneditable =
|
||||
$tables.selected?._id === TableNames.USERS &&
|
||||
|
@ -65,13 +73,8 @@
|
|||
$: invalid =
|
||||
!field.name ||
|
||||
(field.type === LINK_TYPE && !field.tableId) ||
|
||||
Object.keys($tables.draft?.schema ?? {}).some(
|
||||
key => key !== originalName && key === field.name
|
||||
) ||
|
||||
columnNameInvalid
|
||||
$: columnNameInvalid = PROHIBITED_COLUMN_NAMES.some(
|
||||
name => field.name === name
|
||||
)
|
||||
Object.keys(errors).length !== 0
|
||||
$: errors = checkErrors(field)
|
||||
|
||||
// used to select what different options can be displayed for column type
|
||||
$: canBeSearched =
|
||||
|
@ -83,18 +86,36 @@
|
|||
$: canBeRequired =
|
||||
field.type !== LINK_TYPE && !uneditable && field.type !== AUTO_TYPE
|
||||
$: relationshipOptions = getRelationshipOptions(field)
|
||||
$: external = table.type === "external"
|
||||
// in the case of internal tables the sourceId will just be undefined
|
||||
$: tableOptions = $tables.list.filter(
|
||||
opt =>
|
||||
opt._id !== $tables.draft._id &&
|
||||
opt.type === table.type &&
|
||||
table.sourceId === opt.sourceId
|
||||
)
|
||||
$: typeEnabled =
|
||||
!originalName ||
|
||||
(originalName && SWITCHABLE_TYPES.indexOf(field.type) !== -1)
|
||||
|
||||
async function saveColumn() {
|
||||
if (field.type === AUTO_TYPE) {
|
||||
field = buildAutoColumn($tables.draft.name, field.name, field.subtype)
|
||||
}
|
||||
await tables.saveField({
|
||||
originalName,
|
||||
field,
|
||||
primaryDisplay,
|
||||
indexes,
|
||||
})
|
||||
dispatch("updatecolumns")
|
||||
if (field.type !== LINK_TYPE) {
|
||||
delete field.fieldName
|
||||
}
|
||||
try {
|
||||
await tables.saveField({
|
||||
originalName,
|
||||
field,
|
||||
primaryDisplay,
|
||||
indexes,
|
||||
})
|
||||
dispatch("updatecolumns")
|
||||
} catch (err) {
|
||||
notifications.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
function deleteColumn() {
|
||||
|
@ -162,6 +183,7 @@
|
|||
|
||||
function hideDeleteDialog() {
|
||||
confirmDeleteDialog.hide()
|
||||
deleteColName = ""
|
||||
deletion = false
|
||||
}
|
||||
|
||||
|
@ -169,7 +191,7 @@
|
|||
if (!field || !field.tableId) {
|
||||
return null
|
||||
}
|
||||
const linkTable = tableOptions.find(table => table._id === field.tableId)
|
||||
const linkTable = tableOptions?.find(table => table._id === field.tableId)
|
||||
if (!linkTable) {
|
||||
return null
|
||||
}
|
||||
|
@ -193,6 +215,80 @@
|
|||
},
|
||||
]
|
||||
}
|
||||
|
||||
function getAllowedTypes() {
|
||||
if (originalName && ALLOWABLE_STRING_TYPES.indexOf(field.type) !== -1) {
|
||||
return ALLOWABLE_STRING_OPTIONS
|
||||
} else if (
|
||||
originalName &&
|
||||
ALLOWABLE_NUMBER_TYPES.indexOf(field.type) !== -1
|
||||
) {
|
||||
return ALLOWABLE_NUMBER_OPTIONS
|
||||
} else if (!external) {
|
||||
return [
|
||||
...Object.values(fieldDefinitions),
|
||||
{ name: "Auto Column", type: AUTO_TYPE },
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
FIELDS.STRING,
|
||||
FIELDS.LONGFORM,
|
||||
FIELDS.OPTIONS,
|
||||
FIELDS.DATETIME,
|
||||
FIELDS.NUMBER,
|
||||
FIELDS.BOOLEAN,
|
||||
FIELDS.ARRAY,
|
||||
FIELDS.FORMULA,
|
||||
FIELDS.LINK,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
function checkConstraints(fieldToCheck) {
|
||||
// most types need this, just make sure its always present
|
||||
if (fieldToCheck && !fieldToCheck.constraints) {
|
||||
fieldToCheck.constraints = {}
|
||||
}
|
||||
// some string types may have been built by server, may not always have constraints
|
||||
if (fieldToCheck.type === STRING_TYPE && !fieldToCheck.constraints.length) {
|
||||
fieldToCheck.constraints.length = {}
|
||||
}
|
||||
// some number types made server-side will be missing constraints
|
||||
if (
|
||||
fieldToCheck.type === NUMBER_TYPE &&
|
||||
!fieldToCheck.constraints.numericality
|
||||
) {
|
||||
fieldToCheck.constraints.numericality = {}
|
||||
}
|
||||
if (fieldToCheck.type === DATE_TYPE && !fieldToCheck.constraints.datetime) {
|
||||
fieldToCheck.constraints.datetime = {}
|
||||
}
|
||||
}
|
||||
|
||||
function checkErrors(fieldInfo) {
|
||||
function inUse(tbl, column, ogName = null) {
|
||||
return Object.keys(tbl?.schema || {}).some(
|
||||
key => key !== ogName && key === column
|
||||
)
|
||||
}
|
||||
const newError = {}
|
||||
if (PROHIBITED_COLUMN_NAMES.some(name => fieldInfo.name === name)) {
|
||||
newError.name = `${PROHIBITED_COLUMN_NAMES.join(
|
||||
", "
|
||||
)} are not allowed as column names`
|
||||
} else if (inUse($tables.draft, fieldInfo.name, originalName)) {
|
||||
newError.name = `Column name already in use.`
|
||||
}
|
||||
if (fieldInfo.fieldName && fieldInfo.tableId) {
|
||||
const relatedTable = $tables.list.find(
|
||||
tbl => tbl._id === fieldInfo.tableId
|
||||
)
|
||||
if (inUse(relatedTable, fieldInfo.fieldName)) {
|
||||
newError.relatedName = `Column name already in use in table ${relatedTable.name}`
|
||||
}
|
||||
}
|
||||
return newError
|
||||
}
|
||||
</script>
|
||||
|
||||
<ModalContent
|
||||
|
@ -205,20 +301,15 @@
|
|||
label="Name"
|
||||
bind:value={field.name}
|
||||
disabled={uneditable || (linkEditDisabled && field.type === LINK_TYPE)}
|
||||
error={columnNameInvalid
|
||||
? `${PROHIBITED_COLUMN_NAMES.join(", ")} are not allowed as column names`
|
||||
: ""}
|
||||
error={errors?.name}
|
||||
/>
|
||||
|
||||
<Select
|
||||
disabled={originalName}
|
||||
disabled={!typeEnabled}
|
||||
label="Type"
|
||||
bind:value={field.type}
|
||||
on:change={handleTypeChange}
|
||||
options={[
|
||||
...Object.values(fieldDefinitions),
|
||||
{ name: "Auto Column", type: AUTO_TYPE },
|
||||
]}
|
||||
options={getAllowedTypes()}
|
||||
getOptionLabel={field => field.name}
|
||||
getOptionValue={field => field.type}
|
||||
/>
|
||||
|
@ -245,7 +336,7 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
{#if canBeSearched}
|
||||
{#if canBeSearched && !external}
|
||||
<div>
|
||||
<Label grey small>Search Indexes</Label>
|
||||
<Toggle
|
||||
|
@ -319,6 +410,7 @@
|
|||
disabled={linkEditDisabled}
|
||||
label={`Column name in other table`}
|
||||
bind:value={field.fieldName}
|
||||
error={errors.relatedName}
|
||||
/>
|
||||
{:else if field.type === FORMULA_TYPE}
|
||||
<ModalBindableInput
|
||||
|
@ -327,7 +419,7 @@
|
|||
value={field.formula}
|
||||
on:change={e => (field.formula = e.detail)}
|
||||
bindings={getBindings({ table })}
|
||||
serverSide="true"
|
||||
allowJS
|
||||
/>
|
||||
{:else if field.type === AUTO_TYPE}
|
||||
<Select
|
||||
|
@ -348,9 +440,20 @@
|
|||
</ModalContent>
|
||||
<ConfirmDialog
|
||||
bind:this={confirmDeleteDialog}
|
||||
body={`Are you sure you wish to delete this column? Your data will be deleted and this action cannot be undone.`}
|
||||
okText="Delete Column"
|
||||
onOk={deleteColumn}
|
||||
onCancel={hideDeleteDialog}
|
||||
title="Confirm Deletion"
|
||||
/>
|
||||
disabled={deleteColName !== field.name}
|
||||
>
|
||||
<p>
|
||||
Are you sure you wish to delete the column <b>{field.name}?</b>
|
||||
Your data will be deleted and this action cannot be undone - enter the column
|
||||
name to confirm.
|
||||
</p>
|
||||
<Input
|
||||
dataCy="delete-column-confirm"
|
||||
bind:value={deleteColName}
|
||||
placeholder={field.name}
|
||||
/>
|
||||
</ConfirmDialog>
|
||||
|
|
|
@ -69,6 +69,7 @@
|
|||
({ _id }) => _id === $views.selected?.tableId
|
||||
)
|
||||
$: fields = viewTable && Object.keys(viewTable.schema)
|
||||
$: schema = viewTable && viewTable.schema ? viewTable.schema : {}
|
||||
|
||||
function saveView() {
|
||||
views.save(view)
|
||||
|
@ -90,29 +91,29 @@
|
|||
|
||||
function isMultipleChoice(field) {
|
||||
return (
|
||||
viewTable.schema[field]?.constraints?.inclusion?.length ||
|
||||
viewTable.schema[field]?.type === "boolean"
|
||||
schema[field]?.constraints?.inclusion?.length ||
|
||||
schema[field]?.type === "boolean"
|
||||
)
|
||||
}
|
||||
|
||||
function fieldOptions(field) {
|
||||
return viewTable.schema[field]?.type === "options"
|
||||
? viewTable.schema[field]?.constraints.inclusion
|
||||
return schema[field]?.type === "options"
|
||||
? schema[field]?.constraints.inclusion
|
||||
: [true, false]
|
||||
}
|
||||
|
||||
function isDate(field) {
|
||||
return viewTable.schema[field]?.type === "datetime"
|
||||
return schema[field]?.type === "datetime"
|
||||
}
|
||||
|
||||
function isNumber(field) {
|
||||
return viewTable.schema[field]?.type === "number"
|
||||
return schema[field]?.type === "number"
|
||||
}
|
||||
|
||||
const fieldChanged = filter => ev => {
|
||||
// Reset if type changed
|
||||
const oldType = viewTable.schema[filter.key]?.type
|
||||
const newType = viewTable.schema[ev.detail]?.type
|
||||
const oldType = schema[filter.key]?.type
|
||||
const newType = schema[ev.detail]?.type
|
||||
if (filter.key && ev.detail && oldType !== newType) {
|
||||
filter.value = ""
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<script>
|
||||
import { ModalContent, Label, notifications, Body } from "@budibase/bbui"
|
||||
import TableDataImport from "../../TableNavigator/TableDataImport.svelte"
|
||||
import api from "builderStore/api"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let tableId
|
||||
let dataImport
|
||||
|
||||
$: valid = dataImport?.csvString != null && dataImport?.valid
|
||||
|
||||
async function importData() {
|
||||
const response = await api.post(`/api/tables/${tableId}/import`, {
|
||||
dataImport,
|
||||
})
|
||||
if (response.status !== 200) {
|
||||
const error = await response.text()
|
||||
notifications.error(`Unable to import data - ${error}`)
|
||||
} else {
|
||||
notifications.success("Rows successfully imported.")
|
||||
}
|
||||
dispatch("updaterows")
|
||||
}
|
||||
</script>
|
||||
|
||||
<ModalContent
|
||||
title="Import Data"
|
||||
confirmText="Import"
|
||||
onConfirm={importData}
|
||||
disabled={!valid}
|
||||
>
|
||||
<Body
|
||||
>Import rows to an existing table from a CSV. Only columns from the CSV
|
||||
which exist in the table will be imported.</Body
|
||||
>
|
||||
<Label grey extraSmall>CSV to import</Label>
|
||||
<TableDataImport bind:dataImport bind:existingTableId={tableId} />
|
||||
</ModalContent>
|
||||
|
||||
<style>
|
||||
</style>
|
|
@ -23,8 +23,6 @@
|
|||
// Show updated permissions in UI: REMOVE
|
||||
permissions = await permissionsStore.forResource(resourceId)
|
||||
notifications.success("Updated permissions.")
|
||||
// TODO: update permissions
|
||||
// permissions[]
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<script>
|
||||
export let width = "18"
|
||||
export let height = "18"
|
||||
|
||||
import OracleLogo from "assets/oracle.png"
|
||||
</script>
|
||||
|
||||
<div class>
|
||||
<img {height} {width} src={OracleLogo} alt="oracle logo" />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
img {
|
||||
padding-top: 1px;
|
||||
}
|
||||
</style>
|
|
@ -10,6 +10,7 @@ import MySQL from "./MySQL.svelte"
|
|||
import ArangoDB from "./ArangoDB.svelte"
|
||||
import Rest from "./Rest.svelte"
|
||||
import Budibase from "./Budibase.svelte"
|
||||
import Oracle from "./Oracle.svelte"
|
||||
|
||||
export default {
|
||||
BUDIBASE: Budibase,
|
||||
|
@ -24,4 +25,5 @@ export default {
|
|||
MYSQL: MySQL,
|
||||
ARANGODB: ArangoDB,
|
||||
REST: Rest,
|
||||
ORACLE: Oracle,
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { goto } from "@roxi/routify"
|
||||
import { datasources } from "stores/backend"
|
||||
import { datasources, tables } from "stores/backend"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import { ActionMenu, MenuItem, Icon } from "@budibase/bbui"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
|
@ -13,10 +13,16 @@
|
|||
|
||||
async function deleteDatasource() {
|
||||
const wasSelectedSource = $datasources.selected
|
||||
const wasSelectedTable = $tables.selected
|
||||
await datasources.delete(datasource)
|
||||
notifications.success("Datasource deleted")
|
||||
// navigate to first index page if the source you are deleting is selected
|
||||
if (wasSelectedSource === datasource._id) {
|
||||
const entities = Object.values(datasource.entities)
|
||||
if (
|
||||
wasSelectedSource === datasource._id ||
|
||||
(entities &&
|
||||
entities.find(entity => entity._id === wasSelectedTable?._id))
|
||||
) {
|
||||
$goto("./datasource")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,19 @@
|
|||
export let fromRelationship = {}
|
||||
export let toRelationship = {}
|
||||
export let close
|
||||
export let selectedFromTable
|
||||
|
||||
let originalFromName = fromRelationship.name,
|
||||
originalToName = toRelationship.name
|
||||
|
||||
if (fromRelationship && !fromRelationship.relationshipType) {
|
||||
fromRelationship.relationshipType = RelationshipTypes.MANY_TO_ONE
|
||||
}
|
||||
|
||||
if (toRelationship && selectedFromTable) {
|
||||
toRelationship.tableId = selectedFromTable._id
|
||||
}
|
||||
|
||||
function inSchema(table, prop, ogName) {
|
||||
if (!table || !prop || prop === ogName) {
|
||||
return false
|
||||
|
@ -62,6 +71,9 @@
|
|||
if ($touched.toCol && !toRelate.name) {
|
||||
errors.toCol = colNotSet
|
||||
}
|
||||
if ($touched.primary && !fromPrimary) {
|
||||
errors.primary = "Please pick the primary key"
|
||||
}
|
||||
// currently don't support relationships back onto the table itself, needs to relate out
|
||||
const tableError = "From/to/through tables must be different"
|
||||
if (fromTable && (fromTable === toTable || fromTable === throughTable)) {
|
||||
|
@ -86,6 +98,16 @@
|
|||
return errors
|
||||
}
|
||||
|
||||
let fromPrimary
|
||||
$: {
|
||||
if (!fromPrimary && fromTable) {
|
||||
fromPrimary = fromTable.primary[0]
|
||||
}
|
||||
}
|
||||
$: isManyToMany =
|
||||
fromRelationship?.relationshipType === RelationshipTypes.MANY_TO_MANY
|
||||
$: isManyToOne =
|
||||
fromRelationship?.relationshipType === RelationshipTypes.MANY_TO_ONE
|
||||
$: tableOptions = plusTables.map(table => ({
|
||||
label: table.name,
|
||||
value: table._id,
|
||||
|
@ -114,6 +136,7 @@
|
|||
},
|
||||
]
|
||||
$: updateRelationshipType(fromRelationship?.relationshipType)
|
||||
$: tableChanged(fromTable, toTable)
|
||||
|
||||
function updateRelationshipType(fromType) {
|
||||
if (fromType === RelationshipTypes.MANY_TO_MANY) {
|
||||
|
@ -169,13 +192,13 @@
|
|||
// foreignKey is what is linking out of the current table.
|
||||
relateFrom = {
|
||||
...relateFrom,
|
||||
foreignKey: fromTable.primary[0],
|
||||
foreignKey: fromPrimary,
|
||||
}
|
||||
relateTo = {
|
||||
...relateTo,
|
||||
relationshipType: RelationshipTypes.ONE_TO_MANY,
|
||||
foreignKey: relateFrom.fieldName,
|
||||
fieldName: fromTable.primary[0],
|
||||
fieldName: fromPrimary,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,7 +228,6 @@
|
|||
originalToName = toRelationship.name
|
||||
originalFromName = fromRelationship.name
|
||||
await save()
|
||||
await tables.fetch()
|
||||
}
|
||||
|
||||
async function deleteRelationship() {
|
||||
|
@ -215,10 +237,26 @@
|
|||
await tables.fetch()
|
||||
close()
|
||||
}
|
||||
|
||||
function tableChanged(fromTbl, toTbl) {
|
||||
fromRelationship.name = toTbl?.name || ""
|
||||
errors.fromCol = ""
|
||||
toRelationship.name = fromTbl?.name || ""
|
||||
errors.toCol = ""
|
||||
if (toTbl || fromTbl) {
|
||||
checkForErrors(
|
||||
fromTable,
|
||||
toTable,
|
||||
through,
|
||||
fromRelationship,
|
||||
toRelationship
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<ModalContent
|
||||
title="Create Relationship"
|
||||
title="Define Relationship"
|
||||
confirmText="Save"
|
||||
onConfirm={saveRelationship}
|
||||
disabled={!valid}
|
||||
|
@ -234,10 +272,20 @@
|
|||
<Select
|
||||
label="Select from table"
|
||||
options={tableOptions}
|
||||
disabled={!!selectedFromTable}
|
||||
on:change={() => ($touched.from = true)}
|
||||
bind:error={errors.from}
|
||||
bind:value={toRelationship.tableId}
|
||||
/>
|
||||
{#if isManyToOne && fromTable}
|
||||
<Select
|
||||
label={`Primary Key (${fromTable?.name})`}
|
||||
options={Object.keys(fromTable?.schema)}
|
||||
on:change={() => ($touched.primary = true)}
|
||||
bind:error={errors.primary}
|
||||
bind:value={fromPrimary}
|
||||
/>
|
||||
{/if}
|
||||
<Select
|
||||
label={"Select to table"}
|
||||
options={tableOptions}
|
||||
|
@ -245,7 +293,7 @@
|
|||
bind:error={errors.to}
|
||||
bind:value={fromRelationship.tableId}
|
||||
/>
|
||||
{#if fromRelationship?.relationshipType === RelationshipTypes.MANY_TO_MANY}
|
||||
{#if isManyToMany}
|
||||
<Select
|
||||
label={"Through"}
|
||||
options={tableOptions}
|
||||
|
@ -269,7 +317,7 @@
|
|||
bind:value={fromRelationship.throughFrom}
|
||||
/>
|
||||
{/if}
|
||||
{:else if fromRelationship?.relationshipType && toTable}
|
||||
{:else if isManyToOne && toTable}
|
||||
<Select
|
||||
label={`Foreign Key (${toTable?.name})`}
|
||||
options={Object.keys(toTable?.schema).filter(
|
|
@ -1,6 +1,5 @@
|
|||
<script>
|
||||
import { Select } from "@budibase/bbui"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import { Select, InlineAlert, notifications } from "@budibase/bbui"
|
||||
import { FIELDS } from "constants/backend"
|
||||
import api from "builderStore/api"
|
||||
|
||||
|
@ -12,19 +11,27 @@
|
|||
valid: true,
|
||||
schema: {},
|
||||
}
|
||||
export let existingTableId
|
||||
|
||||
let csvString
|
||||
let primaryDisplay
|
||||
let csvString = undefined
|
||||
let primaryDisplay = undefined
|
||||
let schema = {}
|
||||
let fields = []
|
||||
let hasValidated = false
|
||||
|
||||
$: valid = !schema || fields.every(column => schema[column].success)
|
||||
$: valid =
|
||||
!schema ||
|
||||
(fields.every(column => schema[column].success) &&
|
||||
(!hasValidated || Object.keys(schema).length > 0))
|
||||
$: dataImport = {
|
||||
valid,
|
||||
schema: buildTableSchema(schema),
|
||||
csvString,
|
||||
primaryDisplay,
|
||||
}
|
||||
$: noFieldsError = existingTableId
|
||||
? "No columns in CSV match existing table schema"
|
||||
: "Could not find any columns to import"
|
||||
|
||||
function buildTableSchema(schema) {
|
||||
const tableSchema = {}
|
||||
|
@ -46,6 +53,7 @@
|
|||
const response = await api.post("/api/tables/csv/validate", {
|
||||
csvString,
|
||||
schema: schema || {},
|
||||
tableId: existingTableId,
|
||||
})
|
||||
|
||||
const parseResult = await response.json()
|
||||
|
@ -63,6 +71,7 @@
|
|||
notifications.error("CSV Invalid, please try another CSV file")
|
||||
return []
|
||||
}
|
||||
hasValidated = true
|
||||
}
|
||||
|
||||
async function handleFile(evt) {
|
||||
|
@ -138,6 +147,7 @@
|
|||
placeholder={null}
|
||||
getOptionLabel={option => option.label}
|
||||
getOptionValue={option => option.value}
|
||||
disabled={!!existingTableId}
|
||||
/>
|
||||
<span class="field-status" class:error={!schema[columnName].success}>
|
||||
{schema[columnName].success ? "Success" : "Failure"}
|
||||
|
@ -149,15 +159,22 @@
|
|||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if fields.length}
|
||||
<div class="display-column">
|
||||
<Select
|
||||
label="Display Column"
|
||||
bind:value={primaryDisplay}
|
||||
options={fields}
|
||||
sort
|
||||
{#if !existingTableId}
|
||||
<div class="display-column">
|
||||
<Select
|
||||
label="Display Column"
|
||||
bind:value={primaryDisplay}
|
||||
options={fields}
|
||||
sort
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{:else if hasValidated}
|
||||
<div>
|
||||
<InlineAlert
|
||||
header="Invalid CSV"
|
||||
bind:message={noFieldsError}
|
||||
type="error"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { goto } from "@roxi/routify"
|
||||
import { allScreens, store } from "builderStore"
|
||||
import { tables } from "stores/backend"
|
||||
import { tables, datasources } from "stores/backend"
|
||||
import {
|
||||
ActionMenu,
|
||||
Icon,
|
||||
|
@ -21,8 +21,10 @@
|
|||
let originalName = table.name
|
||||
let templateScreens
|
||||
let willBeDeleted
|
||||
let deleteTableName
|
||||
|
||||
$: external = table?.type === "external"
|
||||
$: allowDeletion = !external || table?.created
|
||||
|
||||
function showDeleteModal() {
|
||||
templateScreens = $allScreens.filter(
|
||||
|
@ -36,15 +38,26 @@
|
|||
|
||||
async function deleteTable() {
|
||||
const wasSelectedTable = $tables.selected
|
||||
await tables.delete(table)
|
||||
store.actions.screens.delete(templateScreens)
|
||||
await tables.fetch()
|
||||
notifications.success("Table deleted")
|
||||
if (wasSelectedTable._id === table._id) {
|
||||
$goto("./table")
|
||||
try {
|
||||
await tables.delete(table)
|
||||
await store.actions.screens.delete(templateScreens)
|
||||
await tables.fetch()
|
||||
if (table.type === "external") {
|
||||
await datasources.fetch()
|
||||
}
|
||||
notifications.success("Table deleted")
|
||||
if (wasSelectedTable && wasSelectedTable._id === table._id) {
|
||||
$goto("./table")
|
||||
}
|
||||
} catch (err) {
|
||||
notifications.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
function hideDeleteDialog() {
|
||||
deleteTableName = ""
|
||||
}
|
||||
|
||||
async function save() {
|
||||
await tables.save(table)
|
||||
notifications.success("Table renamed successfully")
|
||||
|
@ -59,15 +72,17 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<ActionMenu>
|
||||
<div slot="control" class="icon">
|
||||
<Icon s hoverable name="MoreSmallList" />
|
||||
</div>
|
||||
<MenuItem icon="Edit" on:click={editorModal.show}>Edit</MenuItem>
|
||||
{#if !external}
|
||||
{#if allowDeletion}
|
||||
<ActionMenu>
|
||||
<div slot="control" class="icon">
|
||||
<Icon s hoverable name="MoreSmallList" />
|
||||
</div>
|
||||
{#if !external}
|
||||
<MenuItem icon="Edit" on:click={editorModal.show}>Edit</MenuItem>
|
||||
{/if}
|
||||
<MenuItem icon="Delete" on:click={showDeleteModal}>Delete</MenuItem>
|
||||
{/if}
|
||||
</ActionMenu>
|
||||
</ActionMenu>
|
||||
{/if}
|
||||
|
||||
<Modal bind:this={editorModal}>
|
||||
<ModalContent
|
||||
|
@ -89,11 +104,15 @@
|
|||
bind:this={confirmDeleteDialog}
|
||||
okText="Delete Table"
|
||||
onOk={deleteTable}
|
||||
onCancel={hideDeleteDialog}
|
||||
title="Confirm Deletion"
|
||||
disabled={deleteTableName !== table.name}
|
||||
>
|
||||
Are you sure you wish to delete the table
|
||||
<i>{table.name}?</i>
|
||||
The following will also be deleted:
|
||||
<p>
|
||||
Are you sure you wish to delete the table
|
||||
<b>{table.name}?</b>
|
||||
The following will also be deleted:
|
||||
</p>
|
||||
<b>
|
||||
<div class="delete-items">
|
||||
{#each willBeDeleted as item}
|
||||
|
@ -101,7 +120,15 @@
|
|||
{/each}
|
||||
</div>
|
||||
</b>
|
||||
This action cannot be undone.
|
||||
<p>
|
||||
This action cannot be undone - to continue please enter the table name below
|
||||
to confirm.
|
||||
</p>
|
||||
<Input
|
||||
bind:value={deleteTableName}
|
||||
placeholder={table.name}
|
||||
dataCy="delete-table-confirm"
|
||||
/>
|
||||
</ConfirmDialog>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
<script context="module">
|
||||
import { Label } from "@budibase/bbui"
|
||||
|
||||
export const EditorModes = {
|
||||
JS: {
|
||||
name: "javascript",
|
||||
|
@ -21,6 +19,7 @@
|
|||
</script>
|
||||
|
||||
<script>
|
||||
import { Label } from "@budibase/bbui"
|
||||
import CodeMirror from "components/integration/codemirror"
|
||||
import { themeStore } from "builderStore"
|
||||
import { createEventDispatcher, onMount } from "svelte"
|
||||
|
@ -156,4 +155,9 @@
|
|||
div :global(.CodeMirror-focused) {
|
||||
border-color: var(--spectrum-alias-border-color-mouse-focus);
|
||||
}
|
||||
|
||||
/* Ensure hints are always on top */
|
||||
:global(.CodeMirror-hints) {
|
||||
z-index: 999999;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let bindableProperties
|
||||
export let bindings
|
||||
export let value = ""
|
||||
export let valid
|
||||
export let allowJS = false
|
||||
|
@ -36,17 +36,23 @@
|
|||
let hbsValue = initialValueJS ? null : value
|
||||
|
||||
$: usingJS = mode === "JavaScript"
|
||||
$: ({ context } = groupBy("type", bindableProperties))
|
||||
$: searchRgx = new RegExp(search, "ig")
|
||||
$: filteredBindings = context?.filter(context => {
|
||||
return context.readableBinding.match(searchRgx)
|
||||
})
|
||||
$: categories = Object.entries(groupBy("category", bindings))
|
||||
$: filteredCategories = categories
|
||||
.map(([name, categoryBindings]) => ({
|
||||
name,
|
||||
bindings: categoryBindings?.filter(binding => {
|
||||
return binding.readableBinding.match(searchRgx)
|
||||
}),
|
||||
}))
|
||||
.filter(category => category.bindings?.length > 0)
|
||||
$: filteredHelpers = helpers?.filter(helper => {
|
||||
return helper.label.match(searchRgx) || helper.description.match(searchRgx)
|
||||
})
|
||||
$: codeMirrorHints = bindings?.map(x => `$("${x.readableBinding}")`)
|
||||
|
||||
const updateValue = value => {
|
||||
valid = isValid(readableToRuntimeBinding(bindableProperties, value))
|
||||
valid = isValid(readableToRuntimeBinding(bindings, value))
|
||||
if (valid) {
|
||||
dispatch("change", value)
|
||||
}
|
||||
|
@ -91,7 +97,7 @@
|
|||
}
|
||||
|
||||
onMount(() => {
|
||||
valid = isValid(readableToRuntimeBinding(bindableProperties, value))
|
||||
valid = isValid(readableToRuntimeBinding(bindings, value))
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -102,18 +108,29 @@
|
|||
<div class="heading">Search</div>
|
||||
<Search placeholder="Search" bind:value={search} />
|
||||
</section>
|
||||
{#if filteredBindings?.length}
|
||||
<section>
|
||||
<div class="heading">Bindable Values</div>
|
||||
<ul>
|
||||
{#each filteredBindings as binding}
|
||||
<li on:click={() => addBinding(binding)}>
|
||||
{binding.readableBinding}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</section>
|
||||
{/if}
|
||||
{#each filteredCategories as category}
|
||||
{#if category.bindings?.length}
|
||||
<section>
|
||||
<div class="heading">{category.name}</div>
|
||||
<ul>
|
||||
{#each category.bindings as binding}
|
||||
<li on:click={() => addBinding(binding)}>
|
||||
<span class="binding__label">{binding.readableBinding}</span>
|
||||
{#if binding.type}
|
||||
<span class="binding__type">{binding.type}</span>
|
||||
{/if}
|
||||
{#if binding.description}
|
||||
<br />
|
||||
<div class="binding__description">
|
||||
{binding.description || ""}
|
||||
</div>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</section>
|
||||
{/if}
|
||||
{/each}
|
||||
{#if filteredHelpers?.length && !usingJS}
|
||||
<section>
|
||||
<div class="heading">Helpers</div>
|
||||
|
@ -162,7 +179,7 @@
|
|||
height={200}
|
||||
value={decodeJSBinding(jsValue)}
|
||||
on:change={onChangeJSValue}
|
||||
hints={context?.map(x => `$("${x.readableBinding}")`)}
|
||||
hints={codeMirrorHints}
|
||||
/>
|
||||
<Body size="S">
|
||||
JavaScript expressions are executed as functions, so ensure that
|
||||
|
@ -234,6 +251,24 @@
|
|||
color: var(--spectrum-global-color-gray-900) !important;
|
||||
}
|
||||
|
||||
.binding__label {
|
||||
font-weight: 600;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.binding__description {
|
||||
color: var(--spectrum-global-color-gray-700);
|
||||
margin: 0.5rem 0 0 0;
|
||||
white-space: normal;
|
||||
}
|
||||
.binding__type {
|
||||
font-family: monospace;
|
||||
background-color: var(--spectrum-global-color-gray-200);
|
||||
border-radius: var(--border-radius-s);
|
||||
padding: 2px 4px;
|
||||
margin-left: 2px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.helper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<script>
|
||||
import BindingPanel from "./BindingPanel.svelte"
|
||||
|
||||
export let bindings = []
|
||||
export let valid
|
||||
export let value = ""
|
||||
export let allowJS = false
|
||||
|
||||
$: enrichedBindings = enrichBindings(bindings)
|
||||
|
||||
// Ensure bindings have the correct categories
|
||||
const enrichBindings = bindings => {
|
||||
if (!bindings?.length) {
|
||||
return bindings
|
||||
}
|
||||
return bindings?.map(binding => ({
|
||||
...binding,
|
||||
category: "Bindable Values",
|
||||
type: null,
|
||||
}))
|
||||
}
|
||||
</script>
|
||||
|
||||
<BindingPanel
|
||||
bind:valid
|
||||
bindings={enrichedBindings}
|
||||
{value}
|
||||
{allowJS}
|
||||
on:change
|
||||
/>
|
|
@ -4,11 +4,11 @@
|
|||
readableToRuntimeBinding,
|
||||
runtimeToReadableBinding,
|
||||
} from "builderStore/dataBinding"
|
||||
import BindingPanel from "components/common/bindings/BindingPanel.svelte"
|
||||
import ClientBindingPanel from "components/common/bindings/ClientBindingPanel.svelte"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { isJSBinding } from "@budibase/string-templates"
|
||||
|
||||
export let panel = BindingPanel
|
||||
export let panel = ClientBindingPanel
|
||||
export let value = ""
|
||||
export let bindings = []
|
||||
export let title = "Bindings"
|
||||
|
@ -17,6 +17,7 @@
|
|||
export let disabled = false
|
||||
export let options
|
||||
export let allowJS = true
|
||||
export let appendBindingsAsOptions = true
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let bindingDrawer
|
||||
|
@ -24,28 +25,49 @@
|
|||
$: readableValue = runtimeToReadableBinding(bindings, value)
|
||||
$: tempValue = readableValue
|
||||
$: isJS = isJSBinding(value)
|
||||
$: allOptions = buildOptions(options, bindings, appendBindingsAsOptions)
|
||||
|
||||
const handleClose = () => {
|
||||
onChange(tempValue)
|
||||
bindingDrawer.hide()
|
||||
}
|
||||
|
||||
const onChange = value => {
|
||||
const onChange = (value, optionPicked) => {
|
||||
// Add HBS braces if picking binding
|
||||
if (optionPicked && !options?.includes(value)) {
|
||||
value = `{{ ${value} }}`
|
||||
}
|
||||
|
||||
dispatch("change", readableToRuntimeBinding(bindings, value))
|
||||
}
|
||||
|
||||
const buildOptions = (options, bindings, appendBindingsAsOptions) => {
|
||||
if (!appendBindingsAsOptions) {
|
||||
return options
|
||||
}
|
||||
return []
|
||||
.concat(options || [])
|
||||
.concat(bindings?.map(binding => binding.readableBinding) || [])
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="control">
|
||||
<Combobox
|
||||
{label}
|
||||
{disabled}
|
||||
readonly={isJS}
|
||||
value={isJS ? "(JavaScript function)" : readableValue}
|
||||
on:change={event => onChange(event.detail)}
|
||||
on:type={e => onChange(e.detail, false)}
|
||||
on:pick={e => onChange(e.detail, true)}
|
||||
{placeholder}
|
||||
{options}
|
||||
options={allOptions}
|
||||
/>
|
||||
{#if !disabled}
|
||||
<div class="icon" on:click={bindingDrawer.show}>
|
||||
<div
|
||||
class="icon"
|
||||
on:click={bindingDrawer.show}
|
||||
data-cy="text-binding-button"
|
||||
>
|
||||
<Icon size="S" name="FlashOn" />
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -61,7 +83,7 @@
|
|||
value={readableValue}
|
||||
close={handleClose}
|
||||
on:change={event => (tempValue = event.detail)}
|
||||
bindableProperties={bindings}
|
||||
{bindings}
|
||||
{allowJS}
|
||||
/>
|
||||
</Drawer>
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
readableToRuntimeBinding,
|
||||
runtimeToReadableBinding,
|
||||
} from "builderStore/dataBinding"
|
||||
import BindingPanel from "components/common/bindings/BindingPanel.svelte"
|
||||
import ClientBindingPanel from "components/common/bindings/ClientBindingPanel.svelte"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { isJSBinding } from "@budibase/string-templates"
|
||||
|
||||
export let panel = BindingPanel
|
||||
export let panel = ClientBindingPanel
|
||||
export let value = ""
|
||||
export let bindings = []
|
||||
export let title = "Bindings"
|
||||
|
@ -40,6 +40,7 @@
|
|||
<Input
|
||||
{label}
|
||||
{disabled}
|
||||
readonly={isJS}
|
||||
value={isJS ? "(JavaScript function)" : readableValue}
|
||||
on:change={event => onChange(event.detail)}
|
||||
{placeholder}
|
||||
|
@ -63,7 +64,7 @@
|
|||
bind:valid
|
||||
value={readableValue}
|
||||
on:change={event => (tempValue = event.detail)}
|
||||
bindableProperties={bindings}
|
||||
{bindings}
|
||||
{allowJS}
|
||||
/>
|
||||
</Drawer>
|
||||
|
|
|
@ -6,20 +6,23 @@
|
|||
} from "builderStore/dataBinding"
|
||||
import ServerBindingPanel from "components/common/bindings/ServerBindingPanel.svelte"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { isJSBinding } from "@budibase/string-templates"
|
||||
|
||||
export let panel = ServerBindingPanel
|
||||
export let value = ""
|
||||
export let bindings = []
|
||||
export let thin = true
|
||||
export let title = "Bindings"
|
||||
export let placeholder
|
||||
export let label
|
||||
export let allowJS = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let bindingModal
|
||||
let valid = true
|
||||
|
||||
$: readableValue = runtimeToReadableBinding(bindings, value)
|
||||
$: tempValue = readableValue
|
||||
$: isJS = isJSBinding(value)
|
||||
|
||||
const saveBinding = () => {
|
||||
onChange(tempValue)
|
||||
|
@ -34,8 +37,8 @@
|
|||
<div class="control">
|
||||
<Input
|
||||
{label}
|
||||
{thin}
|
||||
value={readableValue}
|
||||
readonly={isJS}
|
||||
value={isJS ? "(JavaScript function)" : readableValue}
|
||||
on:change={event => onChange(event.detail)}
|
||||
{placeholder}
|
||||
/>
|
||||
|
@ -55,7 +58,8 @@
|
|||
value={readableValue}
|
||||
bind:valid
|
||||
on:change={e => (tempValue = e.detail)}
|
||||
bindableProperties={bindings}
|
||||
{bindings}
|
||||
{allowJS}
|
||||
/>
|
||||
</div>
|
||||
</ModalContent>
|
||||
|
|
|
@ -1,209 +1,27 @@
|
|||
<script>
|
||||
import groupBy from "lodash/fp/groupBy"
|
||||
import { Search, TextArea, DrawerContent } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { isValid } from "@budibase/string-templates"
|
||||
import { handlebarsCompletions } from "constants/completions"
|
||||
import { readableToRuntimeBinding } from "builderStore/dataBinding"
|
||||
import { addHBSBinding } from "./utils"
|
||||
import BindingPanel from "./BindingPanel.svelte"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let bindableProperties = []
|
||||
export let valid = true
|
||||
export let bindings = []
|
||||
export let valid
|
||||
export let value = ""
|
||||
export let allowJS = false
|
||||
|
||||
let helpers = handlebarsCompletions()
|
||||
let getCaretPosition
|
||||
let search = ""
|
||||
$: enrichedBindings = enrichBindings(bindings)
|
||||
|
||||
$: categories = Object.entries(groupBy("category", bindableProperties))
|
||||
$: valid = isValid(readableToRuntimeBinding(bindableProperties, value))
|
||||
$: dispatch("change", value)
|
||||
$: searchRgx = new RegExp(search, "ig")
|
||||
$: filteredCategories = categories.map(([categoryName, bindings]) => {
|
||||
const filteredBindings = bindings.filter(binding => {
|
||||
return binding.label.match(searchRgx)
|
||||
})
|
||||
return [categoryName, filteredBindings]
|
||||
})
|
||||
$: filteredHelpers = helpers?.filter(helper => {
|
||||
return helper.label.match(searchRgx) || helper.description.match(searchRgx)
|
||||
})
|
||||
// Ensure bindings have the correct properties
|
||||
const enrichBindings = bindings => {
|
||||
return bindings?.map(binding => ({
|
||||
...binding,
|
||||
readableBinding: binding.label || binding.readableBinding,
|
||||
runtimeBinding: binding.path || binding.runtimeBinding,
|
||||
}))
|
||||
}
|
||||
</script>
|
||||
|
||||
<DrawerContent>
|
||||
<svelte:fragment slot="sidebar">
|
||||
<div class="container">
|
||||
<section>
|
||||
<div class="heading">Search</div>
|
||||
<Search placeholder="Search" bind:value={search} />
|
||||
</section>
|
||||
{#each filteredCategories as [categoryName, bindings]}
|
||||
{#if bindings.length}
|
||||
<section>
|
||||
<div class="heading">{categoryName}</div>
|
||||
<ul>
|
||||
{#each bindings as binding}
|
||||
<li
|
||||
on:click={() => {
|
||||
value = addHBSBinding(value, getCaretPosition(), binding)
|
||||
}}
|
||||
>
|
||||
<span class="binding__label">{binding.label}</span>
|
||||
<span class="binding__type">{binding.type}</span>
|
||||
{#if binding.description}
|
||||
<br />
|
||||
<div class="binding__description">
|
||||
{binding.description || ""}
|
||||
</div>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</section>
|
||||
{/if}
|
||||
{/each}
|
||||
{#if filteredHelpers?.length}
|
||||
<section>
|
||||
<div class="heading">Helpers</div>
|
||||
<ul>
|
||||
{#each filteredHelpers as helper}
|
||||
<li
|
||||
on:click={() => {
|
||||
value = addHBSBinding(value, getCaretPosition(), helper.text)
|
||||
}}
|
||||
>
|
||||
<div class="helper">
|
||||
<div class="helper__name">{helper.displayText}</div>
|
||||
<div class="helper__description">
|
||||
{@html helper.description}
|
||||
</div>
|
||||
<pre class="helper__example">{helper.example || ''}</pre>
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</section>
|
||||
{/if}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<div class="main">
|
||||
<TextArea
|
||||
bind:getCaretPosition
|
||||
bind:value
|
||||
placeholder="Add text, or click the objects on the left to add them to the textbox."
|
||||
/>
|
||||
{#if !valid}
|
||||
<p class="syntax-error">
|
||||
Current Handlebars syntax is invalid, please check the guide
|
||||
<a href="https://handlebarsjs.com/guide/">here</a>
|
||||
for more details.
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</DrawerContent>
|
||||
|
||||
<style>
|
||||
.main :global(textarea) {
|
||||
min-height: 150px !important;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: calc(-1 * var(--spacing-xl));
|
||||
}
|
||||
.heading {
|
||||
font-size: var(--font-size-s);
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
color: var(--spectrum-global-color-gray-600);
|
||||
padding: var(--spacing-xl) 0 var(--spacing-m) 0;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 0 var(--spacing-xl) var(--spacing-xl) var(--spacing-xl);
|
||||
}
|
||||
section:not(:first-child) {
|
||||
border-top: var(--border-light);
|
||||
}
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
font-size: var(--font-size-s);
|
||||
padding: var(--spacing-m);
|
||||
border-radius: 4px;
|
||||
border: var(--border-light);
|
||||
transition: background-color 130ms ease-in-out, color 130ms ease-in-out,
|
||||
border-color 130ms ease-in-out;
|
||||
}
|
||||
li:not(:last-of-type) {
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
li :global(*) {
|
||||
transition: color 130ms ease-in-out;
|
||||
}
|
||||
li:hover {
|
||||
color: var(--spectrum-global-color-gray-900);
|
||||
background-color: var(--spectrum-global-color-gray-50);
|
||||
border-color: var(--spectrum-global-color-gray-500);
|
||||
cursor: pointer;
|
||||
}
|
||||
li:hover :global(*) {
|
||||
color: var(--spectrum-global-color-gray-900) !important;
|
||||
}
|
||||
|
||||
.helper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
.helper__name {
|
||||
font-weight: bold;
|
||||
}
|
||||
.helper__description,
|
||||
.helper__description :global(*) {
|
||||
color: var(--spectrum-global-color-gray-700);
|
||||
}
|
||||
.helper__example {
|
||||
white-space: normal;
|
||||
margin: 0.5rem 0 0 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
.helper__description :global(p) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.syntax-error {
|
||||
padding-top: var(--spacing-m);
|
||||
color: var(--red);
|
||||
font-size: 12px;
|
||||
}
|
||||
.syntax-error a {
|
||||
color: var(--red);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.binding__label {
|
||||
font-weight: 600;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.binding__description {
|
||||
color: var(--spectrum-global-color-gray-700);
|
||||
margin: 0.5rem 0 0 0;
|
||||
white-space: normal;
|
||||
}
|
||||
.binding__type {
|
||||
font-family: monospace;
|
||||
background-color: var(--spectrum-global-color-gray-200);
|
||||
border-radius: var(--border-radius-s);
|
||||
padding: 2px 4px;
|
||||
margin-left: 2px;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
<BindingPanel
|
||||
bind:valid
|
||||
bindings={enrichedBindings}
|
||||
{value}
|
||||
{allowJS}
|
||||
on:change
|
||||
/>
|
||||
|
|
|
@ -32,15 +32,13 @@
|
|||
.component("@budibase/standard-components/screenslot")
|
||||
.instanceName("Content Placeholder")
|
||||
.json()
|
||||
|
||||
|
||||
// Messages that can be sent from the iframe preview to the builder
|
||||
// Budibase events are and initalisation events
|
||||
const MessageTypes = {
|
||||
IFRAME_LOADED: "iframe-loaded",
|
||||
READY: "ready",
|
||||
ERROR: "error",
|
||||
BUDIBASE: "type",
|
||||
KEYDOWN: "keydown"
|
||||
}
|
||||
|
||||
// Construct iframe template
|
||||
|
@ -69,7 +67,7 @@
|
|||
theme: $store.theme,
|
||||
customTheme: $store.customTheme,
|
||||
previewDevice: $store.previewDevice,
|
||||
messagePassing: $store.clientFeatures.messagePassing
|
||||
messagePassing: $store.clientFeatures.messagePassing,
|
||||
}
|
||||
|
||||
// Saving pages and screens to the DB causes them to have _revs.
|
||||
|
@ -111,7 +109,6 @@
|
|||
loading = false
|
||||
error = event.error || "An unknown error occurred"
|
||||
},
|
||||
[MessageTypes.KEYDOWN]: handleKeydownEvent
|
||||
}
|
||||
|
||||
const messageHandler = handlers[message.data.type] || handleBudibaseEvent
|
||||
|
@ -122,16 +119,25 @@
|
|||
window.addEventListener("message", receiveMessage)
|
||||
if (!$store.clientFeatures.messagePassing) {
|
||||
// Legacy - remove in later versions of BB
|
||||
iframe.contentWindow.addEventListener("ready", () => {
|
||||
receiveMessage({ data: { type: MessageTypes.READY }})
|
||||
}, { once: true })
|
||||
iframe.contentWindow.addEventListener("error", event => {
|
||||
receiveMessage({ data: { type: MessageTypes.ERROR, error: event.detail }})
|
||||
}, { once: true })
|
||||
iframe.contentWindow.addEventListener(
|
||||
"ready",
|
||||
() => {
|
||||
receiveMessage({ data: { type: MessageTypes.READY } })
|
||||
},
|
||||
{ once: true }
|
||||
)
|
||||
iframe.contentWindow.addEventListener(
|
||||
"error",
|
||||
event => {
|
||||
receiveMessage({
|
||||
data: { type: MessageTypes.ERROR, error: event.detail },
|
||||
})
|
||||
},
|
||||
{ once: true }
|
||||
)
|
||||
// Add listener for events sent by client library in preview
|
||||
iframe.contentWindow.addEventListener("bb-event", handleBudibaseEvent)
|
||||
iframe.contentWindow.addEventListener("keydown", handleKeydownEvent)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Remove all iframe event listeners on component destroy
|
||||
|
@ -140,14 +146,20 @@
|
|||
window.removeEventListener("message", receiveMessage)
|
||||
if (!$store.clientFeatures.messagePassing) {
|
||||
// Legacy - remove in later versions of BB
|
||||
iframe.contentWindow.removeEventListener("bb-event", handleBudibaseEvent)
|
||||
iframe.contentWindow.removeEventListener("keydown", handleKeydownEvent)
|
||||
iframe.contentWindow.removeEventListener(
|
||||
"bb-event",
|
||||
handleBudibaseEvent
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const handleBudibaseEvent = event => {
|
||||
const { type, data } = event.data || event.detail
|
||||
if (!type) {
|
||||
return
|
||||
}
|
||||
|
||||
if (type === "select-component" && data.id) {
|
||||
store.actions.components.select({ _id: data.id })
|
||||
} else if (type === "update-prop") {
|
||||
|
@ -183,19 +195,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
const handleKeydownEvent = event => {
|
||||
const { key } = event.data || event
|
||||
if (
|
||||
(key === "Delete" || key === "Backspace") &&
|
||||
selectedComponentId &&
|
||||
["input", "textarea"].indexOf(
|
||||
iframe.contentWindow.document.activeElement?.tagName.toLowerCase()
|
||||
) === -1
|
||||
) {
|
||||
confirmDeleteComponent(selectedComponentId)
|
||||
}
|
||||
}
|
||||
|
||||
const confirmDeleteComponent = componentId => {
|
||||
idToDelete = componentId
|
||||
confirmDeleteDialog.show()
|
||||
|
|
|
@ -1,10 +1,31 @@
|
|||
[
|
||||
"section",
|
||||
"container",
|
||||
"dataprovider",
|
||||
"table",
|
||||
"repeater",
|
||||
"button",
|
||||
{
|
||||
"name": "Blocks",
|
||||
"icon": "Article",
|
||||
"children": [
|
||||
"tableblock",
|
||||
"cardsblock",
|
||||
"repeaterblock"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Layout",
|
||||
"icon": "ClassicGridView",
|
||||
"children": [
|
||||
"container",
|
||||
"section"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Data",
|
||||
"icon": "Data",
|
||||
"children": [
|
||||
"dataprovider",
|
||||
"repeater",
|
||||
"table",
|
||||
"dynamicfilter"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Form",
|
||||
"icon": "Form",
|
||||
|
@ -51,6 +72,7 @@
|
|||
"children": [
|
||||
"heading",
|
||||
"text",
|
||||
"button",
|
||||
"divider",
|
||||
"image",
|
||||
"backgroundimage",
|
||||
|
|
|
@ -84,7 +84,6 @@ export default `
|
|||
if (window.loadBudibase) {
|
||||
window.loadBudibase()
|
||||
document.documentElement.classList.add("loaded")
|
||||
window.parent.postMessage({ type: "iframe-loaded" })
|
||||
} else {
|
||||
throw "The client library couldn't be loaded"
|
||||
}
|
||||
|
@ -94,10 +93,6 @@ export default `
|
|||
}
|
||||
|
||||
window.addEventListener("message", receiveMessage)
|
||||
window.addEventListener("keydown", evt => {
|
||||
window.parent.postMessage({ type: "keydown", key: event.key })
|
||||
})
|
||||
|
||||
window.parent.postMessage({ type: "ready" })
|
||||
</script>
|
||||
</head>
|
||||
|
|
|
@ -13,6 +13,12 @@
|
|||
$: noChildrenAllowed = !component || !definition?.hasChildren
|
||||
$: noPaste = !$store.componentToPaste
|
||||
|
||||
// "editable" has been repurposed for inline text editing.
|
||||
// It remains here for legacy compatibility.
|
||||
// Future components should define "static": true for indicate they should
|
||||
// not show a context menu.
|
||||
$: showMenu = definition?.editable !== false && definition?.static !== true
|
||||
|
||||
const moveUpComponent = () => {
|
||||
const asset = get(currentAsset)
|
||||
const parent = findComponentParent(asset.props, component._id)
|
||||
|
@ -47,7 +53,7 @@
|
|||
|
||||
const duplicateComponent = () => {
|
||||
storeComponentForCopy(false)
|
||||
pasteComponent("below")
|
||||
pasteComponent("below", true)
|
||||
}
|
||||
|
||||
const deleteComponent = async () => {
|
||||
|
@ -63,13 +69,13 @@
|
|||
store.actions.components.copy(component, cut)
|
||||
}
|
||||
|
||||
const pasteComponent = mode => {
|
||||
const pasteComponent = (mode, preserveBindings = false) => {
|
||||
// lives in store - also used by drag drop
|
||||
store.actions.components.paste(component, mode)
|
||||
store.actions.components.paste(component, mode, preserveBindings)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if definition?.editable !== false}
|
||||
{#if showMenu}
|
||||
<ActionMenu>
|
||||
<div slot="control" class="icon">
|
||||
<Icon size="S" hoverable name="MoreSmallList" />
|
||||
|
|
|
@ -10,10 +10,11 @@
|
|||
import { roles } from "stores/backend"
|
||||
import ComponentNavigationTree from "components/design/NavigationPanel/ComponentNavigationTree/index.svelte"
|
||||
import Layout from "components/design/NavigationPanel/Layout.svelte"
|
||||
import NewScreenModal from "components/design/NavigationPanel/NewScreenModal.svelte"
|
||||
import NewLayoutModal from "components/design/NavigationPanel/NewLayoutModal.svelte"
|
||||
import { Icon, Modal, Select, Search, Tabs, Tab } from "@budibase/bbui"
|
||||
|
||||
export let showModal
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
title: "Screens",
|
||||
|
@ -24,8 +25,7 @@
|
|||
key: "layout",
|
||||
},
|
||||
]
|
||||
|
||||
let modal
|
||||
let newLayoutModal
|
||||
$: selected = tabs.find(t => t.key === $params.assetType)?.title || "Screens"
|
||||
|
||||
const navigate = ({ detail }) => {
|
||||
|
@ -85,9 +85,6 @@
|
|||
<div class="nav-items-container">
|
||||
<ComponentNavigationTree />
|
||||
</div>
|
||||
<Modal bind:this={modal}>
|
||||
<NewScreenModal />
|
||||
</Modal>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab title="Layouts">
|
||||
|
@ -95,14 +92,18 @@
|
|||
{#each $store.layouts as layout, idx (layout._id)}
|
||||
<Layout {layout} border={idx > 0} />
|
||||
{/each}
|
||||
<Modal bind:this={modal}>
|
||||
<Modal bind:this={newLayoutModal}>
|
||||
<NewLayoutModal />
|
||||
</Modal>
|
||||
</div>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<div class="add-button">
|
||||
<Icon hoverable name="AddCircle" on:click={modal.show} />
|
||||
<Icon
|
||||
hoverable
|
||||
name="AddCircle"
|
||||
on:click={selected === "Layouts" ? newLayoutModal.show() : showModal()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
<script>
|
||||
import { ModalContent, Body, Detail } from "@budibase/bbui"
|
||||
|
||||
export let selectedScreens
|
||||
export let chooseModal
|
||||
export let save
|
||||
let selectedNav
|
||||
let createdScreens = []
|
||||
$: blankSelected = selectedScreens.length === 1
|
||||
</script>
|
||||
|
||||
<ModalContent
|
||||
title="Select navigation"
|
||||
cancelText="Back"
|
||||
onCancel={() => (blankSelected ? chooseModal(1) : chooseModal(0))}
|
||||
size="M"
|
||||
onConfirm={() => {
|
||||
save(createdScreens)
|
||||
}}
|
||||
disabled={!selectedNav}
|
||||
>
|
||||
<Body size="S"
|
||||
>Please select your preferred layout for the new application:</Body
|
||||
>
|
||||
|
||||
<div class="wrapper">
|
||||
<div
|
||||
data-cy="left-nav"
|
||||
on:click={() => (selectedNav = "Left")}
|
||||
class:unselected={selectedNav && selectedNav !== "Left"}
|
||||
>
|
||||
<div class="box">
|
||||
<div class="side-nav" />
|
||||
</div>
|
||||
<div><Detail>Side Nav</Detail></div>
|
||||
</div>
|
||||
<div
|
||||
on:click={() => (selectedNav = "Top")}
|
||||
class:unselected={selectedNav && selectedNav !== "Top"}
|
||||
>
|
||||
<div class="box">
|
||||
<div class="top-nav" />
|
||||
</div>
|
||||
<div><Detail>Top Nav</Detail></div>
|
||||
</div>
|
||||
<div
|
||||
on:click={() => (selectedNav = "None")}
|
||||
class:unselected={selectedNav && selectedNav !== "None"}
|
||||
>
|
||||
<div class="box" />
|
||||
<div><Detail>No Nav</Detail></div>
|
||||
</div>
|
||||
</div>
|
||||
</ModalContent>
|
||||
|
||||
<style>
|
||||
.side-nav {
|
||||
float: left;
|
||||
background: #d3d3d3 0% 0% no-repeat padding-box;
|
||||
border-radius: 2px 0px 0px 2px;
|
||||
height: 100%;
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
.top-nav {
|
||||
background: #d3d3d3 0% 0% no-repeat padding-box;
|
||||
vertical-align: top;
|
||||
width: 100%;
|
||||
height: 15%;
|
||||
}
|
||||
.box {
|
||||
display: inline-block;
|
||||
background: #eaeaea 0% 0% no-repeat padding-box;
|
||||
border: 1px solid #d3d3d3;
|
||||
border-radius: 2px;
|
||||
opacity: 1;
|
||||
width: 120px;
|
||||
height: 70px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
padding-top: 4%;
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin-right: 5%;
|
||||
}
|
||||
.unselected {
|
||||
opacity: 0.3;
|
||||
}
|
||||
</style>
|
|
@ -1,117 +1,178 @@
|
|||
<script>
|
||||
import { store, allScreens, selectedAccessRole } from "builderStore"
|
||||
import { store } from "builderStore"
|
||||
import { tables } from "stores/backend"
|
||||
import { roles } from "stores/backend"
|
||||
import { Input, Select, ModalContent, Toggle } from "@budibase/bbui"
|
||||
import {
|
||||
ModalContent,
|
||||
Body,
|
||||
Detail,
|
||||
Layout,
|
||||
Icon,
|
||||
ProgressCircle,
|
||||
} from "@budibase/bbui"
|
||||
import getTemplates from "builderStore/store/screenTemplates"
|
||||
import analytics, { Events } from "analytics"
|
||||
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
||||
import { onDestroy } from "svelte"
|
||||
|
||||
const CONTAINER = "@budibase/standard-components/container"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
let name = ""
|
||||
let routeError
|
||||
let baseComponent = CONTAINER
|
||||
let templateIndex
|
||||
let draftScreen
|
||||
let createLink = true
|
||||
let roleId = $selectedAccessRole || "BASIC"
|
||||
export let chooseModal
|
||||
export let save
|
||||
export let showProgressCircle = false
|
||||
|
||||
$: templates = getTemplates($store, $tables.list)
|
||||
$: route = !route && $allScreens.length === 0 ? "*" : route
|
||||
$: {
|
||||
if (templates && templateIndex === undefined) {
|
||||
templateIndex = 0
|
||||
templateChanged(0)
|
||||
}
|
||||
let selectedScreens = []
|
||||
|
||||
const blankScreen = "createFromScratch"
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function setScreens() {
|
||||
dispatch("save", {
|
||||
screens: selectedScreens,
|
||||
})
|
||||
}
|
||||
|
||||
const templateChanged = newTemplateIndex => {
|
||||
if (newTemplateIndex === undefined) return
|
||||
draftScreen = templates[newTemplateIndex].create()
|
||||
if (draftScreen.props._instanceName) {
|
||||
name = draftScreen.props._instanceName
|
||||
}
|
||||
$: blankSelected = selectedScreens?.length === 1
|
||||
$: autoSelected = selectedScreens?.length > 0 && !blankSelected
|
||||
|
||||
if (draftScreen.props._component) {
|
||||
baseComponent = draftScreen.props._component
|
||||
}
|
||||
let templates = getTemplates($store, $tables.list)
|
||||
|
||||
if (draftScreen.routing) {
|
||||
route = draftScreen.routing.route
|
||||
}
|
||||
}
|
||||
|
||||
const save = async () => {
|
||||
if (!route) {
|
||||
routeError = "URL is required"
|
||||
const confirm = async () => {
|
||||
if (autoSelected) {
|
||||
setScreens()
|
||||
await save()
|
||||
} else {
|
||||
if (routeExists(route, roleId)) {
|
||||
routeError = "This URL is already taken for this access role"
|
||||
} else {
|
||||
routeError = ""
|
||||
}
|
||||
setScreens()
|
||||
chooseModal(1)
|
||||
}
|
||||
|
||||
if (routeError) return false
|
||||
|
||||
draftScreen.props._instanceName = name
|
||||
draftScreen.props._component = baseComponent
|
||||
draftScreen.routing = { route, roleId }
|
||||
|
||||
await store.actions.screens.create(draftScreen)
|
||||
if (createLink) {
|
||||
await store.actions.components.links.save(route, name)
|
||||
}
|
||||
await store.actions.routing.fetch()
|
||||
|
||||
if (templateIndex !== undefined) {
|
||||
const template = templates[templateIndex]
|
||||
analytics.captureEvent(Events.SCREEN.CREATED, {
|
||||
template: template.id || template.name,
|
||||
})
|
||||
}
|
||||
const toggleScreenSelection = table => {
|
||||
if (selectedScreens.find(s => s.table === table.name)) {
|
||||
selectedScreens = selectedScreens.filter(
|
||||
screen => screen.table !== table.name
|
||||
)
|
||||
} else {
|
||||
let partialTemplates = getTemplates($store, $tables.list).filter(
|
||||
template => template.table === table.name
|
||||
)
|
||||
selectedScreens = [...partialTemplates, ...selectedScreens]
|
||||
}
|
||||
}
|
||||
|
||||
const routeExists = (route, roleId) => {
|
||||
return $allScreens.some(
|
||||
screen =>
|
||||
screen.routing.route.toLowerCase() === route.toLowerCase() &&
|
||||
screen.routing.roleId === roleId
|
||||
)
|
||||
}
|
||||
|
||||
const routeChanged = event => {
|
||||
if (!event.detail.startsWith("/")) {
|
||||
route = "/" + event.detail
|
||||
}
|
||||
route = sanitizeUrl(route)
|
||||
}
|
||||
onDestroy(() => {
|
||||
selectedScreens = []
|
||||
})
|
||||
</script>
|
||||
|
||||
<ModalContent title="New Screen" confirmText="Create Screen" onConfirm={save}>
|
||||
<Select
|
||||
label="Choose a Template"
|
||||
bind:value={templateIndex}
|
||||
on:change={ev => templateChanged(ev.detail)}
|
||||
options={templates}
|
||||
placeholder={null}
|
||||
getOptionLabel={x => x.name}
|
||||
getOptionValue={(x, idx) => idx}
|
||||
/>
|
||||
<Input label="Name" bind:value={name} />
|
||||
<Input
|
||||
label="Url"
|
||||
error={routeError}
|
||||
bind:value={route}
|
||||
on:change={routeChanged}
|
||||
/>
|
||||
<Select
|
||||
label="Access"
|
||||
bind:value={roleId}
|
||||
options={$roles}
|
||||
getOptionLabel={x => x.name}
|
||||
getOptionValue={x => x._id}
|
||||
/>
|
||||
<Toggle text="Create link in navigation bar" bind:value={createLink} />
|
||||
</ModalContent>
|
||||
<div>
|
||||
<ModalContent
|
||||
title="Add screens"
|
||||
confirmText="Add Screens"
|
||||
cancelText="Cancel"
|
||||
onConfirm={() => confirm()}
|
||||
disabled={!selectedScreens.length}
|
||||
size="L"
|
||||
>
|
||||
<Body size="XS"
|
||||
>Please select the screens you would like to add to your application.
|
||||
Autogenerated screens come with CRUD functionality.</Body
|
||||
>
|
||||
|
||||
<Layout noPadding gap="S">
|
||||
<Detail size="S">Blank screen</Detail>
|
||||
<div
|
||||
class="item"
|
||||
class:selected={selectedScreens.find(x => x.id.includes(blankScreen))}
|
||||
on:click={() =>
|
||||
toggleScreenSelection(templates.find(t => t.id === blankScreen))}
|
||||
class:disabled={autoSelected}
|
||||
>
|
||||
<div data-cy="blank-screen" class="content">
|
||||
<div class="text">Blank</div>
|
||||
</div>
|
||||
<div
|
||||
style="color: var(--spectrum-global-color-green-600); float: right"
|
||||
>
|
||||
{#if selectedScreens.find(x => x.id === blankScreen)}
|
||||
<div class="checkmark-spacing">
|
||||
<Icon size="S" name="CheckmarkCircleOutline" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if $tables.list.filter(table => table._id !== "ta_users").length > 0}
|
||||
<Detail size="S">Autogenerated Screens</Detail>
|
||||
|
||||
{#each $tables.list.filter(table => table._id !== "ta_users") as table}
|
||||
<div
|
||||
class:disabled={blankSelected}
|
||||
class:selected={selectedScreens.find(x => x.table === table.name)}
|
||||
on:click={() => toggleScreenSelection(table)}
|
||||
class="item"
|
||||
>
|
||||
<div class="content">
|
||||
<div class="text">{table.name}</div>
|
||||
</div>
|
||||
<div
|
||||
style="color: var(--spectrum-global-color-green-600); float: right"
|
||||
>
|
||||
{#if selectedScreens.find(x => x.table === table.name)}
|
||||
<div class="checkmark-spacing">
|
||||
<Icon size="S" name="CheckmarkCircleOutline" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</Layout>
|
||||
<div slot="footer">
|
||||
{#if showProgressCircle}
|
||||
<div class="footer-progress"><ProgressCircle size="S" /></div>
|
||||
{/if}
|
||||
</div>
|
||||
</ModalContent>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.disabled {
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
}
|
||||
.checkmark-spacing {
|
||||
margin-right: var(--spacing-m);
|
||||
}
|
||||
|
||||
.content {
|
||||
letter-spacing: 0px;
|
||||
}
|
||||
|
||||
.footer-progress {
|
||||
margin-top: var(--spacing-s);
|
||||
}
|
||||
|
||||
.text {
|
||||
font-weight: 600;
|
||||
margin-left: var(--spacing-m);
|
||||
font-size: 14px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.item {
|
||||
cursor: pointer;
|
||||
grid-gap: var(--spectrum-alias-grid-margin-xsmall);
|
||||
padding: var(--spectrum-alias-item-padding-s);
|
||||
background: var(--spectrum-alias-background-color-primary);
|
||||
transition: 0.3s all;
|
||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
border-width: 1px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.item:hover,
|
||||
.selected {
|
||||
background: var(--spectrum-alias-background-color-tertiary);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
<script>
|
||||
import { ModalContent, Input, ProgressCircle } from "@budibase/bbui"
|
||||
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
||||
import { selectedAccessRole, allScreens } from "builderStore"
|
||||
import { onDestroy } from "svelte"
|
||||
|
||||
export let screenName
|
||||
export let url
|
||||
export let chooseModal
|
||||
export let save
|
||||
export let showProgressCircle = false
|
||||
|
||||
let routeError
|
||||
let roleId = $selectedAccessRole || "BASIC"
|
||||
|
||||
const routeChanged = event => {
|
||||
if (!event.detail.startsWith("/")) {
|
||||
url = "/" + event.detail
|
||||
}
|
||||
url = sanitizeUrl(url)
|
||||
|
||||
if (routeExists(url, roleId)) {
|
||||
routeError = "This URL is already taken for this access role"
|
||||
} else {
|
||||
routeError = ""
|
||||
}
|
||||
}
|
||||
|
||||
const routeExists = (url, roleId) => {
|
||||
return $allScreens.some(
|
||||
screen =>
|
||||
screen.routing.route.toLowerCase() === url.toLowerCase() &&
|
||||
screen.routing.roleId === roleId
|
||||
)
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
screenName = ""
|
||||
url = ""
|
||||
})
|
||||
</script>
|
||||
|
||||
<ModalContent
|
||||
size="M"
|
||||
title={"Enter details"}
|
||||
confirmText={"Continue"}
|
||||
onCancel={() => chooseModal(0)}
|
||||
onConfirm={() => save()}
|
||||
cancelText={"Back"}
|
||||
disabled={!screenName || !url || routeError}
|
||||
>
|
||||
<Input label="Name" bind:value={screenName} />
|
||||
<Input
|
||||
label="URL"
|
||||
error={routeError}
|
||||
bind:value={url}
|
||||
on:change={routeChanged}
|
||||
/>
|
||||
<div slot="footer">
|
||||
{#if showProgressCircle}
|
||||
<div class="footer-progress"><ProgressCircle size="S" /></div>
|
||||
{/if}
|
||||
</div>
|
||||
</ModalContent>
|
||||
|
||||
<style>
|
||||
.footer-progress {
|
||||
margin-top: var(--spacing-s);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,133 @@
|
|||
<script>
|
||||
import ScreenDetailsModal from "components/design/NavigationPanel/ScreenDetailsModal.svelte"
|
||||
import NewScreenModal from "components/design/NavigationPanel/NewScreenModal.svelte"
|
||||
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
||||
import { Modal } from "@budibase/bbui"
|
||||
import { store, selectedAccessRole, allScreens } from "builderStore"
|
||||
import analytics, { Events } from "analytics"
|
||||
|
||||
let newScreenModal
|
||||
let navigationSelectionModal
|
||||
let screenDetailsModal
|
||||
let screenName = ""
|
||||
let url = ""
|
||||
let selectedScreens = []
|
||||
let roleId = $selectedAccessRole || "BASIC"
|
||||
let showProgressCircle = false
|
||||
let routeError
|
||||
let createdScreens = []
|
||||
|
||||
const createScreens = async () => {
|
||||
for (let screen of selectedScreens) {
|
||||
let test = screen.create()
|
||||
createdScreens.push(test)
|
||||
analytics.captureEvent(Events.SCREEN.CREATED, {
|
||||
template: screen.id || screen.name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const save = async () => {
|
||||
showProgressCircle = true
|
||||
await createScreens()
|
||||
for (let screen of createdScreens) {
|
||||
await saveScreens(screen)
|
||||
}
|
||||
await store.actions.routing.fetch()
|
||||
selectedScreens = []
|
||||
createdScreens = []
|
||||
screenName = ""
|
||||
url = ""
|
||||
showProgressCircle = false
|
||||
}
|
||||
|
||||
const saveScreens = async draftScreen => {
|
||||
let existingScreenCount = $store.screens.filter(
|
||||
s => s.props._instanceName == draftScreen.props._instanceName
|
||||
).length
|
||||
if (existingScreenCount > 0) {
|
||||
let oldUrlArr = draftScreen.routing.route.split("/")
|
||||
oldUrlArr[1] = `${oldUrlArr[1]}-${existingScreenCount + 1}`
|
||||
draftScreen.routing.route = oldUrlArr.join("/")
|
||||
}
|
||||
|
||||
let route = url ? sanitizeUrl(`${url}`) : draftScreen.routing.route
|
||||
if (draftScreen) {
|
||||
if (!route) {
|
||||
routeError = "URL is required"
|
||||
} else {
|
||||
if (routeExists(route, roleId)) {
|
||||
routeError = "This URL is already taken for this access role"
|
||||
} else {
|
||||
routeError = ""
|
||||
}
|
||||
}
|
||||
|
||||
if (routeError) return false
|
||||
|
||||
if (screenName) {
|
||||
draftScreen.props._instanceName = screenName
|
||||
}
|
||||
|
||||
draftScreen.routing.route = route
|
||||
|
||||
await store.actions.screens.create(draftScreen)
|
||||
if (draftScreen.props._instanceName.endsWith("List")) {
|
||||
await store.actions.components.links.save(
|
||||
draftScreen.routing.route,
|
||||
draftScreen.routing.route.split("/")[1]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const routeExists = (route, roleId) => {
|
||||
return $allScreens.some(
|
||||
screen =>
|
||||
screen.routing.route.toLowerCase() === route.toLowerCase() &&
|
||||
screen.routing.roleId === roleId
|
||||
)
|
||||
}
|
||||
|
||||
export const showModal = () => {
|
||||
newScreenModal.show()
|
||||
}
|
||||
|
||||
const setScreens = evt => {
|
||||
selectedScreens = evt.detail.screens
|
||||
}
|
||||
|
||||
const chooseModal = index => {
|
||||
/*
|
||||
0 = newScreenModal
|
||||
1 = screenDetailsModal
|
||||
2 = navigationSelectionModal
|
||||
*/
|
||||
if (index === 0) {
|
||||
newScreenModal.show()
|
||||
} else if (index === 1) {
|
||||
screenDetailsModal.show()
|
||||
} else if (index === 2) {
|
||||
navigationSelectionModal.show()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal bind:this={newScreenModal}>
|
||||
<NewScreenModal
|
||||
on:save={setScreens}
|
||||
{showProgressCircle}
|
||||
{save}
|
||||
{chooseModal}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<Modal bind:this={screenDetailsModal}>
|
||||
<ScreenDetailsModal
|
||||
bind:screenName
|
||||
bind:url
|
||||
{showProgressCircle}
|
||||
{save}
|
||||
{chooseModal}
|
||||
/>
|
||||
</Modal>
|
|
@ -12,6 +12,7 @@
|
|||
export let componentInstance
|
||||
export let assetInstance
|
||||
export let bindings
|
||||
export let componentBindings
|
||||
|
||||
const layoutDefinition = []
|
||||
const screenDefinition = [
|
||||
|
@ -21,10 +22,24 @@
|
|||
{ key: "layoutId", label: "Layout", control: LayoutSelect },
|
||||
]
|
||||
|
||||
$: settings = componentDefinition?.settings ?? []
|
||||
$: sections = getSections(componentDefinition)
|
||||
$: isLayout = assetInstance && assetInstance.favicon
|
||||
$: assetDefinition = isLayout ? layoutDefinition : screenDefinition
|
||||
|
||||
const getSections = definition => {
|
||||
const settings = definition?.settings ?? []
|
||||
const generalSettings = settings.filter(setting => !setting.section)
|
||||
const customSections = settings.filter(setting => setting.section)
|
||||
return [
|
||||
{
|
||||
name: "General",
|
||||
info: componentDefinition?.info,
|
||||
settings: generalSettings,
|
||||
},
|
||||
...(customSections || []),
|
||||
]
|
||||
}
|
||||
|
||||
const updateProp = store.actions.components.updateProp
|
||||
|
||||
const canRenderControl = setting => {
|
||||
|
@ -59,19 +74,18 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<DetailSummary name="General" collapsible={false}>
|
||||
{#if !componentInstance._component.endsWith("/layout")}
|
||||
<PropertyControl
|
||||
bindable={false}
|
||||
control={Input}
|
||||
label="Name"
|
||||
key="_instanceName"
|
||||
value={componentInstance._instanceName}
|
||||
onChange={val => updateProp("_instanceName", val)}
|
||||
/>
|
||||
{/if}
|
||||
{#if settings && settings.length > 0}
|
||||
{#each settings as setting (setting.key)}
|
||||
{#each sections as section, idx (section.name)}
|
||||
<DetailSummary name={section.name} collapsible={false}>
|
||||
{#if idx === 0 && !componentInstance._component.endsWith("/layout")}
|
||||
<PropertyControl
|
||||
control={Input}
|
||||
label="Name"
|
||||
key="_instanceName"
|
||||
value={componentInstance._instanceName}
|
||||
onChange={val => updateProp("_instanceName", val)}
|
||||
/>
|
||||
{/if}
|
||||
{#each section.settings as setting (setting.key)}
|
||||
{#if canRenderControl(setting)}
|
||||
<PropertyControl
|
||||
type={setting.type}
|
||||
|
@ -80,7 +94,7 @@
|
|||
key={setting.key}
|
||||
value={componentInstance[setting.key] ??
|
||||
componentInstance[setting.key]?.defaultValue}
|
||||
{componentInstance}
|
||||
nested={setting.nested}
|
||||
onChange={val => updateProp(setting.key, val)}
|
||||
props={{
|
||||
options: setting.options || [],
|
||||
|
@ -89,20 +103,22 @@
|
|||
max: setting.max || null,
|
||||
}}
|
||||
{bindings}
|
||||
{componentBindings}
|
||||
{componentInstance}
|
||||
{componentDefinition}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
{#if componentDefinition?.component?.endsWith("/fieldgroup")}
|
||||
<ResetFieldsButton {componentInstance} />
|
||||
{/if}
|
||||
{#if componentDefinition?.info}
|
||||
<div class="text">
|
||||
{@html componentDefinition?.info}
|
||||
</div>
|
||||
{/if}
|
||||
</DetailSummary>
|
||||
{#if idx === 0 && componentDefinition?.component?.endsWith("/fieldgroup")}
|
||||
<ResetFieldsButton {componentInstance} />
|
||||
{/if}
|
||||
{#if section?.info}
|
||||
<div class="text">
|
||||
{@html section.info}
|
||||
</div>
|
||||
{/if}
|
||||
</DetailSummary>
|
||||
{/each}
|
||||
|
||||
<style>
|
||||
.text {
|
||||
|
|
|
@ -6,13 +6,20 @@
|
|||
import DesignSection from "./DesignSection.svelte"
|
||||
import CustomStylesSection from "./CustomStylesSection.svelte"
|
||||
import ConditionalUISection from "./ConditionalUISection.svelte"
|
||||
import { getBindableProperties } from "builderStore/dataBinding"
|
||||
import {
|
||||
getBindableProperties,
|
||||
getComponentBindableProperties,
|
||||
} from "builderStore/dataBinding"
|
||||
|
||||
$: componentInstance = $selectedComponent
|
||||
$: componentDefinition = store.actions.components.getDefinition(
|
||||
$selectedComponent?._component
|
||||
)
|
||||
$: bindings = getBindableProperties($currentAsset, $store.selectedComponentId)
|
||||
$: componentBindings = getComponentBindableProperties(
|
||||
$currentAsset,
|
||||
$store.selectedComponentId
|
||||
)
|
||||
</script>
|
||||
|
||||
<Tabs selected="Settings" noPadding>
|
||||
|
@ -28,6 +35,7 @@
|
|||
{componentInstance}
|
||||
{componentDefinition}
|
||||
{bindings}
|
||||
{componentBindings}
|
||||
/>
|
||||
<DesignSection {componentInstance} {componentDefinition} {bindings} />
|
||||
<CustomStylesSection
|
||||
|
|
|
@ -13,9 +13,10 @@
|
|||
import { generate } from "shortid"
|
||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||
import { OperatorOptions, getValidOperatorsForType } from "constants/lucene"
|
||||
import { selectedComponent, store } from "builderStore"
|
||||
import { selectedComponent } from "builderStore"
|
||||
import { getComponentForSettingType } from "./componentSettings"
|
||||
import PropertyControl from "./PropertyControl.svelte"
|
||||
import { getComponentSettings } from "builderStore/storeUtils"
|
||||
|
||||
export let conditions = []
|
||||
export let bindings = []
|
||||
|
@ -55,15 +56,11 @@
|
|||
]
|
||||
|
||||
let dragDisabled = true
|
||||
$: definition = store.actions.components.getDefinition(
|
||||
$selectedComponent?._component
|
||||
)
|
||||
$: settings = (definition?.settings ?? []).map(setting => {
|
||||
return {
|
||||
label: setting.label,
|
||||
value: setting.key,
|
||||
}
|
||||
})
|
||||
$: settings = getComponentSettings($selectedComponent?._component)
|
||||
$: settingOptions = settings.map(setting => ({
|
||||
label: setting.label,
|
||||
value: setting.key,
|
||||
}))
|
||||
$: conditions.forEach(link => {
|
||||
if (!link.id) {
|
||||
link.id = generate()
|
||||
|
@ -71,9 +68,7 @@
|
|||
})
|
||||
|
||||
const getSettingDefinition = key => {
|
||||
return definition?.settings?.find(setting => {
|
||||
return setting.key === key
|
||||
})
|
||||
return settings.find(setting => setting.key === key)
|
||||
}
|
||||
|
||||
const getComponentForSetting = key => {
|
||||
|
@ -175,7 +170,10 @@
|
|||
bind:value={condition.action}
|
||||
/>
|
||||
{#if condition.action === "update"}
|
||||
<Select options={settings} bind:value={condition.setting} />
|
||||
<Select
|
||||
options={settingOptions}
|
||||
bind:value={condition.setting}
|
||||
/>
|
||||
<div>TO</div>
|
||||
{#if getSettingDefinition(condition.setting)}
|
||||
<PropertyControl
|
||||
|
|
|
@ -11,10 +11,7 @@
|
|||
const getValue = component => `{{ literal ${makePropSafe(component._id)} }}`
|
||||
|
||||
$: path = findComponentPath($currentAsset.props, $store.selectedComponentId)
|
||||
$: providers = path.filter(
|
||||
component =>
|
||||
component._component === "@budibase/standard-components/dataprovider"
|
||||
)
|
||||
$: providers = path.filter(c => c._component?.endsWith("/dataprovider"))
|
||||
|
||||
// Set initial value to closest data provider
|
||||
onMount(() => {
|
||||
|
|
|
@ -20,16 +20,18 @@
|
|||
import { notifications } from "@budibase/bbui"
|
||||
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
|
||||
import IntegrationQueryEditor from "components/integration/index.svelte"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let anchorRight, dropdownRight
|
||||
let drawer
|
||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||
|
||||
export let value = {}
|
||||
export let otherSources
|
||||
export let showAllQueries
|
||||
export let bindings = []
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const arrayTypes = ["attachment", "array"]
|
||||
let anchorRight, dropdownRight
|
||||
let drawer
|
||||
|
||||
$: text = value?.label ?? "Choose an option"
|
||||
$: tables = $tablesStore.list.map(m => ({
|
||||
label: m.name,
|
||||
|
@ -54,8 +56,6 @@
|
|||
name: query.name,
|
||||
tableId: query._id,
|
||||
...query,
|
||||
schema: query.schema,
|
||||
parameters: query.parameters,
|
||||
type: "query",
|
||||
}))
|
||||
$: dataProviders = getDataProviderComponents(
|
||||
|
@ -65,29 +65,40 @@
|
|||
label: provider._instanceName,
|
||||
name: provider._instanceName,
|
||||
providerId: provider._id,
|
||||
value: `{{ literal [${provider._id}] }}`,
|
||||
value: `{{ literal ${safe(provider._id)} }}`,
|
||||
type: "provider",
|
||||
schema: provider.schema,
|
||||
}))
|
||||
$: queryBindableProperties = bindings.map(property => ({
|
||||
...property,
|
||||
category: property.type === "instance" ? "Component" : "Table",
|
||||
label: property.readableBinding,
|
||||
path: property.readableBinding,
|
||||
}))
|
||||
$: links = bindings
|
||||
.filter(x => x.fieldSchema?.type === "link")
|
||||
.map(property => {
|
||||
.map(binding => {
|
||||
const { providerId, readableBinding, fieldSchema } = binding || {}
|
||||
const { name, tableId } = fieldSchema || {}
|
||||
const safeProviderId = safe(providerId)
|
||||
return {
|
||||
providerId: property.providerId,
|
||||
label: property.readableBinding,
|
||||
fieldName: property.fieldSchema.name,
|
||||
tableId: property.fieldSchema.tableId,
|
||||
providerId,
|
||||
label: readableBinding,
|
||||
fieldName: name,
|
||||
tableId,
|
||||
type: "link",
|
||||
// These properties will be enriched by the client library and provide
|
||||
// details of the parent row of the relationship field, from context
|
||||
rowId: `{{ ${property.providerId}._id }}`,
|
||||
rowTableId: `{{ ${property.providerId}.tableId }}`,
|
||||
rowId: `{{ ${safeProviderId}.${safe("_id")} }}`,
|
||||
rowTableId: `{{ ${safeProviderId}.${safe("tableId")} }}`,
|
||||
}
|
||||
})
|
||||
$: fields = bindings
|
||||
.filter(x => arrayTypes.includes(x.fieldSchema?.type))
|
||||
.map(binding => {
|
||||
const { providerId, readableBinding, runtimeBinding } = binding
|
||||
const { name, type, tableId } = binding.fieldSchema
|
||||
return {
|
||||
providerId,
|
||||
label: readableBinding,
|
||||
fieldName: name,
|
||||
fieldType: type,
|
||||
tableId,
|
||||
type: "field",
|
||||
value: `{{ literal ${runtimeBinding} }}`,
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -102,6 +113,14 @@
|
|||
).source
|
||||
return $integrations[source].query[query.queryVerb]
|
||||
}
|
||||
|
||||
const getQueryParams = query => {
|
||||
return $queriesStore.list.find(q => q._id === query?._id)?.parameters || []
|
||||
}
|
||||
|
||||
const getQueryDatasource = query => {
|
||||
return $datasources.list.find(ds => ds._id === query?.datasourceId)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container" bind:this={anchorRight}>
|
||||
|
@ -127,11 +146,10 @@
|
|||
</Button>
|
||||
<DrawerContent slot="body">
|
||||
<Layout noPadding>
|
||||
{#if value.parameters.length > 0}
|
||||
{#if getQueryParams(value._id).length > 0}
|
||||
<ParameterBuilder
|
||||
bind:customParams={value.queryParams}
|
||||
parameters={queries.find(query => query._id === value._id)
|
||||
.parameters}
|
||||
parameters={getQueryParams(value)}
|
||||
{bindings}
|
||||
/>
|
||||
{/if}
|
||||
|
@ -139,9 +157,7 @@
|
|||
height={200}
|
||||
query={value}
|
||||
schema={fetchQueryDefinition(value)}
|
||||
datasource={$datasources.list.find(
|
||||
ds => ds._id === value.datasourceId
|
||||
)}
|
||||
datasource={getQueryDatasource(value)}
|
||||
editable={false}
|
||||
/>
|
||||
</Layout>
|
||||
|
@ -159,52 +175,71 @@
|
|||
<li on:click={() => handleSelected(table)}>{table.label}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<Divider size="S" />
|
||||
<div class="title">
|
||||
<Heading size="XS">Views</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each views as view}
|
||||
<li on:click={() => handleSelected(view)}>{view.label}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<Divider size="S" />
|
||||
<div class="title">
|
||||
<Heading size="XS">Relationships</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each links as link}
|
||||
<li on:click={() => handleSelected(link)}>{link.label}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<Divider size="S" />
|
||||
<div class="title">
|
||||
<Heading size="XS">Queries</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each queries as query}
|
||||
<li
|
||||
class:selected={value === query}
|
||||
on:click={() => handleSelected(query)}
|
||||
>
|
||||
{query.label}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<Divider size="S" />
|
||||
<div class="title">
|
||||
<Heading size="XS">Data Providers</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each dataProviders as provider}
|
||||
<li
|
||||
class:selected={value === provider}
|
||||
on:click={() => handleSelected(provider)}
|
||||
>
|
||||
{provider.label}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{#if views?.length}
|
||||
<Divider size="S" />
|
||||
<div class="title">
|
||||
<Heading size="XS">Views</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each views as view}
|
||||
<li on:click={() => handleSelected(view)}>{view.label}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
{#if queries?.length}
|
||||
<Divider size="S" />
|
||||
<div class="title">
|
||||
<Heading size="XS">Queries</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each queries as query}
|
||||
<li
|
||||
class:selected={value === query}
|
||||
on:click={() => handleSelected(query)}
|
||||
>
|
||||
{query.label}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
{#if links?.length}
|
||||
<Divider size="S" />
|
||||
<div class="title">
|
||||
<Heading size="XS">Relationships</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each links as link}
|
||||
<li on:click={() => handleSelected(link)}>{link.label}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
{#if fields?.length}
|
||||
<Divider size="S" />
|
||||
<div class="title">
|
||||
<Heading size="XS">Fields</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each fields as field}
|
||||
<li on:click={() => handleSelected(field)}>{field.label}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
{#if dataProviders?.length}
|
||||
<Divider size="S" />
|
||||
<div class="title">
|
||||
<Heading size="XS">Data Providers</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each dataProviders as provider}
|
||||
<li
|
||||
class:selected={value === provider}
|
||||
on:click={() => handleSelected(provider)}
|
||||
>
|
||||
{provider.label}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
{#if otherSources?.length}
|
||||
<Divider size="S" />
|
||||
<div class="title">
|
||||
|
|
|
@ -11,14 +11,14 @@
|
|||
Select,
|
||||
} from "@budibase/bbui"
|
||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||
import BindingPanel from "components/common/bindings/BindingPanel.svelte"
|
||||
import ClientBindingPanel from "components/common/bindings/ClientBindingPanel.svelte"
|
||||
import { generate } from "shortid"
|
||||
import { getValidOperatorsForType, OperatorOptions } from "constants/lucene"
|
||||
|
||||
export let schemaFields
|
||||
export let filters = []
|
||||
export let bindings = []
|
||||
export let panel = BindingPanel
|
||||
export let panel = ClientBindingPanel
|
||||
export let allowBindings = true
|
||||
|
||||
const BannedTypes = ["link", "attachment", "formula"]
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
<script>
|
||||
import { Input } from "@budibase/bbui"
|
||||
import { isJSBinding } from "@budibase/string-templates"
|
||||
|
||||
export let value
|
||||
|
||||
$: isJS = isJSBinding(value)
|
||||
</script>
|
||||
|
||||
<Input
|
||||
{...$$props}
|
||||
value={isJS ? "(JavaScript function)" : value}
|
||||
readonly={isJS}
|
||||
on:change
|
||||
/>
|
|
@ -2,13 +2,15 @@
|
|||
import { Button, ActionButton, Drawer } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import NavigationDrawer from "./NavigationDrawer.svelte"
|
||||
import { cloneDeep } from "lodash/fp"
|
||||
|
||||
export let value = []
|
||||
let drawer
|
||||
let links = cloneDeep(value)
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const save = () => {
|
||||
dispatch("change", value)
|
||||
dispatch("change", links)
|
||||
drawer.hide()
|
||||
}
|
||||
</script>
|
||||
|
@ -19,5 +21,5 @@
|
|||
Configure the links in your navigation bar.
|
||||
</svelte:fragment>
|
||||
<Button cta slot="buttons" on:click={save}>Save</Button>
|
||||
<NavigationDrawer slot="body" bind:links={value} />
|
||||
<NavigationDrawer slot="body" bind:links />
|
||||
</Drawer>
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
<script>
|
||||
import { Button, Icon, Drawer, Label } from "@budibase/bbui"
|
||||
import { Label } from "@budibase/bbui"
|
||||
import {
|
||||
readableToRuntimeBinding,
|
||||
runtimeToReadableBinding,
|
||||
} from "builderStore/dataBinding"
|
||||
import BindingPanel from "components/common/bindings/BindingPanel.svelte"
|
||||
import { capitalise } from "helpers"
|
||||
|
||||
export let label = ""
|
||||
export let bindable = true
|
||||
export let componentInstance = {}
|
||||
export let control = null
|
||||
export let key = ""
|
||||
|
@ -17,18 +14,19 @@
|
|||
export let props = {}
|
||||
export let onChange = () => {}
|
||||
export let bindings = []
|
||||
export let componentBindings = []
|
||||
export let nested = false
|
||||
|
||||
let bindingDrawer
|
||||
let anchor
|
||||
let valid
|
||||
|
||||
$: safeValue = getSafeValue(value, props.defaultValue, bindings)
|
||||
$: allBindings = getAllBindings(bindings, componentBindings, nested)
|
||||
$: safeValue = getSafeValue(value, props.defaultValue, allBindings)
|
||||
$: tempValue = safeValue
|
||||
$: replaceBindings = val => readableToRuntimeBinding(bindings, val)
|
||||
$: replaceBindings = val => readableToRuntimeBinding(allBindings, val)
|
||||
|
||||
const handleClose = () => {
|
||||
handleChange(tempValue)
|
||||
bindingDrawer.hide()
|
||||
const getAllBindings = (bindings, componentBindings, nested) => {
|
||||
if (!nested) {
|
||||
return bindings
|
||||
}
|
||||
return [...(componentBindings || []), ...(bindings || [])]
|
||||
}
|
||||
|
||||
// Handle a value change of any type
|
||||
|
@ -64,7 +62,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="property-control" bind:this={anchor} data-cy={`setting-${key}`}>
|
||||
<div class="property-control" data-cy={`setting-${key}`}>
|
||||
{#if type !== "boolean" && label}
|
||||
<div class="label">
|
||||
<Label>{label}</Label>
|
||||
|
@ -78,37 +76,12 @@
|
|||
updateOnChange={false}
|
||||
on:change={handleChange}
|
||||
onChange={handleChange}
|
||||
{bindings}
|
||||
bindings={allBindings}
|
||||
name={key}
|
||||
text={label}
|
||||
{type}
|
||||
{...props}
|
||||
/>
|
||||
{#if bindable && !key.startsWith("_") && type === "text"}
|
||||
<div
|
||||
class="icon"
|
||||
data-cy={`${key}-binding-button`}
|
||||
on:click={bindingDrawer.show}
|
||||
>
|
||||
<Icon size="S" name="FlashOn" />
|
||||
</div>
|
||||
<Drawer bind:this={bindingDrawer} title={capitalise(key)}>
|
||||
<svelte:fragment slot="description">
|
||||
Add the objects on the left to enrich your text.
|
||||
</svelte:fragment>
|
||||
<Button cta slot="buttons" disabled={!valid} on:click={handleClose}>
|
||||
Save
|
||||
</Button>
|
||||
<BindingPanel
|
||||
slot="body"
|
||||
bind:valid
|
||||
value={safeValue}
|
||||
on:change={e => (tempValue = e.detail)}
|
||||
bindableProperties={bindings}
|
||||
allowJS
|
||||
/>
|
||||
</Drawer>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -120,41 +93,10 @@
|
|||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.label {
|
||||
text-transform: capitalize;
|
||||
padding-bottom: var(--spectrum-global-dimension-size-65);
|
||||
}
|
||||
|
||||
.control {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.icon {
|
||||
right: 1px;
|
||||
top: 1px;
|
||||
bottom: 1px;
|
||||
position: absolute;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
box-sizing: border-box;
|
||||
border-left: 1px solid var(--spectrum-alias-border-color);
|
||||
border-top-right-radius: var(--spectrum-alias-border-radius-regular);
|
||||
border-bottom-right-radius: var(--spectrum-alias-border-radius-regular);
|
||||
width: 31px;
|
||||
color: var(--spectrum-alias-text-color);
|
||||
background-color: var(--spectrum-global-color-gray-75);
|
||||
transition: background-color
|
||||
var(--spectrum-global-animation-duration-100, 130ms),
|
||||
box-shadow var(--spectrum-global-animation-duration-100, 130ms),
|
||||
border-color var(--spectrum-global-animation-duration-100, 130ms);
|
||||
}
|
||||
.icon:hover {
|
||||
cursor: pointer;
|
||||
color: var(--spectrum-alias-text-color-hover);
|
||||
background-color: var(--spectrum-global-color-gray-50);
|
||||
border-color: var(--spectrum-alias-border-color-hover);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -10,4 +10,10 @@
|
|||
.filter(x => x != null)
|
||||
</script>
|
||||
|
||||
<DrawerBindableCombobox {value} {bindings} on:change options={urlOptions} />
|
||||
<DrawerBindableCombobox
|
||||
{value}
|
||||
{bindings}
|
||||
on:change
|
||||
options={urlOptions}
|
||||
appendBindingsAsOptions={false}
|
||||
/>
|
||||
|
|
|
@ -15,10 +15,10 @@ import URLSelect from "./URLSelect.svelte"
|
|||
import OptionsEditor from "./OptionsEditor/OptionsEditor.svelte"
|
||||
import FormFieldSelect from "./FormFieldSelect.svelte"
|
||||
import ValidationEditor from "./ValidationEditor/ValidationEditor.svelte"
|
||||
import Input from "./Input.svelte"
|
||||
import DrawerBindableCombobox from "components/common/bindings/DrawerBindableCombobox.svelte"
|
||||
|
||||
const componentMap = {
|
||||
text: Input,
|
||||
text: DrawerBindableCombobox,
|
||||
select: Select,
|
||||
dataSource: DataSourceSelect,
|
||||
dataProvider: DataProviderSelect,
|
||||
|
|
|
@ -54,7 +54,6 @@
|
|||
<DetailSummary name="Screen" collapsible={false}>
|
||||
{#each screenSettings as def (`${componentInstance._id}-${def.key}`)}
|
||||
<PropertyControl
|
||||
bindable={false}
|
||||
control={def.control}
|
||||
label={def.label}
|
||||
key={def.key}
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
{#each properties as prop (`${componentInstance._id}-${prop.key}-${prop.label}`)}
|
||||
<div style="grid-column: {prop.column || 'auto'}">
|
||||
<PropertyControl
|
||||
bindable={false}
|
||||
label={`${prop.label}${hasPropChanged(style, prop) ? " *" : ""}`}
|
||||
control={prop.control}
|
||||
key={prop.key}
|
||||
|
|
|
@ -9,7 +9,6 @@ export const margin = {
|
|||
label: "Top",
|
||||
key: "margin-top",
|
||||
control: Select,
|
||||
bindable: false,
|
||||
placeholder: "None",
|
||||
options: [
|
||||
{ label: "4px", value: "4px" },
|
||||
|
@ -30,7 +29,6 @@ export const margin = {
|
|||
label: "Right",
|
||||
key: "margin-right",
|
||||
control: Select,
|
||||
bindable: false,
|
||||
placeholder: "None",
|
||||
options: [
|
||||
{ label: "4px", value: "4px" },
|
||||
|
@ -51,7 +49,6 @@ export const margin = {
|
|||
label: "Bottom",
|
||||
key: "margin-bottom",
|
||||
control: Select,
|
||||
bindable: false,
|
||||
placeholder: "None",
|
||||
options: [
|
||||
{ label: "4px", value: "4px" },
|
||||
|
@ -72,7 +69,6 @@ export const margin = {
|
|||
label: "Left",
|
||||
key: "margin-left",
|
||||
control: Select,
|
||||
bindable: false,
|
||||
placeholder: "None",
|
||||
options: [
|
||||
{ label: "4px", value: "4px" },
|
||||
|
@ -100,7 +96,6 @@ export const padding = {
|
|||
label: "Top",
|
||||
key: "padding-top",
|
||||
control: Select,
|
||||
bindable: false,
|
||||
placeholder: "None",
|
||||
options: [
|
||||
{ label: "4px", value: "4px" },
|
||||
|
@ -121,7 +116,6 @@ export const padding = {
|
|||
label: "Right",
|
||||
key: "padding-right",
|
||||
control: Select,
|
||||
bindable: false,
|
||||
placeholder: "None",
|
||||
options: [
|
||||
{ label: "4px", value: "4px" },
|
||||
|
@ -142,7 +136,6 @@ export const padding = {
|
|||
label: "Bottom",
|
||||
key: "padding-bottom",
|
||||
control: Select,
|
||||
bindable: false,
|
||||
placeholder: "None",
|
||||
options: [
|
||||
{ label: "4px", value: "4px" },
|
||||
|
@ -163,7 +156,6 @@ export const padding = {
|
|||
label: "Left",
|
||||
key: "padding-left",
|
||||
control: Select,
|
||||
bindable: false,
|
||||
placeholder: "None",
|
||||
options: [
|
||||
{ label: "4px", value: "4px" },
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue