Merge branch 'develop' into oracle-datasource
This commit is contained in:
commit
f94a0eadbe
|
@ -18,7 +18,7 @@ jobs:
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [12.x]
|
node-version: [14.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
|
@ -18,7 +18,7 @@ jobs:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 12.x
|
node-version: 14.x
|
||||||
- run: yarn
|
- run: yarn
|
||||||
- run: yarn bootstrap
|
- run: yarn bootstrap
|
||||||
- run: yarn lint
|
- run: yarn lint
|
||||||
|
|
|
@ -16,7 +16,7 @@ jobs:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 12.x
|
node-version: 14.x
|
||||||
- run: yarn
|
- run: yarn
|
||||||
- run: yarn bootstrap
|
- run: yarn bootstrap
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ jobs:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 12.x
|
node-version: 14.x
|
||||||
- run: yarn
|
- run: yarn
|
||||||
- run: yarn bootstrap
|
- run: yarn bootstrap
|
||||||
- run: yarn lint
|
- run: yarn lint
|
||||||
|
@ -55,4 +55,4 @@ jobs:
|
||||||
env:
|
env:
|
||||||
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
|
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
|
||||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}
|
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}
|
||||||
BUDIBASE_RELEASE_VERSION: ${{ steps.previoustag.outputs.tag }}
|
BUDIBASE_RELEASE_VERSION: ${{ steps.previoustag.outputs.tag }}
|
||||||
|
|
46
README.md
46
README.md
|
@ -54,17 +54,51 @@
|
||||||
<br /><br />
|
<br /><br />
|
||||||
## ✨ Features
|
## ✨ 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 />
|
<br /><br /><br />
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,7 @@ services:
|
||||||
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
|
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
|
||||||
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
|
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
|
||||||
MINIO_URL: http://minio-service:9000
|
MINIO_URL: http://minio-service:9000
|
||||||
|
APPS_URL: http://app-service:4002
|
||||||
COUCH_DB_USERNAME: ${COUCH_DB_USER}
|
COUCH_DB_USERNAME: ${COUCH_DB_USER}
|
||||||
COUCH_DB_PASSWORD: ${COUCH_DB_PASSWORD}
|
COUCH_DB_PASSWORD: ${COUCH_DB_PASSWORD}
|
||||||
COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984
|
COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984
|
||||||
|
|
|
@ -113,6 +113,8 @@ spec:
|
||||||
value: {{ .Values.globals.smtp.port | quote }}
|
value: {{ .Values.globals.smtp.port | quote }}
|
||||||
- name: SMTP_FROM_ADDRESS
|
- name: SMTP_FROM_ADDRESS
|
||||||
value: {{ .Values.globals.smtp.from | quote }}
|
value: {{ .Values.globals.smtp.from | quote }}
|
||||||
|
- name: APPS_URL
|
||||||
|
value: http://app-service:{{ .Values.services.apps.port }}
|
||||||
image: budibase/worker
|
image: budibase/worker
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
name: bbworker
|
name: bbworker
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "0.9.175",
|
"version": "0.9.185-alpha.10",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -59,6 +59,7 @@
|
||||||
"mode:self": "yarn env:selfhost:enable && yarn env:multi:disable && yarn env:account:disable",
|
"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:cloud": "yarn env:selfhost:disable && yarn env:multi:enable && yarn env:account:disable",
|
||||||
"mode:account": "yarn mode:cloud && yarn env:account:enable",
|
"mode:account": "yarn mode:cloud && yarn env:account:enable",
|
||||||
|
"security:audit": "node scripts/audit.js",
|
||||||
"postinstall": "husky install"
|
"postinstall": "husky install"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
user: require("./src/cache/user"),
|
user: require("./src/cache/user"),
|
||||||
|
app: require("./src/cache/appMetadata"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/auth",
|
"name": "@budibase/auth",
|
||||||
"version": "0.9.175",
|
"version": "0.9.185-alpha.10",
|
||||||
"description": "Authentication middlewares for budibase builder and apps",
|
"description": "Authentication middlewares for budibase builder and apps",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"author": "Budibase",
|
"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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ exports.UserStatus = {
|
||||||
exports.Cookies = {
|
exports.Cookies = {
|
||||||
CurrentApp: "budibase:currentapp",
|
CurrentApp: "budibase:currentapp",
|
||||||
Auth: "budibase:auth",
|
Auth: "budibase:auth",
|
||||||
|
Init: "budibase:init",
|
||||||
OIDC_CONFIG: "budibase:oidc:config",
|
OIDC_CONFIG: "budibase:oidc:config",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
const { newid } = require("../hashing")
|
const { newid } = require("../hashing")
|
||||||
const Replication = require("./Replication")
|
const Replication = require("./Replication")
|
||||||
const { DEFAULT_TENANT_ID } = require("../constants")
|
const { DEFAULT_TENANT_ID, Configs } = require("../constants")
|
||||||
const env = require("../environment")
|
const env = require("../environment")
|
||||||
const { StaticDatabases, SEPARATOR, DocumentTypes } = require("./constants")
|
const { StaticDatabases, SEPARATOR, DocumentTypes } = require("./constants")
|
||||||
const { getTenantId, getTenantIDFromAppID } = require("../tenancy")
|
const { getTenantId, getTenantIDFromAppID } = require("../tenancy")
|
||||||
const fetch = require("node-fetch")
|
const fetch = require("node-fetch")
|
||||||
const { getCouch } = require("./index")
|
const { getCouch } = require("./index")
|
||||||
|
const { getAppMetadata } = require("../cache/appMetadata")
|
||||||
|
|
||||||
|
const NO_APP_ERROR = "No app provided"
|
||||||
|
|
||||||
const UNICODE_MAX = "\ufff0"
|
const UNICODE_MAX = "\ufff0"
|
||||||
|
|
||||||
|
@ -45,14 +48,23 @@ function getDocParams(docType, docId = null, otherProps = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.isDevAppID = appId => {
|
exports.isDevAppID = appId => {
|
||||||
|
if (!appId) {
|
||||||
|
throw NO_APP_ERROR
|
||||||
|
}
|
||||||
return appId.startsWith(exports.APP_DEV_PREFIX)
|
return appId.startsWith(exports.APP_DEV_PREFIX)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.isProdAppID = appId => {
|
exports.isProdAppID = appId => {
|
||||||
|
if (!appId) {
|
||||||
|
throw NO_APP_ERROR
|
||||||
|
}
|
||||||
return appId.startsWith(exports.APP_PREFIX) && !exports.isDevAppID(appId)
|
return appId.startsWith(exports.APP_PREFIX) && !exports.isDevAppID(appId)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isDevApp(app) {
|
function isDevApp(app) {
|
||||||
|
if (!app) {
|
||||||
|
throw NO_APP_ERROR
|
||||||
|
}
|
||||||
return exports.isDevAppID(app.appId)
|
return exports.isDevAppID(app.appId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,6 +164,17 @@ exports.getDeployedAppID = appId => {
|
||||||
return 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 = () => {
|
exports.getCouchUrl = () => {
|
||||||
if (!env.COUCH_DB_URL) return
|
if (!env.COUCH_DB_URL) return
|
||||||
|
|
||||||
|
@ -221,16 +244,16 @@ exports.getAllApps = async (CouchDB, { dev, all, idsOnly } = {}) => {
|
||||||
if (idsOnly) {
|
if (idsOnly) {
|
||||||
return appDbNames
|
return appDbNames
|
||||||
}
|
}
|
||||||
const appPromises = appDbNames.map(db =>
|
const appPromises = appDbNames.map(app =>
|
||||||
// skip setup otherwise databases could be re-created
|
// 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) {
|
if (appPromises.length === 0) {
|
||||||
return []
|
return []
|
||||||
} else {
|
} else {
|
||||||
const response = await Promise.allSettled(appPromises)
|
const response = await Promise.allSettled(appPromises)
|
||||||
const apps = response
|
const apps = response
|
||||||
.filter(result => result.status === "fulfilled")
|
.filter(result => result.status === "fulfilled" && result.value != null)
|
||||||
.map(({ value }) => value)
|
.map(({ value }) => value)
|
||||||
if (!all) {
|
if (!all) {
|
||||||
return apps.filter(app => {
|
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) => {
|
exports.dbExists = async (CouchDB, dbName) => {
|
||||||
let exists = false
|
let exists = false
|
||||||
try {
|
try {
|
||||||
|
@ -322,13 +363,50 @@ const getScopedFullConfig = async function (db, { type, user, workspace }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the config with the most granular scope based on context
|
// 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)
|
(a, b) => determineScore(a) - determineScore(b)
|
||||||
)[0]
|
)[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
|
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) {
|
async function getScopedConfig(db, params) {
|
||||||
const configDoc = await getScopedFullConfig(db, params)
|
const configDoc = await getScopedFullConfig(db, params)
|
||||||
return configDoc && configDoc.config ? configDoc.config : configDoc
|
return configDoc && configDoc.config ? configDoc.config : configDoc
|
||||||
|
|
|
@ -25,6 +25,7 @@ module.exports = {
|
||||||
DISABLE_ACCOUNT_PORTAL: process.env.DISABLE_ACCOUNT_PORTAL,
|
DISABLE_ACCOUNT_PORTAL: process.env.DISABLE_ACCOUNT_PORTAL,
|
||||||
SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED),
|
SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED),
|
||||||
COOKIE_DOMAIN: process.env.COOKIE_DOMAIN,
|
COOKIE_DOMAIN: process.env.COOKIE_DOMAIN,
|
||||||
|
PLATFORM_URL: process.env.PLATFORM_URL,
|
||||||
isTest,
|
isTest,
|
||||||
_set(key, value) {
|
_set(key, value) {
|
||||||
process.env[key] = value
|
process.env[key] = value
|
||||||
|
|
|
@ -92,6 +92,10 @@ module.exports = (
|
||||||
finalise(ctx, { authenticated, user, internal, version, publicEndpoint })
|
finalise(ctx, { authenticated, user, internal, version, publicEndpoint })
|
||||||
return next()
|
return next()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
// invalid token, clear the cookie
|
||||||
|
if (err && err.name === "JsonWebTokenError") {
|
||||||
|
clearCookie(ctx, Cookies.Auth)
|
||||||
|
}
|
||||||
// allow configuring for public access
|
// allow configuring for public access
|
||||||
if ((opts && opts.publicAllowed) || publicEndpoint) {
|
if ((opts && opts.publicAllowed) || publicEndpoint) {
|
||||||
finalise(ctx, { authenticated: false, version, publicEndpoint })
|
finalise(ctx, { authenticated: false, version, publicEndpoint })
|
||||||
|
|
|
@ -6,6 +6,7 @@ exports.ObjectStoreBuckets = {
|
||||||
APPS: "prod-budi-app-assets",
|
APPS: "prod-budi-app-assets",
|
||||||
TEMPLATES: "templates",
|
TEMPLATES: "templates",
|
||||||
GLOBAL: "global",
|
GLOBAL: "global",
|
||||||
|
GLOBAL_CLOUD: "prod-budi-tenant-uploads",
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.budibaseTempDir = function () {
|
exports.budibaseTempDir = function () {
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
const Client = require("./index")
|
const Client = require("./index")
|
||||||
const utils = require("./utils")
|
const utils = require("./utils")
|
||||||
|
|
||||||
let userClient, sessionClient
|
let userClient, sessionClient, appClient
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
userClient = await new Client(utils.Databases.USER_CACHE).init()
|
userClient = await new Client(utils.Databases.USER_CACHE).init()
|
||||||
sessionClient = await new Client(utils.Databases.SESSIONS).init()
|
sessionClient = await new Client(utils.Databases.SESSIONS).init()
|
||||||
|
appClient = await new Client(utils.Databases.APP_METADATA).init()
|
||||||
}
|
}
|
||||||
|
|
||||||
process.on("exit", async () => {
|
process.on("exit", async () => {
|
||||||
if (userClient) await userClient.finish()
|
if (userClient) await userClient.finish()
|
||||||
if (sessionClient) await sessionClient.finish()
|
if (sessionClient) await sessionClient.finish()
|
||||||
|
if (appClient) await appClient.finish()
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -26,4 +28,10 @@ module.exports = {
|
||||||
}
|
}
|
||||||
return sessionClient
|
return sessionClient
|
||||||
},
|
},
|
||||||
|
getAppClient: async () => {
|
||||||
|
if (!appClient) {
|
||||||
|
await init()
|
||||||
|
}
|
||||||
|
return appClient
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ exports.Databases = {
|
||||||
SESSIONS: "session",
|
SESSIONS: "session",
|
||||||
USER_CACHE: "users",
|
USER_CACHE: "users",
|
||||||
FLAGS: "flags",
|
FLAGS: "flags",
|
||||||
|
APP_METADATA: "appMetadata",
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.SEPARATOR = SEPARATOR
|
exports.SEPARATOR = SEPARATOR
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/bbui",
|
"name": "@budibase/bbui",
|
||||||
"description": "A UI solution used in the different Budibase projects.",
|
"description": "A UI solution used in the different Budibase projects.",
|
||||||
"version": "0.9.175",
|
"version": "0.9.185-alpha.10",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"module": "dist/bbui.es.js",
|
"module": "dist/bbui.es.js",
|
||||||
|
|
|
@ -36,5 +36,7 @@
|
||||||
{getOptionLabel}
|
{getOptionLabel}
|
||||||
{getOptionValue}
|
{getOptionValue}
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
|
on:pick
|
||||||
|
on:type
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
<label
|
<label
|
||||||
class="spectrum-Checkbox spectrum-Checkbox--emphasized {sizeClass}"
|
class="spectrum-Checkbox spectrum-Checkbox--emphasized {sizeClass}"
|
||||||
class:is-invalid={!!error}
|
class:is-invalid={!!error}
|
||||||
|
class:checked={value}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
checked={value}
|
checked={value}
|
||||||
|
@ -50,6 +51,16 @@
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<style>
|
<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 {
|
.spectrum-Checkbox-input {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,8 +40,15 @@
|
||||||
open = false
|
open = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChange = e => {
|
const onType = e => {
|
||||||
selectOption(e.target.value)
|
const value = e.target.value
|
||||||
|
dispatch("type", value)
|
||||||
|
selectOption(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPick = value => {
|
||||||
|
dispatch("pick", value)
|
||||||
|
selectOption(value)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -62,7 +69,7 @@
|
||||||
type="text"
|
type="text"
|
||||||
on:focus={() => (focus = true)}
|
on:focus={() => (focus = true)}
|
||||||
on:blur={() => (focus = false)}
|
on:blur={() => (focus = false)}
|
||||||
on:change={onChange}
|
on:change={onType}
|
||||||
value={value || ""}
|
value={value || ""}
|
||||||
placeholder={placeholder || ""}
|
placeholder={placeholder || ""}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
@ -99,7 +106,7 @@
|
||||||
role="option"
|
role="option"
|
||||||
aria-selected="true"
|
aria-selected="true"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
on:click={() => selectOption(getOptionValue(option))}
|
on:click={() => onPick(getOptionValue(option))}
|
||||||
>
|
>
|
||||||
<span class="spectrum-Menu-itemLabel"
|
<span class="spectrum-Menu-itemLabel"
|
||||||
>{getOptionLabel(option)}</span
|
>{getOptionLabel(option)}</span
|
||||||
|
|
|
@ -47,5 +47,6 @@
|
||||||
--spectrum-semantic-positive-border-color: #2d9d78;
|
--spectrum-semantic-positive-border-color: #2d9d78;
|
||||||
--spectrum-semantic-positive-icon-color: #2d9d78;
|
--spectrum-semantic-positive-icon-color: #2d9d78;
|
||||||
--spectrum-semantic-negative-icon-color: #e34850;
|
--spectrum-semantic-negative-icon-color: #e34850;
|
||||||
|
min-width: 100px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,16 +1,67 @@
|
||||||
<script>
|
<script>
|
||||||
import "@spectrum-css/fieldlabel/dist/index-vars.css"
|
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 size = "M"
|
||||||
|
export let tooltip = ""
|
||||||
|
export let showTooltip = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<label for="" class={`spectrum-FieldLabel spectrum-FieldLabel--size${size}`}>
|
{#if tooltip}
|
||||||
<slot />
|
<div class="container">
|
||||||
</label>
|
<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>
|
<style>
|
||||||
label {
|
label {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
white-space: nowrap;
|
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>
|
</style>
|
||||||
|
|
|
@ -3,12 +3,22 @@
|
||||||
|
|
||||||
export let direction = "top"
|
export let direction = "top"
|
||||||
export let text = ""
|
export let text = ""
|
||||||
|
export let textWrapping = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<span class="u-tooltip-showOnHover tooltip">
|
<!-- Showing / Hiding a text wrapped tooltip should be handled outside the component -->
|
||||||
<slot />
|
{#if textWrapping}
|
||||||
<div class={`spectrum-Tooltip spectrum-Tooltip--${direction}`}>
|
<span class="spectrum-Tooltip spectrum-Tooltip--{direction} is-open">
|
||||||
<span class="spectrum-Tooltip-label">{text}</span>
|
<span class="spectrum-Tooltip-label">{text}</span>
|
||||||
<span class="spectrum-Tooltip-tip" />
|
<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}
|
||||||
|
|
|
@ -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 |
|
@ -11,7 +11,8 @@ it("should rename an unpublished application", () => {
|
||||||
renameApp(appRename)
|
renameApp(appRename)
|
||||||
cy.searchForApplication(appRename)
|
cy.searchForApplication(appRename)
|
||||||
cy.get(".appGrid").find(".wrapper").should("have.length", 1)
|
cy.get(".appGrid").find(".wrapper").should("have.length", 1)
|
||||||
})
|
cy.deleteApp(appRename)
|
||||||
|
})
|
||||||
|
|
||||||
xit("Should rename a published application", () => {
|
xit("Should rename a published application", () => {
|
||||||
// It is not possible to rename a published application
|
// It is not possible to rename a published application
|
||||||
|
|
|
@ -17,6 +17,7 @@ process.env.JWT_SECRET = cypressConfig.env.JWT_SECRET
|
||||||
process.env.COUCH_URL = `leveldb://${tmpdir}/.data/`
|
process.env.COUCH_URL = `leveldb://${tmpdir}/.data/`
|
||||||
process.env.SELF_HOSTED = 1
|
process.env.SELF_HOSTED = 1
|
||||||
process.env.WORKER_URL = "http://localhost:10002/"
|
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_URL = `http://localhost:${MAIN_PORT}/`
|
||||||
process.env.MINIO_ACCESS_KEY = "budibase"
|
process.env.MINIO_ACCESS_KEY = "budibase"
|
||||||
process.env.MINIO_SECRET_KEY = "budibase"
|
process.env.MINIO_SECRET_KEY = "budibase"
|
||||||
|
|
|
@ -43,24 +43,26 @@ Cypress.Commands.add("createApp", name => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("deleteApp", () => {
|
Cypress.Commands.add("deleteApp", appName => {
|
||||||
cy.visit(`localhost:${Cypress.env("PORT")}/builder`)
|
cy.visit(`localhost:${Cypress.env("PORT")}/builder`)
|
||||||
cy.wait(1000)
|
cy.wait(1000)
|
||||||
cy.request(`localhost:${Cypress.env("PORT")}/api/applications?status=all`)
|
cy.request(`localhost:${Cypress.env("PORT")}/api/applications?status=all`)
|
||||||
.its("body")
|
.its("body")
|
||||||
.then(val => {
|
.then(val => {
|
||||||
console.log(val)
|
|
||||||
if (val.length > 0) {
|
if (val.length > 0) {
|
||||||
cy.get(".title > :nth-child(3) > .spectrum-Icon").click()
|
cy.get(".title > :nth-child(3) > .spectrum-Icon").click()
|
||||||
cy.contains("Delete").click()
|
cy.contains("Delete").click()
|
||||||
cy.get(".spectrum-Button--warning").click()
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
|
cy.get("input").type(appName)
|
||||||
|
cy.get(".spectrum-Button--warning").click()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("createTestApp", () => {
|
Cypress.Commands.add("createTestApp", () => {
|
||||||
const appName = "Cypress Tests"
|
const appName = "Cypress Tests"
|
||||||
cy.deleteApp()
|
cy.deleteApp(appName)
|
||||||
cy.createApp(appName, "This app is used for Cypress testing.")
|
cy.createApp(appName, "This app is used for Cypress testing.")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -186,8 +188,16 @@ Cypress.Commands.add("navigateToFrontend", () => {
|
||||||
Cypress.Commands.add("createScreen", (screenName, route) => {
|
Cypress.Commands.add("createScreen", (screenName, route) => {
|
||||||
cy.get("[aria-label=AddCircle]").click()
|
cy.get("[aria-label=AddCircle]").click()
|
||||||
cy.get(".spectrum-Modal").within(() => {
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
cy.get("input").first().type(screenName)
|
cy.get(".item").first().click()
|
||||||
cy.get("input").eq(1).type(route)
|
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.get(".spectrum-Modal").within(() => {
|
||||||
|
cy.get(`[data-cy="left-nav"]`).click()
|
||||||
cy.get(".spectrum-Button--cta").click()
|
cy.get(".spectrum-Button--cta").click()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "0.9.175",
|
"version": "0.9.185-alpha.10",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -9,7 +9,7 @@
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watchAll",
|
"test:watch": "jest --watchAll",
|
||||||
"dev:builder": "routify -c dev:vite",
|
"dev:builder": "routify -c dev:vite",
|
||||||
"dev:vite": "vite",
|
"dev:vite": "vite --host 0.0.0.0",
|
||||||
"rollup": "rollup -c -w",
|
"rollup": "rollup -c -w",
|
||||||
"cy:setup": "node ./cypress/setup.js",
|
"cy:setup": "node ./cypress/setup.js",
|
||||||
"cy:run": "cypress run",
|
"cy:run": "cypress run",
|
||||||
|
@ -65,10 +65,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^0.9.175",
|
"@budibase/bbui": "^0.9.185-alpha.10",
|
||||||
"@budibase/client": "^0.9.175",
|
"@budibase/client": "^0.9.185-alpha.10",
|
||||||
"@budibase/colorpicker": "1.1.2",
|
"@budibase/colorpicker": "1.1.2",
|
||||||
"@budibase/string-templates": "^0.9.175",
|
"@budibase/string-templates": "^0.9.185-alpha.10",
|
||||||
"@sentry/browser": "5.19.1",
|
"@sentry/browser": "5.19.1",
|
||||||
"@spectrum-css/page": "^3.0.1",
|
"@spectrum-css/page": "^3.0.1",
|
||||||
"@spectrum-css/vars": "^3.0.1",
|
"@spectrum-css/vars": "^3.0.1",
|
||||||
|
@ -91,7 +91,7 @@
|
||||||
"@babel/runtime": "^7.13.10",
|
"@babel/runtime": "^7.13.10",
|
||||||
"@rollup/plugin-replace": "^2.4.2",
|
"@rollup/plugin-replace": "^2.4.2",
|
||||||
"@roxi/routify": "2.18.0",
|
"@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/jest-dom": "^5.11.10",
|
||||||
"@testing-library/svelte": "^3.0.0",
|
"@testing-library/svelte": "^3.0.0",
|
||||||
"babel-jest": "^26.6.3",
|
"babel-jest": "^26.6.3",
|
||||||
|
|
|
@ -12,7 +12,7 @@ export default class PosthogClient {
|
||||||
|
|
||||||
posthog.init(this.token, {
|
posthog.init(this.token, {
|
||||||
autocapture: false,
|
autocapture: false,
|
||||||
capture_pageview: false,
|
capture_pageview: true,
|
||||||
api_host: this.url,
|
api_host: this.url,
|
||||||
})
|
})
|
||||||
posthog.set_config({ persistence: "cookie" })
|
posthog.set_config({ persistence: "cookie" })
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
findAllMatchingComponents,
|
findAllMatchingComponents,
|
||||||
findComponent,
|
findComponent,
|
||||||
findComponentPath,
|
findComponentPath,
|
||||||
|
getComponentSettings,
|
||||||
} from "./storeUtils"
|
} from "./storeUtils"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import { queries as queriesStores, tables as tablesStore } from "stores/backend"
|
import { queries as queriesStores, tables as tablesStore } from "stores/backend"
|
||||||
|
@ -30,14 +31,33 @@ export const getBindableProperties = (asset, componentId) => {
|
||||||
const deviceBindings = getDeviceBindings()
|
const deviceBindings = getDeviceBindings()
|
||||||
const stateBindings = getStateBindings()
|
const stateBindings = getStateBindings()
|
||||||
return [
|
return [
|
||||||
...stateBindings,
|
|
||||||
...deviceBindings,
|
|
||||||
...urlBindings,
|
|
||||||
...contextBindings,
|
...contextBindings,
|
||||||
|
...urlBindings,
|
||||||
|
...stateBindings,
|
||||||
...userBindings,
|
...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.
|
* 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
|
* Gets a datasource object for a certain data provider component
|
||||||
*/
|
*/
|
||||||
export const getDatasourceForProvider = (asset, component) => {
|
export const getDatasourceForProvider = (asset, component) => {
|
||||||
const def = store.actions.components.getDefinition(component?._component)
|
const settings = getComponentSettings(component?._component)
|
||||||
if (!def) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this component has a dataProvider setting, go up the stack and use it
|
// 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"
|
return setting.type === "dataProvider"
|
||||||
})
|
})
|
||||||
if (dataProviderSetting) {
|
if (dataProviderSetting) {
|
||||||
|
@ -100,7 +117,7 @@ export const getDatasourceForProvider = (asset, component) => {
|
||||||
|
|
||||||
// Extract datasource from component instance
|
// Extract datasource from component instance
|
||||||
const validSettingTypes = ["dataSource", "table", "schema"]
|
const validSettingTypes = ["dataSource", "table", "schema"]
|
||||||
const datasourceSetting = def.settings.find(setting => {
|
const datasourceSetting = settings.find(setting => {
|
||||||
return validSettingTypes.includes(setting.type)
|
return validSettingTypes.includes(setting.type)
|
||||||
})
|
})
|
||||||
if (!datasourceSetting) {
|
if (!datasourceSetting) {
|
||||||
|
@ -127,9 +144,26 @@ export const getDatasourceForProvider = (asset, component) => {
|
||||||
const getContextBindings = (asset, componentId) => {
|
const getContextBindings = (asset, componentId) => {
|
||||||
// Extract any components which provide data contexts
|
// Extract any components which provide data contexts
|
||||||
const dataProviders = getDataProviderComponents(asset, componentId)
|
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
|
// Create bindings for each data provider
|
||||||
|
let bindings = []
|
||||||
dataProviders.forEach(component => {
|
dataProviders.forEach(component => {
|
||||||
const def = store.actions.components.getDefinition(component._component)
|
const def = store.actions.components.getDefinition(component._component)
|
||||||
const contexts = Array.isArray(def.context) ? def.context : [def.context]
|
const contexts = Array.isArray(def.context) ? def.context : [def.context]
|
||||||
|
@ -142,6 +176,7 @@ const getContextBindings = (asset, componentId) => {
|
||||||
|
|
||||||
let schema
|
let schema
|
||||||
let readablePrefix
|
let readablePrefix
|
||||||
|
let runtimeSuffix = context.suffix
|
||||||
|
|
||||||
if (context.type === "form") {
|
if (context.type === "form") {
|
||||||
// Forms do not need table schemas
|
// Forms do not need table schemas
|
||||||
|
@ -171,22 +206,19 @@ const getContextBindings = (asset, componentId) => {
|
||||||
|
|
||||||
const keys = Object.keys(schema).sort()
|
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
|
// Create bindable properties for each schema field
|
||||||
const safeComponentId = makePropSafe(component._id)
|
|
||||||
keys.forEach(key => {
|
keys.forEach(key => {
|
||||||
const fieldSchema = schema[key]
|
const fieldSchema = schema[key]
|
||||||
|
|
||||||
// Make safe runtime binding and replace certain bindings with a
|
// Make safe runtime binding
|
||||||
// new property to help display components
|
const runtimeBinding = `${safeComponentId}.${makePropSafe(key)}`
|
||||||
let runtimeBoundKey = key
|
|
||||||
if (fieldSchema.type === "link") {
|
|
||||||
runtimeBoundKey = `${key}_text`
|
|
||||||
} else if (fieldSchema.type === "attachment") {
|
|
||||||
runtimeBoundKey = `${key}_first`
|
|
||||||
}
|
|
||||||
const runtimeBinding = `${safeComponentId}.${makePropSafe(
|
|
||||||
runtimeBoundKey
|
|
||||||
)}`
|
|
||||||
|
|
||||||
// Optionally use a prefix with readable bindings
|
// Optionally use a prefix with readable bindings
|
||||||
let readableBinding = component._instanceName
|
let readableBinding = component._instanceName
|
||||||
|
@ -203,7 +235,7 @@ const getContextBindings = (asset, componentId) => {
|
||||||
// Field schema and provider are required to construct relationship
|
// Field schema and provider are required to construct relationship
|
||||||
// datasource options, based on bindable properties
|
// datasource options, based on bindable properties
|
||||||
fieldSchema,
|
fieldSchema,
|
||||||
providerId: component._id,
|
providerId,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -225,17 +257,9 @@ const getUserBindings = () => {
|
||||||
const safeUser = makePropSafe("user")
|
const safeUser = makePropSafe("user")
|
||||||
keys.forEach(key => {
|
keys.forEach(key => {
|
||||||
const fieldSchema = schema[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({
|
bindings.push({
|
||||||
type: "context",
|
type: "context",
|
||||||
runtimeBinding: `${safeUser}.${makePropSafe(runtimeBoundKey)}`,
|
runtimeBinding: `${safeUser}.${makePropSafe(key)}`,
|
||||||
readableBinding: `Current User.${key}`,
|
readableBinding: `Current User.${key}`,
|
||||||
// Field schema and provider are required to construct relationship
|
// Field schema and provider are required to construct relationship
|
||||||
// datasource options, based on bindable properties
|
// datasource options, based on bindable properties
|
||||||
|
@ -309,8 +333,11 @@ const getUrlBindings = asset => {
|
||||||
*/
|
*/
|
||||||
export const getSchemaForDatasource = (asset, datasource, isForm = false) => {
|
export const getSchemaForDatasource = (asset, datasource, isForm = false) => {
|
||||||
let schema, table
|
let schema, table
|
||||||
|
|
||||||
if (datasource) {
|
if (datasource) {
|
||||||
const { type } = datasource
|
const { type } = datasource
|
||||||
|
|
||||||
|
// Determine the source table from the datasource type
|
||||||
if (type === "provider") {
|
if (type === "provider") {
|
||||||
const component = findComponent(asset.props, datasource.providerId)
|
const component = findComponent(asset.props, datasource.providerId)
|
||||||
const source = getDatasourceForProvider(asset, component)
|
const source = getDatasourceForProvider(asset, component)
|
||||||
|
@ -318,11 +345,32 @@ export const getSchemaForDatasource = (asset, datasource, isForm = false) => {
|
||||||
} else if (type === "query") {
|
} else if (type === "query") {
|
||||||
const queries = get(queriesStores).list
|
const queries = get(queriesStores).list
|
||||||
table = queries.find(query => query._id === datasource._id)
|
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 {
|
} else {
|
||||||
const tables = get(tablesStore).list
|
const tables = get(tablesStore).list
|
||||||
table = tables.find(table => table._id === datasource.tableId)
|
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") {
|
if (type === "view") {
|
||||||
schema = cloneDeep(table.views?.[datasource.name]?.schema)
|
schema = cloneDeep(table.views?.[datasource.name]?.schema)
|
||||||
} else if (type === "query" && isForm) {
|
} else if (type === "query" && isForm) {
|
||||||
|
@ -374,8 +422,8 @@ const buildFormSchema = component => {
|
||||||
if (!component) {
|
if (!component) {
|
||||||
return schema
|
return schema
|
||||||
}
|
}
|
||||||
const def = store.actions.components.getDefinition(component._component)
|
const settings = getComponentSettings(component._component)
|
||||||
const fieldSetting = def?.settings?.find(
|
const fieldSetting = settings.find(
|
||||||
setting => setting.key === "field" && setting.type.startsWith("field/")
|
setting => setting.key === "field" && setting.type.startsWith("field/")
|
||||||
)
|
)
|
||||||
if (fieldSetting && component.field) {
|
if (fieldSetting && component.field) {
|
||||||
|
@ -501,7 +549,7 @@ function bindingReplacement(bindableProperties, textWithBindings, convertTo) {
|
||||||
* {{ literal [componentId] }}
|
* {{ literal [componentId] }}
|
||||||
*/
|
*/
|
||||||
function extractLiteralHandlebarsID(value) {
|
function extractLiteralHandlebarsID(value) {
|
||||||
return value?.match(/{{\s*literal[\s[]+([a-fA-F0-9]+)[\s\]]*}}/)?.[1]
|
return value?.match(/{{\s*literal\s*\[+([^\]]+)].*}}/)?.[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -25,6 +25,7 @@ import {
|
||||||
findClosestMatchingComponent,
|
findClosestMatchingComponent,
|
||||||
findAllMatchingComponents,
|
findAllMatchingComponents,
|
||||||
findComponent,
|
findComponent,
|
||||||
|
getComponentSettings,
|
||||||
} from "../storeUtils"
|
} from "../storeUtils"
|
||||||
import { uuid } from "../uuid"
|
import { uuid } from "../uuid"
|
||||||
import { removeBindings } from "../dataBinding"
|
import { removeBindings } from "../dataBinding"
|
||||||
|
@ -44,6 +45,7 @@ const INITIAL_FRONTEND_STATE = {
|
||||||
state: false,
|
state: false,
|
||||||
customThemes: false,
|
customThemes: false,
|
||||||
devicePreview: false,
|
devicePreview: false,
|
||||||
|
messagePassing: false,
|
||||||
},
|
},
|
||||||
currentFrontEndType: "none",
|
currentFrontEndType: "none",
|
||||||
selectedScreenId: "",
|
selectedScreenId: "",
|
||||||
|
@ -368,14 +370,13 @@ export const getFrontendStore = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate default props
|
// Generate default props
|
||||||
|
const settings = getComponentSettings(componentName)
|
||||||
let props = { ...presetProps }
|
let props = { ...presetProps }
|
||||||
if (definition.settings) {
|
settings.forEach(setting => {
|
||||||
definition.settings.forEach(setting => {
|
if (setting.defaultValue !== undefined) {
|
||||||
if (setting.defaultValue !== undefined) {
|
props[setting.key] = setting.defaultValue
|
||||||
props[setting.key] = setting.defaultValue
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add any extra properties the component needs
|
// Add any extra properties the component needs
|
||||||
let extras = {}
|
let extras = {}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { Screen } from "./utils/Screen"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: `Create from scratch`,
|
name: `Create from scratch`,
|
||||||
|
id: `createFromScratch`,
|
||||||
create: () => createScreen(),
|
create: () => createScreen(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { store } from "./index"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively searches for a specific component ID
|
* Recursively searches for a specific component ID
|
||||||
*/
|
*/
|
||||||
|
@ -123,3 +125,20 @@ const searchComponentTree = (rootComponent, matchComponent) => {
|
||||||
}
|
}
|
||||||
return null
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -202,7 +202,7 @@
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
.block {
|
.block {
|
||||||
width: 360px;
|
width: 480px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
background-color: var(--background);
|
background-color: var(--background);
|
||||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
|
|
|
@ -8,10 +8,13 @@
|
||||||
|
|
||||||
let name
|
let name
|
||||||
let selectedTrigger
|
let selectedTrigger
|
||||||
|
let nameTouched = false
|
||||||
let triggerVal
|
let triggerVal
|
||||||
export let webhookModal
|
export let webhookModal
|
||||||
|
|
||||||
$: instanceId = $database._id
|
$: instanceId = $database._id
|
||||||
|
$: nameError =
|
||||||
|
nameTouched && !name ? "Please specify a name for the automation." : null
|
||||||
|
|
||||||
async function createAutomation() {
|
async function createAutomation() {
|
||||||
await automationStore.actions.create({
|
await automationStore.actions.create({
|
||||||
|
@ -51,13 +54,18 @@
|
||||||
confirmText="Save"
|
confirmText="Save"
|
||||||
size="M"
|
size="M"
|
||||||
onConfirm={createAutomation}
|
onConfirm={createAutomation}
|
||||||
disabled={!selectedTrigger}
|
disabled={!selectedTrigger || !name}
|
||||||
>
|
>
|
||||||
<Body size="XS"
|
<Body size="XS"
|
||||||
>Please name your automation, then select a trigger. Every automation must
|
>Please name your automation, then select a trigger. Every automation must
|
||||||
start with a trigger.
|
start with a trigger.
|
||||||
</Body>
|
</Body>
|
||||||
<Input bind:value={name} label="Name" />
|
<Input
|
||||||
|
bind:value={name}
|
||||||
|
on:change={() => (nameTouched = true)}
|
||||||
|
bind:error={nameError}
|
||||||
|
label="Name"
|
||||||
|
/>
|
||||||
|
|
||||||
<Layout noPadding>
|
<Layout noPadding>
|
||||||
<Body size="S">Triggers</Body>
|
<Body size="S">Triggers</Body>
|
||||||
|
|
|
@ -234,7 +234,8 @@
|
||||||
<Editor
|
<Editor
|
||||||
mode="javascript"
|
mode="javascript"
|
||||||
on:change={e => {
|
on:change={e => {
|
||||||
onChange(e, key)
|
// need to pass without the value inside
|
||||||
|
onChange({ detail: e.detail.value }, key)
|
||||||
inputData[key] = e.detail.value
|
inputData[key] = e.detail.value
|
||||||
}}
|
}}
|
||||||
value={inputData[key]}
|
value={inputData[key]}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
import CreateViewButton from "./buttons/CreateViewButton.svelte"
|
import CreateViewButton from "./buttons/CreateViewButton.svelte"
|
||||||
import ExistingRelationshipButton from "./buttons/ExistingRelationshipButton.svelte"
|
import ExistingRelationshipButton from "./buttons/ExistingRelationshipButton.svelte"
|
||||||
import ExportButton from "./buttons/ExportButton.svelte"
|
import ExportButton from "./buttons/ExportButton.svelte"
|
||||||
|
import ImportButton from "./buttons/ImportButton.svelte"
|
||||||
import EditRolesButton from "./buttons/EditRolesButton.svelte"
|
import EditRolesButton from "./buttons/EditRolesButton.svelte"
|
||||||
import ManageAccessButton from "./buttons/ManageAccessButton.svelte"
|
import ManageAccessButton from "./buttons/ManageAccessButton.svelte"
|
||||||
import HideAutocolumnButton from "./buttons/HideAutocolumnButton.svelte"
|
import HideAutocolumnButton from "./buttons/HideAutocolumnButton.svelte"
|
||||||
|
@ -124,6 +125,10 @@
|
||||||
<HideAutocolumnButton bind:hideAutocolumns />
|
<HideAutocolumnButton bind:hideAutocolumns />
|
||||||
<!-- always have the export last -->
|
<!-- always have the export last -->
|
||||||
<ExportButton view={$tables.selected?._id} />
|
<ExportButton view={$tables.selected?._id} />
|
||||||
|
<ImportButton
|
||||||
|
tableId={$tables.selected?._id}
|
||||||
|
on:updaterows={onUpdateRows}
|
||||||
|
/>
|
||||||
{#key id}
|
{#key id}
|
||||||
<TableFilterButton {schema} on:change={onFilter} />
|
<TableFilterButton {schema} on:change={onFilter} />
|
||||||
{/key}
|
{/key}
|
||||||
|
|
|
@ -8,7 +8,11 @@
|
||||||
import CreateEditRow from "./modals/CreateEditRow.svelte"
|
import CreateEditRow from "./modals/CreateEditRow.svelte"
|
||||||
import CreateEditUser from "./modals/CreateEditUser.svelte"
|
import CreateEditUser from "./modals/CreateEditUser.svelte"
|
||||||
import CreateEditColumn from "./modals/CreateEditColumn.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"
|
import RoleCell from "./cells/RoleCell.svelte"
|
||||||
|
|
||||||
export let schema = {}
|
export let schema = {}
|
||||||
|
@ -33,6 +37,15 @@
|
||||||
$: isUsersTable = tableId === TableNames.USERS
|
$: isUsersTable = tableId === TableNames.USERS
|
||||||
$: data && resetSelectedRows()
|
$: data && resetSelectedRows()
|
||||||
$: editRowComponent = isUsersTable ? CreateEditUser : CreateEditRow
|
$: editRowComponent = isUsersTable ? CreateEditUser : CreateEditRow
|
||||||
|
$: {
|
||||||
|
UNSORTABLE_TYPES.forEach(type => {
|
||||||
|
Object.values(schema).forEach(col => {
|
||||||
|
if (col.type === type) {
|
||||||
|
col.sortable = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
$: {
|
$: {
|
||||||
if (isUsersTable) {
|
if (isUsersTable) {
|
||||||
customRenderers = [
|
customRenderers = [
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
let modal
|
let modal
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionButton icon="Download" size="S" quiet on:click={modal.show}>
|
<ActionButton icon="DataDownload" size="S" quiet on:click={modal.show}>
|
||||||
Export
|
Export
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<Modal bind:this={modal}>
|
<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>
|
|
@ -18,6 +18,11 @@
|
||||||
FIELDS,
|
FIELDS,
|
||||||
AUTO_COLUMN_SUB_TYPES,
|
AUTO_COLUMN_SUB_TYPES,
|
||||||
RelationshipTypes,
|
RelationshipTypes,
|
||||||
|
ALLOWABLE_STRING_OPTIONS,
|
||||||
|
ALLOWABLE_NUMBER_OPTIONS,
|
||||||
|
ALLOWABLE_STRING_TYPES,
|
||||||
|
ALLOWABLE_NUMBER_TYPES,
|
||||||
|
SWITCHABLE_TYPES,
|
||||||
} from "constants/backend"
|
} from "constants/backend"
|
||||||
import { getAutoColumnInformation, buildAutoColumn } from "builderStore/utils"
|
import { getAutoColumnInformation, buildAutoColumn } from "builderStore/utils"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
|
@ -59,9 +64,6 @@
|
||||||
let deletion
|
let deletion
|
||||||
|
|
||||||
$: checkConstraints(field)
|
$: checkConstraints(field)
|
||||||
$: tableOptions = $tables.list.filter(
|
|
||||||
opt => opt._id !== $tables.draft._id && opt.type === table.type
|
|
||||||
)
|
|
||||||
$: required = !!field?.constraints?.presence || primaryDisplay
|
$: required = !!field?.constraints?.presence || primaryDisplay
|
||||||
$: uneditable =
|
$: uneditable =
|
||||||
$tables.selected?._id === TableNames.USERS &&
|
$tables.selected?._id === TableNames.USERS &&
|
||||||
|
@ -88,6 +90,16 @@
|
||||||
field.type !== LINK_TYPE && !uneditable && field.type !== AUTO_TYPE
|
field.type !== LINK_TYPE && !uneditable && field.type !== AUTO_TYPE
|
||||||
$: relationshipOptions = getRelationshipOptions(field)
|
$: relationshipOptions = getRelationshipOptions(field)
|
||||||
$: external = table.type === "external"
|
$: 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() {
|
async function saveColumn() {
|
||||||
if (field.type === AUTO_TYPE) {
|
if (field.type === AUTO_TYPE) {
|
||||||
|
@ -174,7 +186,7 @@
|
||||||
if (!field || !field.tableId) {
|
if (!field || !field.tableId) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const linkTable = tableOptions.find(table => table._id === field.tableId)
|
const linkTable = tableOptions?.find(table => table._id === field.tableId)
|
||||||
if (!linkTable) {
|
if (!linkTable) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -200,7 +212,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAllowedTypes() {
|
function getAllowedTypes() {
|
||||||
if (!external) {
|
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 [
|
return [
|
||||||
...Object.values(fieldDefinitions),
|
...Object.values(fieldDefinitions),
|
||||||
{ name: "Auto Column", type: AUTO_TYPE },
|
{ name: "Auto Column", type: AUTO_TYPE },
|
||||||
|
@ -255,7 +274,7 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
disabled={originalName}
|
disabled={!typeEnabled}
|
||||||
label="Type"
|
label="Type"
|
||||||
bind:value={field.type}
|
bind:value={field.type}
|
||||||
on:change={handleTypeChange}
|
on:change={handleTypeChange}
|
||||||
|
|
|
@ -69,6 +69,7 @@
|
||||||
({ _id }) => _id === $views.selected?.tableId
|
({ _id }) => _id === $views.selected?.tableId
|
||||||
)
|
)
|
||||||
$: fields = viewTable && Object.keys(viewTable.schema)
|
$: fields = viewTable && Object.keys(viewTable.schema)
|
||||||
|
$: schema = viewTable && viewTable.schema ? viewTable.schema : {}
|
||||||
|
|
||||||
function saveView() {
|
function saveView() {
|
||||||
views.save(view)
|
views.save(view)
|
||||||
|
@ -90,29 +91,29 @@
|
||||||
|
|
||||||
function isMultipleChoice(field) {
|
function isMultipleChoice(field) {
|
||||||
return (
|
return (
|
||||||
viewTable.schema[field]?.constraints?.inclusion?.length ||
|
schema[field]?.constraints?.inclusion?.length ||
|
||||||
viewTable.schema[field]?.type === "boolean"
|
schema[field]?.type === "boolean"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function fieldOptions(field) {
|
function fieldOptions(field) {
|
||||||
return viewTable.schema[field]?.type === "options"
|
return schema[field]?.type === "options"
|
||||||
? viewTable.schema[field]?.constraints.inclusion
|
? schema[field]?.constraints.inclusion
|
||||||
: [true, false]
|
: [true, false]
|
||||||
}
|
}
|
||||||
|
|
||||||
function isDate(field) {
|
function isDate(field) {
|
||||||
return viewTable.schema[field]?.type === "datetime"
|
return schema[field]?.type === "datetime"
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNumber(field) {
|
function isNumber(field) {
|
||||||
return viewTable.schema[field]?.type === "number"
|
return schema[field]?.type === "number"
|
||||||
}
|
}
|
||||||
|
|
||||||
const fieldChanged = filter => ev => {
|
const fieldChanged = filter => ev => {
|
||||||
// Reset if type changed
|
// Reset if type changed
|
||||||
const oldType = viewTable.schema[filter.key]?.type
|
const oldType = schema[filter.key]?.type
|
||||||
const newType = viewTable.schema[ev.detail]?.type
|
const newType = schema[ev.detail]?.type
|
||||||
if (filter.key && ev.detail && oldType !== newType) {
|
if (filter.key && ev.detail && oldType !== newType) {
|
||||||
filter.value = ""
|
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
|
// Show updated permissions in UI: REMOVE
|
||||||
permissions = await permissionsStore.forResource(resourceId)
|
permissions = await permissionsStore.forResource(resourceId)
|
||||||
notifications.success("Updated permissions.")
|
notifications.success("Updated permissions.")
|
||||||
// TODO: update permissions
|
|
||||||
// permissions[]
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select } from "@budibase/bbui"
|
import { Select, InlineAlert, notifications } from "@budibase/bbui"
|
||||||
import { notifications } from "@budibase/bbui"
|
|
||||||
import { FIELDS } from "constants/backend"
|
import { FIELDS } from "constants/backend"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
|
|
||||||
|
@ -12,11 +11,13 @@
|
||||||
valid: true,
|
valid: true,
|
||||||
schema: {},
|
schema: {},
|
||||||
}
|
}
|
||||||
|
export let existingTableId
|
||||||
|
|
||||||
let csvString
|
let csvString = undefined
|
||||||
let primaryDisplay
|
let primaryDisplay = undefined
|
||||||
let schema = {}
|
let schema = {}
|
||||||
let fields = []
|
let fields = []
|
||||||
|
let hasValidated = false
|
||||||
|
|
||||||
$: valid = !schema || fields.every(column => schema[column].success)
|
$: valid = !schema || fields.every(column => schema[column].success)
|
||||||
$: dataImport = {
|
$: dataImport = {
|
||||||
|
@ -25,6 +26,9 @@
|
||||||
csvString,
|
csvString,
|
||||||
primaryDisplay,
|
primaryDisplay,
|
||||||
}
|
}
|
||||||
|
$: noFieldsError = existingTableId
|
||||||
|
? "No columns in CSV match existing table schema"
|
||||||
|
: "Could not find any columns to import"
|
||||||
|
|
||||||
function buildTableSchema(schema) {
|
function buildTableSchema(schema) {
|
||||||
const tableSchema = {}
|
const tableSchema = {}
|
||||||
|
@ -46,6 +50,7 @@
|
||||||
const response = await api.post("/api/tables/csv/validate", {
|
const response = await api.post("/api/tables/csv/validate", {
|
||||||
csvString,
|
csvString,
|
||||||
schema: schema || {},
|
schema: schema || {},
|
||||||
|
tableId: existingTableId,
|
||||||
})
|
})
|
||||||
|
|
||||||
const parseResult = await response.json()
|
const parseResult = await response.json()
|
||||||
|
@ -63,6 +68,7 @@
|
||||||
notifications.error("CSV Invalid, please try another CSV file")
|
notifications.error("CSV Invalid, please try another CSV file")
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
hasValidated = true
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleFile(evt) {
|
async function handleFile(evt) {
|
||||||
|
@ -138,6 +144,7 @@
|
||||||
placeholder={null}
|
placeholder={null}
|
||||||
getOptionLabel={option => option.label}
|
getOptionLabel={option => option.label}
|
||||||
getOptionValue={option => option.value}
|
getOptionValue={option => option.value}
|
||||||
|
disabled={!!existingTableId}
|
||||||
/>
|
/>
|
||||||
<span class="field-status" class:error={!schema[columnName].success}>
|
<span class="field-status" class:error={!schema[columnName].success}>
|
||||||
{schema[columnName].success ? "Success" : "Failure"}
|
{schema[columnName].success ? "Success" : "Failure"}
|
||||||
|
@ -149,15 +156,22 @@
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{#if !existingTableId}
|
||||||
|
<div class="display-column">
|
||||||
{#if fields.length}
|
<Select
|
||||||
<div class="display-column">
|
label="Display Column"
|
||||||
<Select
|
bind:value={primaryDisplay}
|
||||||
label="Display Column"
|
options={fields}
|
||||||
bind:value={primaryDisplay}
|
sort
|
||||||
options={fields}
|
/>
|
||||||
sort
|
</div>
|
||||||
|
{/if}
|
||||||
|
{:else if hasValidated}
|
||||||
|
<div>
|
||||||
|
<InlineAlert
|
||||||
|
header="Invalid CSV"
|
||||||
|
bind:message={noFieldsError}
|
||||||
|
type="error"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
export let onOk = undefined
|
export let onOk = undefined
|
||||||
export let onCancel = undefined
|
export let onCancel = undefined
|
||||||
export let warning = true
|
export let warning = true
|
||||||
|
export let disabled
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
|
|
||||||
|
@ -26,6 +27,7 @@
|
||||||
confirmText={okText}
|
confirmText={okText}
|
||||||
{cancelText}
|
{cancelText}
|
||||||
{warning}
|
{warning}
|
||||||
|
{disabled}
|
||||||
>
|
>
|
||||||
<Body size="S">
|
<Body size="S">
|
||||||
{body}
|
{body}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let options
|
export let options
|
||||||
export let allowJS = true
|
export let allowJS = true
|
||||||
|
export let appendBindingsAsOptions = true
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
let bindingDrawer
|
let bindingDrawer
|
||||||
|
@ -24,15 +25,30 @@
|
||||||
$: readableValue = runtimeToReadableBinding(bindings, value)
|
$: readableValue = runtimeToReadableBinding(bindings, value)
|
||||||
$: tempValue = readableValue
|
$: tempValue = readableValue
|
||||||
$: isJS = isJSBinding(value)
|
$: isJS = isJSBinding(value)
|
||||||
|
$: allOptions = buildOptions(options, bindings, appendBindingsAsOptions)
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
onChange(tempValue)
|
onChange(tempValue)
|
||||||
bindingDrawer.hide()
|
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))
|
dispatch("change", readableToRuntimeBinding(bindings, value))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const buildOptions = (options, bindings, appendBindingsAsOptions) => {
|
||||||
|
if (!appendBindingsAsOptions) {
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
.concat(options || [])
|
||||||
|
.concat(bindings?.map(binding => binding.readableBinding) || [])
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="control">
|
<div class="control">
|
||||||
|
@ -40,12 +56,17 @@
|
||||||
{label}
|
{label}
|
||||||
{disabled}
|
{disabled}
|
||||||
value={isJS ? "(JavaScript function)" : readableValue}
|
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}
|
{placeholder}
|
||||||
{options}
|
options={allOptions}
|
||||||
/>
|
/>
|
||||||
{#if !disabled}
|
{#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" />
|
<Icon size="S" name="FlashOn" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
<script>
|
<script>
|
||||||
import { Icon, Modal, notifications, ModalContent } from "@budibase/bbui"
|
import {
|
||||||
|
Icon,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
notifications,
|
||||||
|
ModalContent,
|
||||||
|
} from "@budibase/bbui"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
|
|
||||||
let revertModal
|
let revertModal
|
||||||
|
let appName
|
||||||
|
|
||||||
$: appId = $store.appId
|
$: appId = $store.appId
|
||||||
|
|
||||||
|
@ -33,10 +40,17 @@
|
||||||
|
|
||||||
<Icon name="Revert" hoverable on:click={revertModal.show} />
|
<Icon name="Revert" hoverable on:click={revertModal.show} />
|
||||||
<Modal bind:this={revertModal}>
|
<Modal bind:this={revertModal}>
|
||||||
<ModalContent title="Revert Changes" confirmText="Revert" onConfirm={revert}>
|
<ModalContent
|
||||||
|
title="Revert Changes"
|
||||||
|
confirmText="Revert"
|
||||||
|
onConfirm={revert}
|
||||||
|
disabled={appName !== $store.name}
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
>The changes you have made will be deleted and the application reverted
|
>The changes you have made will be deleted and the application reverted
|
||||||
back to its production state.</span
|
back to its production state.</span
|
||||||
>
|
>
|
||||||
|
<span>Please enter your app name to continue.</span>
|
||||||
|
<Input bind:value={appName} />
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -32,6 +32,16 @@
|
||||||
.component("@budibase/standard-components/screenslot")
|
.component("@budibase/standard-components/screenslot")
|
||||||
.instanceName("Content Placeholder")
|
.instanceName("Content Placeholder")
|
||||||
.json()
|
.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
|
// Construct iframe template
|
||||||
$: template = iframeTemplate.replace(
|
$: template = iframeTemplate.replace(
|
||||||
|
@ -59,6 +69,7 @@
|
||||||
theme: $store.theme,
|
theme: $store.theme,
|
||||||
customTheme: $store.customTheme,
|
customTheme: $store.customTheme,
|
||||||
previewDevice: $store.previewDevice,
|
previewDevice: $store.previewDevice,
|
||||||
|
messagePassing: $store.clientFeatures.messagePassing
|
||||||
}
|
}
|
||||||
|
|
||||||
// Saving pages and screens to the DB causes them to have _revs.
|
// Saving pages and screens to the DB causes them to have _revs.
|
||||||
|
@ -80,11 +91,14 @@
|
||||||
// Refresh the preview when required
|
// Refresh the preview when required
|
||||||
$: refreshContent(strippedJson)
|
$: refreshContent(strippedJson)
|
||||||
|
|
||||||
onMount(() => {
|
function receiveMessage(message) {
|
||||||
// Initialise the app when mounted
|
const handlers = {
|
||||||
iframe.contentWindow.addEventListener(
|
[MessageTypes.READY]: () => {
|
||||||
"ready",
|
// Initialise the app when mounted
|
||||||
() => {
|
if ($store.clientFeatures.messagePassing) {
|
||||||
|
if (!loading) return
|
||||||
|
}
|
||||||
|
|
||||||
// Display preview immediately if the intelligent loading feature
|
// Display preview immediately if the intelligent loading feature
|
||||||
// is not supported
|
// is not supported
|
||||||
if (!$store.clientFeatures.intelligentLoading) {
|
if (!$store.clientFeatures.intelligentLoading) {
|
||||||
|
@ -92,34 +106,48 @@
|
||||||
}
|
}
|
||||||
refreshContent(strippedJson)
|
refreshContent(strippedJson)
|
||||||
},
|
},
|
||||||
{ once: true }
|
[MessageTypes.ERROR]: event => {
|
||||||
)
|
// Catch any app errors
|
||||||
|
|
||||||
// Catch any app errors
|
|
||||||
iframe.contentWindow.addEventListener(
|
|
||||||
"error",
|
|
||||||
event => {
|
|
||||||
loading = false
|
loading = false
|
||||||
error = event.detail || "An unknown error occurred"
|
error = event.error || "An unknown error occurred"
|
||||||
},
|
},
|
||||||
{ once: true }
|
[MessageTypes.KEYDOWN]: handleKeydownEvent
|
||||||
)
|
}
|
||||||
|
|
||||||
// Add listener for events sent by client library in preview
|
const messageHandler = handlers[message.data.type] || handleBudibaseEvent
|
||||||
iframe.contentWindow.addEventListener("bb-event", handleBudibaseEvent)
|
messageHandler(message)
|
||||||
iframe.contentWindow.addEventListener("keydown", handleKeydownEvent)
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
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 })
|
||||||
|
// 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
|
// Remove all iframe event listeners on component destroy
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
if (iframe.contentWindow) {
|
if (iframe.contentWindow) {
|
||||||
iframe.contentWindow.removeEventListener("bb-event", handleBudibaseEvent)
|
window.removeEventListener("message", receiveMessage)
|
||||||
iframe.contentWindow.removeEventListener("keydown", handleKeydownEvent)
|
if (!$store.clientFeatures.messagePassing) {
|
||||||
|
// Legacy - remove in later versions of BB
|
||||||
|
iframe.contentWindow.removeEventListener("bb-event", handleBudibaseEvent)
|
||||||
|
iframe.contentWindow.removeEventListener("keydown", handleKeydownEvent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleBudibaseEvent = event => {
|
const handleBudibaseEvent = event => {
|
||||||
const { type, data } = event.detail
|
const { type, data } = event.data || event.detail
|
||||||
if (type === "select-component" && data.id) {
|
if (type === "select-component" && data.id) {
|
||||||
store.actions.components.select({ _id: data.id })
|
store.actions.components.select({ _id: data.id })
|
||||||
} else if (type === "update-prop") {
|
} else if (type === "update-prop") {
|
||||||
|
@ -151,13 +179,14 @@
|
||||||
store.actions.components.paste(destination, data.mode)
|
store.actions.components.paste(destination, data.mode)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warning(`Client sent unknown event type: ${type}`)
|
console.warn(`Client sent unknown event type: ${type}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleKeydownEvent = event => {
|
const handleKeydownEvent = event => {
|
||||||
|
const { key } = event.data || event
|
||||||
if (
|
if (
|
||||||
(event.key === "Delete" || event.key === "Backspace") &&
|
(key === "Delete" || key === "Backspace") &&
|
||||||
selectedComponentId &&
|
selectedComponentId &&
|
||||||
["input", "textarea"].indexOf(
|
["input", "textarea"].indexOf(
|
||||||
iframe.contentWindow.document.activeElement?.tagName.toLowerCase()
|
iframe.contentWindow.document.activeElement?.tagName.toLowerCase()
|
||||||
|
|
|
@ -1,4 +1,13 @@
|
||||||
[
|
[
|
||||||
|
{
|
||||||
|
"name": "Blocks",
|
||||||
|
"icon": "Article",
|
||||||
|
"children": [
|
||||||
|
"tableblock",
|
||||||
|
"cardsblock",
|
||||||
|
"repeaterblock"
|
||||||
|
]
|
||||||
|
},
|
||||||
"section",
|
"section",
|
||||||
"container",
|
"container",
|
||||||
"dataprovider",
|
"dataprovider",
|
||||||
|
|
|
@ -54,7 +54,7 @@ export default `
|
||||||
if (!parsed) {
|
if (!parsed) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract data from message
|
// Extract data from message
|
||||||
const {
|
const {
|
||||||
selectedComponentId,
|
selectedComponentId,
|
||||||
|
@ -84,17 +84,21 @@ export default `
|
||||||
if (window.loadBudibase) {
|
if (window.loadBudibase) {
|
||||||
window.loadBudibase()
|
window.loadBudibase()
|
||||||
document.documentElement.classList.add("loaded")
|
document.documentElement.classList.add("loaded")
|
||||||
window.dispatchEvent(new Event("iframe-loaded"))
|
window.parent.postMessage({ type: "iframe-loaded" })
|
||||||
} else {
|
} else {
|
||||||
throw "The client library couldn't be loaded"
|
throw "The client library couldn't be loaded"
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.dispatchEvent(new CustomEvent("error", { detail: error }))
|
window.parent.postMessage({ type: "error", error })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("message", receiveMessage)
|
window.addEventListener("message", receiveMessage)
|
||||||
window.dispatchEvent(new Event("ready"))
|
window.addEventListener("keydown", evt => {
|
||||||
|
window.parent.postMessage({ type: "keydown", key: event.key })
|
||||||
|
})
|
||||||
|
|
||||||
|
window.parent.postMessage({ type: "ready" })
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body/>
|
<body/>
|
||||||
|
|
|
@ -10,10 +10,11 @@
|
||||||
import { roles } from "stores/backend"
|
import { roles } from "stores/backend"
|
||||||
import ComponentNavigationTree from "components/design/NavigationPanel/ComponentNavigationTree/index.svelte"
|
import ComponentNavigationTree from "components/design/NavigationPanel/ComponentNavigationTree/index.svelte"
|
||||||
import Layout from "components/design/NavigationPanel/Layout.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 NewLayoutModal from "components/design/NavigationPanel/NewLayoutModal.svelte"
|
||||||
import { Icon, Modal, Select, Search, Tabs, Tab } from "@budibase/bbui"
|
import { Icon, Modal, Select, Search, Tabs, Tab } from "@budibase/bbui"
|
||||||
|
|
||||||
|
export let showModal
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
title: "Screens",
|
title: "Screens",
|
||||||
|
@ -85,9 +86,6 @@
|
||||||
<div class="nav-items-container">
|
<div class="nav-items-container">
|
||||||
<ComponentNavigationTree />
|
<ComponentNavigationTree />
|
||||||
</div>
|
</div>
|
||||||
<Modal bind:this={modal}>
|
|
||||||
<NewScreenModal />
|
|
||||||
</Modal>
|
|
||||||
</div>
|
</div>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab title="Layouts">
|
<Tab title="Layouts">
|
||||||
|
@ -102,7 +100,7 @@
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<div class="add-button">
|
<div class="add-button">
|
||||||
<Icon hoverable name="AddCircle" on:click={modal.show} />
|
<Icon hoverable name="AddCircle" on:click={showModal()} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,179 @@
|
||||||
|
<script>
|
||||||
|
import { ModalContent, Body, Detail } from "@budibase/bbui"
|
||||||
|
import { store, selectedAccessRole, allScreens } from "builderStore"
|
||||||
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
|
export let selectedScreens
|
||||||
|
export let screenName
|
||||||
|
export let url
|
||||||
|
export let chooseModal
|
||||||
|
|
||||||
|
let roleId = $selectedAccessRole || "BASIC"
|
||||||
|
|
||||||
|
let routeError
|
||||||
|
let selectedNav
|
||||||
|
let createdScreens = []
|
||||||
|
$: {
|
||||||
|
selectedScreens?.forEach(screen => {
|
||||||
|
createdScreens = [...createdScreens, screen.create()]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$: blankSelected = selectedScreens.length === 1
|
||||||
|
|
||||||
|
const save = async screens => {
|
||||||
|
for (let screen of screens) {
|
||||||
|
await saveScreens(screen)
|
||||||
|
}
|
||||||
|
|
||||||
|
let navLayout = cloneDeep(
|
||||||
|
$store.layouts.find(layout => layout._id === "layout_private_master")
|
||||||
|
)
|
||||||
|
navLayout.props.navigation = selectedNav
|
||||||
|
|
||||||
|
await store.actions.routing.fetch()
|
||||||
|
await store.actions.layouts.save(navLayout)
|
||||||
|
selectedScreens = []
|
||||||
|
screenName = ""
|
||||||
|
url = ""
|
||||||
|
}
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
selectedScreens = []
|
||||||
|
screenName = ""
|
||||||
|
url = ""
|
||||||
|
})
|
||||||
|
</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,115 @@
|
||||||
<script>
|
<script>
|
||||||
import { store, allScreens, selectedAccessRole } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import { tables } from "stores/backend"
|
import { tables } from "stores/backend"
|
||||||
import { roles } from "stores/backend"
|
import { ModalContent, Body, Detail, Layout, Icon } from "@budibase/bbui"
|
||||||
import { Input, Select, ModalContent, Toggle } from "@budibase/bbui"
|
|
||||||
import getTemplates from "builderStore/store/screenTemplates"
|
import getTemplates from "builderStore/store/screenTemplates"
|
||||||
import analytics, { Events } from "analytics"
|
|
||||||
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
|
||||||
|
|
||||||
const CONTAINER = "@budibase/standard-components/container"
|
export let selectedScreens = []
|
||||||
|
export let chooseModal
|
||||||
|
|
||||||
let name = ""
|
const blankScreen = "createFromScratch"
|
||||||
let routeError
|
|
||||||
let baseComponent = CONTAINER
|
$: blankSelected = selectedScreens?.length === 1
|
||||||
let templateIndex
|
$: autoSelected = selectedScreens?.length > 0 && !blankSelected
|
||||||
let draftScreen
|
|
||||||
let createLink = true
|
|
||||||
let roleId = $selectedAccessRole || "BASIC"
|
|
||||||
|
|
||||||
$: templates = getTemplates($store, $tables.list)
|
$: templates = getTemplates($store, $tables.list)
|
||||||
$: route = !route && $allScreens.length === 0 ? "*" : route
|
const toggleScreenSelection = table => {
|
||||||
$: {
|
if (selectedScreens.find(s => s.name.includes(table.name))) {
|
||||||
if (templates && templateIndex === undefined) {
|
selectedScreens = selectedScreens.filter(
|
||||||
templateIndex = 0
|
screen => !screen.name.includes(table.name)
|
||||||
templateChanged(0)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const templateChanged = newTemplateIndex => {
|
|
||||||
if (newTemplateIndex === undefined) return
|
|
||||||
draftScreen = templates[newTemplateIndex].create()
|
|
||||||
if (draftScreen.props._instanceName) {
|
|
||||||
name = draftScreen.props._instanceName
|
|
||||||
}
|
|
||||||
|
|
||||||
if (draftScreen.props._component) {
|
|
||||||
baseComponent = draftScreen.props._component
|
|
||||||
}
|
|
||||||
|
|
||||||
if (draftScreen.routing) {
|
|
||||||
route = draftScreen.routing.route
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const save = async () => {
|
|
||||||
if (!route) {
|
|
||||||
routeError = "URL is required"
|
|
||||||
} else {
|
} else {
|
||||||
if (routeExists(route, roleId)) {
|
templates = templates.filter(template =>
|
||||||
routeError = "This URL is already taken for this access role"
|
template.name.includes(table.name)
|
||||||
} else {
|
)
|
||||||
routeError = ""
|
selectedScreens = [...templates, ...selectedScreens]
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 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)
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ModalContent title="New Screen" confirmText="Create Screen" onConfirm={save}>
|
<ModalContent
|
||||||
<Select
|
title="Add screens"
|
||||||
label="Choose a Template"
|
confirmText="Add Screens"
|
||||||
bind:value={templateIndex}
|
cancelText="Cancel"
|
||||||
on:change={ev => templateChanged(ev.detail)}
|
onConfirm={() => (autoSelected ? chooseModal(2) : chooseModal(1))}
|
||||||
options={templates}
|
disabled={!selectedScreens.length}
|
||||||
placeholder={null}
|
size="L"
|
||||||
getOptionLabel={x => x.name}
|
>
|
||||||
getOptionValue={(x, idx) => idx}
|
<Body size="XS"
|
||||||
/>
|
>Please select the screens you would like to add to your application.
|
||||||
<Input label="Name" bind:value={name} />
|
Autogenerated screens come with CRUD functionality.</Body
|
||||||
<Input
|
>
|
||||||
label="Url"
|
|
||||||
error={routeError}
|
<Layout noPadding gap="S">
|
||||||
bind:value={route}
|
<Detail size="S">Blank screen</Detail>
|
||||||
on:change={routeChanged}
|
<div
|
||||||
/>
|
class="item"
|
||||||
<Select
|
class:selected={selectedScreens.find(x => x.id.includes(blankScreen))}
|
||||||
label="Access"
|
on:click={() =>
|
||||||
bind:value={roleId}
|
toggleScreenSelection(templates.find(t => t.id === blankScreen))}
|
||||||
options={$roles}
|
class:disabled={autoSelected}
|
||||||
getOptionLabel={x => x.name}
|
>
|
||||||
getOptionValue={x => x._id}
|
<div data-cy="blank-screen" class="content">
|
||||||
/>
|
<Body size="S">Blank</Body>
|
||||||
<Toggle text="Create link in navigation bar" bind:value={createLink} />
|
</div>
|
||||||
|
<div style="color: var(--spectrum-global-color-green-600); float: right">
|
||||||
|
{#if selectedScreens.find(x => x.id === blankScreen)}
|
||||||
|
<Icon size="S" name="CheckmarkCircleOutline" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Detail size="S">Autogenerated Screens</Detail>
|
||||||
|
|
||||||
|
{#each $tables.list.filter(table => table.type !== "external") as table}
|
||||||
|
<div
|
||||||
|
class:disabled={blankSelected}
|
||||||
|
class:selected={selectedScreens.find(x => x.name.includes(table.name))}
|
||||||
|
on:click={() => toggleScreenSelection(table)}
|
||||||
|
class="item"
|
||||||
|
>
|
||||||
|
<div class="content">
|
||||||
|
{table.name}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="color: var(--spectrum-global-color-green-600); float: right"
|
||||||
|
>
|
||||||
|
{#if selectedScreens.find(x => x.name.includes(table.name))}
|
||||||
|
<Icon size="S" name="CheckmarkCircleOutline" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</Layout>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.disabled {
|
||||||
|
opacity: 0.3;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
letter-spacing: 0px;
|
||||||
|
color: #2c2c2c;
|
||||||
|
}
|
||||||
|
.item {
|
||||||
|
cursor: pointer;
|
||||||
|
grid-gap: var(--spectrum-alias-grid-margin-xsmall);
|
||||||
|
padding: var(--spectrum-alias-item-padding-s);
|
||||||
|
background: var(--background);
|
||||||
|
transition: 0.3s all;
|
||||||
|
border: solid var(--spectrum-alias-border-color);
|
||||||
|
border-radius: 2px;
|
||||||
|
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,51 @@
|
||||||
|
<script>
|
||||||
|
import { ModalContent, Input } from "@budibase/bbui"
|
||||||
|
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
||||||
|
import { selectedAccessRole, allScreens } from "builderStore"
|
||||||
|
|
||||||
|
export let screenName
|
||||||
|
export let url
|
||||||
|
export let chooseModal
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ModalContent
|
||||||
|
size="M"
|
||||||
|
title={"Enter details"}
|
||||||
|
confirmText={"Continue"}
|
||||||
|
onCancel={() => chooseModal(0)}
|
||||||
|
onConfirm={() => chooseModal(2)}
|
||||||
|
cancelText={"Back"}
|
||||||
|
disabled={!screenName || !url || routeError}
|
||||||
|
>
|
||||||
|
<Input label="Name" bind:value={screenName} />
|
||||||
|
<Input
|
||||||
|
label="URL"
|
||||||
|
error={routeError}
|
||||||
|
bind:value={url}
|
||||||
|
on:change={routeChanged}
|
||||||
|
/>
|
||||||
|
</ModalContent>
|
|
@ -0,0 +1,48 @@
|
||||||
|
<script>
|
||||||
|
import NavigationSelectionModal from "components/design/NavigationPanel/NavigationSelectionModal.svelte"
|
||||||
|
import ScreenDetailsModal from "components/design/NavigationPanel/ScreenDetailsModal.svelte"
|
||||||
|
import NewScreenModal from "components/design/NavigationPanel/NewScreenModal.svelte"
|
||||||
|
import { Modal } from "@budibase/bbui"
|
||||||
|
|
||||||
|
let newScreenModal
|
||||||
|
let navigationSelectionModal
|
||||||
|
let screenDetailsModal
|
||||||
|
let screenName = ""
|
||||||
|
let url = ""
|
||||||
|
let selectedScreens = []
|
||||||
|
|
||||||
|
export const showModal = () => {
|
||||||
|
newScreenModal.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
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 bind:selectedScreens {chooseModal} />
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<Modal bind:this={screenDetailsModal}>
|
||||||
|
<ScreenDetailsModal bind:screenName bind:url {chooseModal} />
|
||||||
|
</Modal>
|
||||||
|
<Modal bind:this={navigationSelectionModal}>
|
||||||
|
<NavigationSelectionModal
|
||||||
|
bind:url
|
||||||
|
bind:screenName
|
||||||
|
bind:selectedScreens
|
||||||
|
{chooseModal}
|
||||||
|
/>
|
||||||
|
</Modal>
|
|
@ -12,6 +12,7 @@
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
export let assetInstance
|
export let assetInstance
|
||||||
export let bindings
|
export let bindings
|
||||||
|
export let componentBindings
|
||||||
|
|
||||||
const layoutDefinition = []
|
const layoutDefinition = []
|
||||||
const screenDefinition = [
|
const screenDefinition = [
|
||||||
|
@ -21,10 +22,24 @@
|
||||||
{ key: "layoutId", label: "Layout", control: LayoutSelect },
|
{ key: "layoutId", label: "Layout", control: LayoutSelect },
|
||||||
]
|
]
|
||||||
|
|
||||||
$: settings = componentDefinition?.settings ?? []
|
$: sections = getSections(componentDefinition)
|
||||||
$: isLayout = assetInstance && assetInstance.favicon
|
$: isLayout = assetInstance && assetInstance.favicon
|
||||||
$: assetDefinition = isLayout ? layoutDefinition : screenDefinition
|
$: 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 updateProp = store.actions.components.updateProp
|
||||||
|
|
||||||
const canRenderControl = setting => {
|
const canRenderControl = setting => {
|
||||||
|
@ -59,19 +74,18 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DetailSummary name="General" collapsible={false}>
|
{#each sections as section, idx (section.name)}
|
||||||
{#if !componentInstance._component.endsWith("/layout")}
|
<DetailSummary name={section.name} collapsible={false}>
|
||||||
<PropertyControl
|
{#if idx === 0 && !componentInstance._component.endsWith("/layout")}
|
||||||
bindable={false}
|
<PropertyControl
|
||||||
control={Input}
|
control={Input}
|
||||||
label="Name"
|
label="Name"
|
||||||
key="_instanceName"
|
key="_instanceName"
|
||||||
value={componentInstance._instanceName}
|
value={componentInstance._instanceName}
|
||||||
onChange={val => updateProp("_instanceName", val)}
|
onChange={val => updateProp("_instanceName", val)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#if settings && settings.length > 0}
|
{#each section.settings as setting (setting.key)}
|
||||||
{#each settings as setting (setting.key)}
|
|
||||||
{#if canRenderControl(setting)}
|
{#if canRenderControl(setting)}
|
||||||
<PropertyControl
|
<PropertyControl
|
||||||
type={setting.type}
|
type={setting.type}
|
||||||
|
@ -80,7 +94,7 @@
|
||||||
key={setting.key}
|
key={setting.key}
|
||||||
value={componentInstance[setting.key] ??
|
value={componentInstance[setting.key] ??
|
||||||
componentInstance[setting.key]?.defaultValue}
|
componentInstance[setting.key]?.defaultValue}
|
||||||
{componentInstance}
|
nested={setting.nested}
|
||||||
onChange={val => updateProp(setting.key, val)}
|
onChange={val => updateProp(setting.key, val)}
|
||||||
props={{
|
props={{
|
||||||
options: setting.options || [],
|
options: setting.options || [],
|
||||||
|
@ -89,20 +103,22 @@
|
||||||
max: setting.max || null,
|
max: setting.max || null,
|
||||||
}}
|
}}
|
||||||
{bindings}
|
{bindings}
|
||||||
|
{componentBindings}
|
||||||
|
{componentInstance}
|
||||||
{componentDefinition}
|
{componentDefinition}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{#if idx === 0 && componentDefinition?.component?.endsWith("/fieldgroup")}
|
||||||
{#if componentDefinition?.component?.endsWith("/fieldgroup")}
|
<ResetFieldsButton {componentInstance} />
|
||||||
<ResetFieldsButton {componentInstance} />
|
{/if}
|
||||||
{/if}
|
{#if section?.info}
|
||||||
{#if componentDefinition?.info}
|
<div class="text">
|
||||||
<div class="text">
|
{@html section.info}
|
||||||
{@html componentDefinition?.info}
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
{/if}
|
</DetailSummary>
|
||||||
</DetailSummary>
|
{/each}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.text {
|
.text {
|
||||||
|
|
|
@ -6,13 +6,20 @@
|
||||||
import DesignSection from "./DesignSection.svelte"
|
import DesignSection from "./DesignSection.svelte"
|
||||||
import CustomStylesSection from "./CustomStylesSection.svelte"
|
import CustomStylesSection from "./CustomStylesSection.svelte"
|
||||||
import ConditionalUISection from "./ConditionalUISection.svelte"
|
import ConditionalUISection from "./ConditionalUISection.svelte"
|
||||||
import { getBindableProperties } from "builderStore/dataBinding"
|
import {
|
||||||
|
getBindableProperties,
|
||||||
|
getComponentBindableProperties,
|
||||||
|
} from "builderStore/dataBinding"
|
||||||
|
|
||||||
$: componentInstance = $selectedComponent
|
$: componentInstance = $selectedComponent
|
||||||
$: componentDefinition = store.actions.components.getDefinition(
|
$: componentDefinition = store.actions.components.getDefinition(
|
||||||
$selectedComponent?._component
|
$selectedComponent?._component
|
||||||
)
|
)
|
||||||
$: bindings = getBindableProperties($currentAsset, $store.selectedComponentId)
|
$: bindings = getBindableProperties($currentAsset, $store.selectedComponentId)
|
||||||
|
$: componentBindings = getComponentBindableProperties(
|
||||||
|
$currentAsset,
|
||||||
|
$store.selectedComponentId
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Tabs selected="Settings" noPadding>
|
<Tabs selected="Settings" noPadding>
|
||||||
|
@ -28,6 +35,7 @@
|
||||||
{componentInstance}
|
{componentInstance}
|
||||||
{componentDefinition}
|
{componentDefinition}
|
||||||
{bindings}
|
{bindings}
|
||||||
|
{componentBindings}
|
||||||
/>
|
/>
|
||||||
<DesignSection {componentInstance} {componentDefinition} {bindings} />
|
<DesignSection {componentInstance} {componentDefinition} {bindings} />
|
||||||
<CustomStylesSection
|
<CustomStylesSection
|
||||||
|
|
|
@ -13,9 +13,10 @@
|
||||||
import { generate } from "shortid"
|
import { generate } from "shortid"
|
||||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
import { OperatorOptions, getValidOperatorsForType } from "constants/lucene"
|
import { OperatorOptions, getValidOperatorsForType } from "constants/lucene"
|
||||||
import { selectedComponent, store } from "builderStore"
|
import { selectedComponent } from "builderStore"
|
||||||
import { getComponentForSettingType } from "./componentSettings"
|
import { getComponentForSettingType } from "./componentSettings"
|
||||||
import PropertyControl from "./PropertyControl.svelte"
|
import PropertyControl from "./PropertyControl.svelte"
|
||||||
|
import { getComponentSettings } from "builderStore/storeUtils"
|
||||||
|
|
||||||
export let conditions = []
|
export let conditions = []
|
||||||
export let bindings = []
|
export let bindings = []
|
||||||
|
@ -55,15 +56,11 @@
|
||||||
]
|
]
|
||||||
|
|
||||||
let dragDisabled = true
|
let dragDisabled = true
|
||||||
$: definition = store.actions.components.getDefinition(
|
$: settings = getComponentSettings($selectedComponent?._component)
|
||||||
$selectedComponent?._component
|
$: settingOptions = settings.map(setting => ({
|
||||||
)
|
label: setting.label,
|
||||||
$: settings = (definition?.settings ?? []).map(setting => {
|
value: setting.key,
|
||||||
return {
|
}))
|
||||||
label: setting.label,
|
|
||||||
value: setting.key,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
$: conditions.forEach(link => {
|
$: conditions.forEach(link => {
|
||||||
if (!link.id) {
|
if (!link.id) {
|
||||||
link.id = generate()
|
link.id = generate()
|
||||||
|
@ -71,9 +68,7 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
const getSettingDefinition = key => {
|
const getSettingDefinition = key => {
|
||||||
return definition?.settings?.find(setting => {
|
return settings.find(setting => setting.key === key)
|
||||||
return setting.key === key
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getComponentForSetting = key => {
|
const getComponentForSetting = key => {
|
||||||
|
@ -175,7 +170,10 @@
|
||||||
bind:value={condition.action}
|
bind:value={condition.action}
|
||||||
/>
|
/>
|
||||||
{#if condition.action === "update"}
|
{#if condition.action === "update"}
|
||||||
<Select options={settings} bind:value={condition.setting} />
|
<Select
|
||||||
|
options={settingOptions}
|
||||||
|
bind:value={condition.setting}
|
||||||
|
/>
|
||||||
<div>TO</div>
|
<div>TO</div>
|
||||||
{#if getSettingDefinition(condition.setting)}
|
{#if getSettingDefinition(condition.setting)}
|
||||||
<PropertyControl
|
<PropertyControl
|
||||||
|
|
|
@ -11,10 +11,7 @@
|
||||||
const getValue = component => `{{ literal ${makePropSafe(component._id)} }}`
|
const getValue = component => `{{ literal ${makePropSafe(component._id)} }}`
|
||||||
|
|
||||||
$: path = findComponentPath($currentAsset.props, $store.selectedComponentId)
|
$: path = findComponentPath($currentAsset.props, $store.selectedComponentId)
|
||||||
$: providers = path.filter(
|
$: providers = path.filter(c => c._component?.endsWith("/dataprovider"))
|
||||||
component =>
|
|
||||||
component._component === "@budibase/standard-components/dataprovider"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Set initial value to closest data provider
|
// Set initial value to closest data provider
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
|
|
@ -20,16 +20,18 @@
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
|
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
|
||||||
import IntegrationQueryEditor from "components/integration/index.svelte"
|
import IntegrationQueryEditor from "components/integration/index.svelte"
|
||||||
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
let anchorRight, dropdownRight
|
|
||||||
let drawer
|
|
||||||
|
|
||||||
export let value = {}
|
export let value = {}
|
||||||
export let otherSources
|
export let otherSources
|
||||||
export let showAllQueries
|
export let showAllQueries
|
||||||
export let bindings = []
|
export let bindings = []
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
const arrayTypes = ["attachment", "array"]
|
||||||
|
let anchorRight, dropdownRight
|
||||||
|
let drawer
|
||||||
|
|
||||||
$: text = value?.label ?? "Choose an option"
|
$: text = value?.label ?? "Choose an option"
|
||||||
$: tables = $tablesStore.list.map(m => ({
|
$: tables = $tablesStore.list.map(m => ({
|
||||||
label: m.name,
|
label: m.name,
|
||||||
|
@ -54,8 +56,6 @@
|
||||||
name: query.name,
|
name: query.name,
|
||||||
tableId: query._id,
|
tableId: query._id,
|
||||||
...query,
|
...query,
|
||||||
schema: query.schema,
|
|
||||||
parameters: query.parameters,
|
|
||||||
type: "query",
|
type: "query",
|
||||||
}))
|
}))
|
||||||
$: dataProviders = getDataProviderComponents(
|
$: dataProviders = getDataProviderComponents(
|
||||||
|
@ -65,29 +65,40 @@
|
||||||
label: provider._instanceName,
|
label: provider._instanceName,
|
||||||
name: provider._instanceName,
|
name: provider._instanceName,
|
||||||
providerId: provider._id,
|
providerId: provider._id,
|
||||||
value: `{{ literal [${provider._id}] }}`,
|
value: `{{ literal ${safe(provider._id)} }}`,
|
||||||
type: "provider",
|
type: "provider",
|
||||||
schema: provider.schema,
|
|
||||||
}))
|
|
||||||
$: queryBindableProperties = bindings.map(property => ({
|
|
||||||
...property,
|
|
||||||
category: property.type === "instance" ? "Component" : "Table",
|
|
||||||
label: property.readableBinding,
|
|
||||||
path: property.readableBinding,
|
|
||||||
}))
|
}))
|
||||||
$: links = bindings
|
$: links = bindings
|
||||||
.filter(x => x.fieldSchema?.type === "link")
|
.filter(x => x.fieldSchema?.type === "link")
|
||||||
.map(property => {
|
.map(binding => {
|
||||||
|
const { providerId, readableBinding, fieldSchema } = binding || {}
|
||||||
|
const { name, tableId } = fieldSchema || {}
|
||||||
|
const safeProviderId = safe(providerId)
|
||||||
return {
|
return {
|
||||||
providerId: property.providerId,
|
providerId,
|
||||||
label: property.readableBinding,
|
label: readableBinding,
|
||||||
fieldName: property.fieldSchema.name,
|
fieldName: name,
|
||||||
tableId: property.fieldSchema.tableId,
|
tableId,
|
||||||
type: "link",
|
type: "link",
|
||||||
// These properties will be enriched by the client library and provide
|
// These properties will be enriched by the client library and provide
|
||||||
// details of the parent row of the relationship field, from context
|
// details of the parent row of the relationship field, from context
|
||||||
rowId: `{{ ${property.providerId}._id }}`,
|
rowId: `{{ ${safeProviderId}.${safe("_id")} }}`,
|
||||||
rowTableId: `{{ ${property.providerId}.tableId }}`,
|
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
|
).source
|
||||||
return $integrations[source].query[query.queryVerb]
|
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>
|
</script>
|
||||||
|
|
||||||
<div class="container" bind:this={anchorRight}>
|
<div class="container" bind:this={anchorRight}>
|
||||||
|
@ -127,11 +146,10 @@
|
||||||
</Button>
|
</Button>
|
||||||
<DrawerContent slot="body">
|
<DrawerContent slot="body">
|
||||||
<Layout noPadding>
|
<Layout noPadding>
|
||||||
{#if value.parameters.length > 0}
|
{#if getQueryParams(value._id).length > 0}
|
||||||
<ParameterBuilder
|
<ParameterBuilder
|
||||||
bind:customParams={value.queryParams}
|
bind:customParams={value.queryParams}
|
||||||
parameters={queries.find(query => query._id === value._id)
|
parameters={getQueryParams(value)}
|
||||||
.parameters}
|
|
||||||
{bindings}
|
{bindings}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -139,9 +157,7 @@
|
||||||
height={200}
|
height={200}
|
||||||
query={value}
|
query={value}
|
||||||
schema={fetchQueryDefinition(value)}
|
schema={fetchQueryDefinition(value)}
|
||||||
datasource={$datasources.list.find(
|
datasource={getQueryDatasource(value)}
|
||||||
ds => ds._id === value.datasourceId
|
|
||||||
)}
|
|
||||||
editable={false}
|
editable={false}
|
||||||
/>
|
/>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -159,52 +175,71 @@
|
||||||
<li on:click={() => handleSelected(table)}>{table.label}</li>
|
<li on:click={() => handleSelected(table)}>{table.label}</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
<Divider size="S" />
|
{#if views?.length}
|
||||||
<div class="title">
|
<Divider size="S" />
|
||||||
<Heading size="XS">Views</Heading>
|
<div class="title">
|
||||||
</div>
|
<Heading size="XS">Views</Heading>
|
||||||
<ul>
|
</div>
|
||||||
{#each views as view}
|
<ul>
|
||||||
<li on:click={() => handleSelected(view)}>{view.label}</li>
|
{#each views as view}
|
||||||
{/each}
|
<li on:click={() => handleSelected(view)}>{view.label}</li>
|
||||||
</ul>
|
{/each}
|
||||||
<Divider size="S" />
|
</ul>
|
||||||
<div class="title">
|
{/if}
|
||||||
<Heading size="XS">Relationships</Heading>
|
{#if queries?.length}
|
||||||
</div>
|
<Divider size="S" />
|
||||||
<ul>
|
<div class="title">
|
||||||
{#each links as link}
|
<Heading size="XS">Queries</Heading>
|
||||||
<li on:click={() => handleSelected(link)}>{link.label}</li>
|
</div>
|
||||||
{/each}
|
<ul>
|
||||||
</ul>
|
{#each queries as query}
|
||||||
<Divider size="S" />
|
<li
|
||||||
<div class="title">
|
class:selected={value === query}
|
||||||
<Heading size="XS">Queries</Heading>
|
on:click={() => handleSelected(query)}
|
||||||
</div>
|
>
|
||||||
<ul>
|
{query.label}
|
||||||
{#each queries as query}
|
</li>
|
||||||
<li
|
{/each}
|
||||||
class:selected={value === query}
|
</ul>
|
||||||
on:click={() => handleSelected(query)}
|
{/if}
|
||||||
>
|
{#if links?.length}
|
||||||
{query.label}
|
<Divider size="S" />
|
||||||
</li>
|
<div class="title">
|
||||||
{/each}
|
<Heading size="XS">Relationships</Heading>
|
||||||
</ul>
|
</div>
|
||||||
<Divider size="S" />
|
<ul>
|
||||||
<div class="title">
|
{#each links as link}
|
||||||
<Heading size="XS">Data Providers</Heading>
|
<li on:click={() => handleSelected(link)}>{link.label}</li>
|
||||||
</div>
|
{/each}
|
||||||
<ul>
|
</ul>
|
||||||
{#each dataProviders as provider}
|
{/if}
|
||||||
<li
|
{#if fields?.length}
|
||||||
class:selected={value === provider}
|
<Divider size="S" />
|
||||||
on:click={() => handleSelected(provider)}
|
<div class="title">
|
||||||
>
|
<Heading size="XS">Fields</Heading>
|
||||||
{provider.label}
|
</div>
|
||||||
</li>
|
<ul>
|
||||||
{/each}
|
{#each fields as field}
|
||||||
</ul>
|
<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}
|
{#if otherSources?.length}
|
||||||
<Divider size="S" />
|
<Divider size="S" />
|
||||||
<div class="title">
|
<div class="title">
|
||||||
|
|
|
@ -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
|
|
||||||
/>
|
|
|
@ -1,14 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { Button, Icon, Drawer, Label } from "@budibase/bbui"
|
import { Label } from "@budibase/bbui"
|
||||||
import {
|
import {
|
||||||
readableToRuntimeBinding,
|
readableToRuntimeBinding,
|
||||||
runtimeToReadableBinding,
|
runtimeToReadableBinding,
|
||||||
} from "builderStore/dataBinding"
|
} from "builderStore/dataBinding"
|
||||||
import BindingPanel from "components/common/bindings/BindingPanel.svelte"
|
|
||||||
import { capitalise } from "helpers"
|
|
||||||
|
|
||||||
export let label = ""
|
export let label = ""
|
||||||
export let bindable = true
|
|
||||||
export let componentInstance = {}
|
export let componentInstance = {}
|
||||||
export let control = null
|
export let control = null
|
||||||
export let key = ""
|
export let key = ""
|
||||||
|
@ -17,18 +14,19 @@
|
||||||
export let props = {}
|
export let props = {}
|
||||||
export let onChange = () => {}
|
export let onChange = () => {}
|
||||||
export let bindings = []
|
export let bindings = []
|
||||||
|
export let componentBindings = []
|
||||||
|
export let nested = false
|
||||||
|
|
||||||
let bindingDrawer
|
$: allBindings = getAllBindings(bindings, componentBindings, nested)
|
||||||
let anchor
|
$: safeValue = getSafeValue(value, props.defaultValue, allBindings)
|
||||||
let valid
|
|
||||||
|
|
||||||
$: safeValue = getSafeValue(value, props.defaultValue, bindings)
|
|
||||||
$: tempValue = safeValue
|
$: tempValue = safeValue
|
||||||
$: replaceBindings = val => readableToRuntimeBinding(bindings, val)
|
$: replaceBindings = val => readableToRuntimeBinding(allBindings, val)
|
||||||
|
|
||||||
const handleClose = () => {
|
const getAllBindings = (bindings, componentBindings, nested) => {
|
||||||
handleChange(tempValue)
|
if (!nested) {
|
||||||
bindingDrawer.hide()
|
return bindings
|
||||||
|
}
|
||||||
|
return [...(componentBindings || []), ...(bindings || [])]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle a value change of any type
|
// Handle a value change of any type
|
||||||
|
@ -64,7 +62,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="property-control" bind:this={anchor} data-cy={`setting-${key}`}>
|
<div class="property-control" data-cy={`setting-${key}`}>
|
||||||
{#if type !== "boolean" && label}
|
{#if type !== "boolean" && label}
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<Label>{label}</Label>
|
<Label>{label}</Label>
|
||||||
|
@ -78,37 +76,12 @@
|
||||||
updateOnChange={false}
|
updateOnChange={false}
|
||||||
on:change={handleChange}
|
on:change={handleChange}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
{bindings}
|
bindings={allBindings}
|
||||||
name={key}
|
name={key}
|
||||||
text={label}
|
text={label}
|
||||||
{type}
|
{type}
|
||||||
{...props}
|
{...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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -120,41 +93,10 @@
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
text-transform: capitalize;
|
|
||||||
padding-bottom: var(--spectrum-global-dimension-size-65);
|
padding-bottom: var(--spectrum-global-dimension-size-65);
|
||||||
}
|
}
|
||||||
|
|
||||||
.control {
|
.control {
|
||||||
position: relative;
|
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>
|
</style>
|
||||||
|
|
|
@ -10,4 +10,10 @@
|
||||||
.filter(x => x != null)
|
.filter(x => x != null)
|
||||||
</script>
|
</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 OptionsEditor from "./OptionsEditor/OptionsEditor.svelte"
|
||||||
import FormFieldSelect from "./FormFieldSelect.svelte"
|
import FormFieldSelect from "./FormFieldSelect.svelte"
|
||||||
import ValidationEditor from "./ValidationEditor/ValidationEditor.svelte"
|
import ValidationEditor from "./ValidationEditor/ValidationEditor.svelte"
|
||||||
import Input from "./Input.svelte"
|
import DrawerBindableCombobox from "components/common/bindings/DrawerBindableCombobox.svelte"
|
||||||
|
|
||||||
const componentMap = {
|
const componentMap = {
|
||||||
text: Input,
|
text: DrawerBindableCombobox,
|
||||||
select: Select,
|
select: Select,
|
||||||
dataSource: DataSourceSelect,
|
dataSource: DataSourceSelect,
|
||||||
dataProvider: DataProviderSelect,
|
dataProvider: DataProviderSelect,
|
||||||
|
|
|
@ -54,7 +54,6 @@
|
||||||
<DetailSummary name="Screen" collapsible={false}>
|
<DetailSummary name="Screen" collapsible={false}>
|
||||||
{#each screenSettings as def (`${componentInstance._id}-${def.key}`)}
|
{#each screenSettings as def (`${componentInstance._id}-${def.key}`)}
|
||||||
<PropertyControl
|
<PropertyControl
|
||||||
bindable={false}
|
|
||||||
control={def.control}
|
control={def.control}
|
||||||
label={def.label}
|
label={def.label}
|
||||||
key={def.key}
|
key={def.key}
|
||||||
|
|
|
@ -30,7 +30,6 @@
|
||||||
{#each properties as prop (`${componentInstance._id}-${prop.key}-${prop.label}`)}
|
{#each properties as prop (`${componentInstance._id}-${prop.key}-${prop.label}`)}
|
||||||
<div style="grid-column: {prop.column || 'auto'}">
|
<div style="grid-column: {prop.column || 'auto'}">
|
||||||
<PropertyControl
|
<PropertyControl
|
||||||
bindable={false}
|
|
||||||
label={`${prop.label}${hasPropChanged(style, prop) ? " *" : ""}`}
|
label={`${prop.label}${hasPropChanged(style, prop) ? " *" : ""}`}
|
||||||
control={prop.control}
|
control={prop.control}
|
||||||
key={prop.key}
|
key={prop.key}
|
||||||
|
|
|
@ -9,7 +9,6 @@ export const margin = {
|
||||||
label: "Top",
|
label: "Top",
|
||||||
key: "margin-top",
|
key: "margin-top",
|
||||||
control: Select,
|
control: Select,
|
||||||
bindable: false,
|
|
||||||
placeholder: "None",
|
placeholder: "None",
|
||||||
options: [
|
options: [
|
||||||
{ label: "4px", value: "4px" },
|
{ label: "4px", value: "4px" },
|
||||||
|
@ -30,7 +29,6 @@ export const margin = {
|
||||||
label: "Right",
|
label: "Right",
|
||||||
key: "margin-right",
|
key: "margin-right",
|
||||||
control: Select,
|
control: Select,
|
||||||
bindable: false,
|
|
||||||
placeholder: "None",
|
placeholder: "None",
|
||||||
options: [
|
options: [
|
||||||
{ label: "4px", value: "4px" },
|
{ label: "4px", value: "4px" },
|
||||||
|
@ -51,7 +49,6 @@ export const margin = {
|
||||||
label: "Bottom",
|
label: "Bottom",
|
||||||
key: "margin-bottom",
|
key: "margin-bottom",
|
||||||
control: Select,
|
control: Select,
|
||||||
bindable: false,
|
|
||||||
placeholder: "None",
|
placeholder: "None",
|
||||||
options: [
|
options: [
|
||||||
{ label: "4px", value: "4px" },
|
{ label: "4px", value: "4px" },
|
||||||
|
@ -72,7 +69,6 @@ export const margin = {
|
||||||
label: "Left",
|
label: "Left",
|
||||||
key: "margin-left",
|
key: "margin-left",
|
||||||
control: Select,
|
control: Select,
|
||||||
bindable: false,
|
|
||||||
placeholder: "None",
|
placeholder: "None",
|
||||||
options: [
|
options: [
|
||||||
{ label: "4px", value: "4px" },
|
{ label: "4px", value: "4px" },
|
||||||
|
@ -100,7 +96,6 @@ export const padding = {
|
||||||
label: "Top",
|
label: "Top",
|
||||||
key: "padding-top",
|
key: "padding-top",
|
||||||
control: Select,
|
control: Select,
|
||||||
bindable: false,
|
|
||||||
placeholder: "None",
|
placeholder: "None",
|
||||||
options: [
|
options: [
|
||||||
{ label: "4px", value: "4px" },
|
{ label: "4px", value: "4px" },
|
||||||
|
@ -121,7 +116,6 @@ export const padding = {
|
||||||
label: "Right",
|
label: "Right",
|
||||||
key: "padding-right",
|
key: "padding-right",
|
||||||
control: Select,
|
control: Select,
|
||||||
bindable: false,
|
|
||||||
placeholder: "None",
|
placeholder: "None",
|
||||||
options: [
|
options: [
|
||||||
{ label: "4px", value: "4px" },
|
{ label: "4px", value: "4px" },
|
||||||
|
@ -142,7 +136,6 @@ export const padding = {
|
||||||
label: "Bottom",
|
label: "Bottom",
|
||||||
key: "padding-bottom",
|
key: "padding-bottom",
|
||||||
control: Select,
|
control: Select,
|
||||||
bindable: false,
|
|
||||||
placeholder: "None",
|
placeholder: "None",
|
||||||
options: [
|
options: [
|
||||||
{ label: "4px", value: "4px" },
|
{ label: "4px", value: "4px" },
|
||||||
|
@ -163,7 +156,6 @@ export const padding = {
|
||||||
label: "Left",
|
label: "Left",
|
||||||
key: "padding-left",
|
key: "padding-left",
|
||||||
control: Select,
|
control: Select,
|
||||||
bindable: false,
|
|
||||||
placeholder: "None",
|
placeholder: "None",
|
||||||
options: [
|
options: [
|
||||||
{ label: "4px", value: "4px" },
|
{ label: "4px", value: "4px" },
|
||||||
|
|
|
@ -19,15 +19,24 @@
|
||||||
import IntegrationQueryEditor from "components/integration/index.svelte"
|
import IntegrationQueryEditor from "components/integration/index.svelte"
|
||||||
import ExternalDataSourceTable from "components/backend/DataTable/ExternalDataSourceTable.svelte"
|
import ExternalDataSourceTable from "components/backend/DataTable/ExternalDataSourceTable.svelte"
|
||||||
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
|
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
|
||||||
import { datasources, integrations, queries } from "stores/backend"
|
import {
|
||||||
|
datasources,
|
||||||
|
integrations,
|
||||||
|
queries,
|
||||||
|
roles,
|
||||||
|
permissions,
|
||||||
|
} from "stores/backend"
|
||||||
import { capitalise } from "../../helpers"
|
import { capitalise } from "../../helpers"
|
||||||
import CodeMirrorEditor from "components/common/CodeMirrorEditor.svelte"
|
import CodeMirrorEditor from "components/common/CodeMirrorEditor.svelte"
|
||||||
|
import { Roles } from "constants/backend"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
export let query
|
export let query
|
||||||
export let fields = []
|
export let fields = []
|
||||||
|
|
||||||
let parameters
|
let parameters
|
||||||
let data = []
|
let data = []
|
||||||
|
let roleId
|
||||||
const transformerDocs =
|
const transformerDocs =
|
||||||
"https://docs.budibase.com/building-apps/data/transformers"
|
"https://docs.budibase.com/building-apps/data/transformers"
|
||||||
const typeOptions = [
|
const typeOptions = [
|
||||||
|
@ -70,7 +79,22 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetDependentFields() {
|
function resetDependentFields() {
|
||||||
if (query.fields.extra) query.fields.extra = {}
|
if (query.fields.extra) {
|
||||||
|
query.fields.extra = {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateRole(role, id = null) {
|
||||||
|
roleId = role
|
||||||
|
if (query?._id || id) {
|
||||||
|
for (let level of ["read", "write"]) {
|
||||||
|
await permissions.save({
|
||||||
|
level,
|
||||||
|
role,
|
||||||
|
resource: query?._id || id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function populateExtraQuery(extraQueryFields) {
|
function populateExtraQuery(extraQueryFields) {
|
||||||
|
@ -122,6 +146,7 @@
|
||||||
async function saveQuery() {
|
async function saveQuery() {
|
||||||
try {
|
try {
|
||||||
const { _id } = await queries.save(query.datasourceId, query)
|
const { _id } = await queries.save(query.datasourceId, query)
|
||||||
|
await updateRole(roleId, _id)
|
||||||
notifications.success(`Query saved successfully.`)
|
notifications.success(`Query saved successfully.`)
|
||||||
$goto(`../${_id}`)
|
$goto(`../${_id}`)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -129,6 +154,18 @@
|
||||||
notifications.error(`Error creating query. ${err.message}`)
|
notifications.error(`Error creating query. ${err.message}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
if (!query || !query._id) {
|
||||||
|
roleId = Roles.BASIC
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
roleId = (await permissions.forResource(query._id))["read"]
|
||||||
|
} catch (err) {
|
||||||
|
roleId = Roles.BASIC
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Layout gap="S" noPadding>
|
<Layout gap="S" noPadding>
|
||||||
|
@ -151,6 +188,16 @@
|
||||||
queryConfig[verb]?.displayName || capitalise(verb)}
|
queryConfig[verb]?.displayName || capitalise(verb)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="config-field">
|
||||||
|
<Label>Access level</Label>
|
||||||
|
<Select
|
||||||
|
value={roleId}
|
||||||
|
on:change={e => updateRole(e.detail)}
|
||||||
|
options={$roles}
|
||||||
|
getOptionLabel={x => x.name}
|
||||||
|
getOptionValue={x => x._id}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
{#if integrationInfo?.extra && query.queryVerb}
|
{#if integrationInfo?.extra && query.queryVerb}
|
||||||
<ExtraQueryConfig
|
<ExtraQueryConfig
|
||||||
{query}
|
{query}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
Checkbox,
|
Checkbox,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { store, automationStore, hostingStore } from "builderStore"
|
import { store, automationStore, hostingStore } from "builderStore"
|
||||||
import { admin } from "stores/portal"
|
import { admin, auth } from "stores/portal"
|
||||||
import { string, mixed, object } from "yup"
|
import { string, mixed, object } from "yup"
|
||||||
import api, { get, post } from "builderStore/api"
|
import api, { get, post } from "builderStore/api"
|
||||||
import analytics, { Events } from "analytics"
|
import analytics, { Events } from "analytics"
|
||||||
|
@ -139,6 +139,7 @@
|
||||||
}
|
}
|
||||||
const userResp = await api.post(`/api/users/metadata/self`, user)
|
const userResp = await api.post(`/api/users/metadata/self`, user)
|
||||||
await userResp.json()
|
await userResp.json()
|
||||||
|
await auth.setInitInfo({})
|
||||||
$goto(`/builder/app/${appJson.instance._id}`)
|
$goto(`/builder/app/${appJson.instance._id}`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
@ -146,6 +147,21 @@
|
||||||
submitting = false
|
submitting = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getModalTitle() {
|
||||||
|
let title = "Create App"
|
||||||
|
if (template.fromFile) {
|
||||||
|
title = "Import App"
|
||||||
|
} else if (template.key) {
|
||||||
|
title = "Create app from template"
|
||||||
|
}
|
||||||
|
return title
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onCancel() {
|
||||||
|
template = null
|
||||||
|
await auth.setInitInfo({})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if showTemplateSelection}
|
{#if showTemplateSelection}
|
||||||
|
@ -172,10 +188,10 @@
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
{:else}
|
{:else}
|
||||||
<ModalContent
|
<ModalContent
|
||||||
title={template?.fromFile ? "Import app" : "Create app"}
|
title={getModalTitle()}
|
||||||
confirmText={template?.fromFile ? "Import app" : "Create app"}
|
confirmText={template?.fromFile ? "Import app" : "Create app"}
|
||||||
onConfirm={createNewApp}
|
onConfirm={createNewApp}
|
||||||
onCancel={inline ? () => (template = null) : null}
|
onCancel={inline ? onCancel : null}
|
||||||
cancelText={inline ? "Back" : undefined}
|
cancelText={inline ? "Back" : undefined}
|
||||||
showCloseIcon={!inline}
|
showCloseIcon={!inline}
|
||||||
disabled={!valid}
|
disabled={!valid}
|
||||||
|
|
|
@ -37,33 +37,33 @@
|
||||||
<p class="detail">{template?.category?.toUpperCase()}</p>
|
<p class="detail">{template?.category?.toUpperCase()}</p>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
<div class="template start-from-scratch" on:click={() => onSelect(null)}>
|
|
||||||
<div
|
|
||||||
class="background-icon"
|
|
||||||
style={`background: rgb(50, 50, 50); color: white;`}
|
|
||||||
>
|
|
||||||
<Icon name="Add" />
|
|
||||||
</div>
|
|
||||||
<Heading size="XS">Start from scratch</Heading>
|
|
||||||
<p class="detail">BLANK</p>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="template import"
|
|
||||||
on:click={() => onSelect(null, { useImport: true })}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="background-icon"
|
|
||||||
style={`background: rgb(50, 50, 50); color: white;`}
|
|
||||||
>
|
|
||||||
<Icon name="Add" />
|
|
||||||
</div>
|
|
||||||
<Heading size="XS">Import an app</Heading>
|
|
||||||
<p class="detail">BLANK</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{:catch err}
|
{:catch err}
|
||||||
<h1 style="color:red">{err}</h1>
|
<h1 style="color:red">{err}</h1>
|
||||||
{/await}
|
{/await}
|
||||||
|
<div class="template start-from-scratch" on:click={() => onSelect(null)}>
|
||||||
|
<div
|
||||||
|
class="background-icon"
|
||||||
|
style={`background: rgb(50, 50, 50); color: white;`}
|
||||||
|
>
|
||||||
|
<Icon name="Add" />
|
||||||
|
</div>
|
||||||
|
<Heading size="XS">Start from scratch</Heading>
|
||||||
|
<p class="detail">BLANK</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="template import"
|
||||||
|
on:click={() => onSelect(null, { useImport: true })}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="background-icon"
|
||||||
|
style={`background: rgb(50, 50, 50); color: white;`}
|
||||||
|
>
|
||||||
|
<Icon name="Add" />
|
||||||
|
</div>
|
||||||
|
<Heading size="XS">Import an app</Heading>
|
||||||
|
<p class="detail">BLANK</p>
|
||||||
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -138,3 +138,19 @@ export const RelationshipTypes = {
|
||||||
ONE_TO_MANY: "one-to-many",
|
ONE_TO_MANY: "one-to-many",
|
||||||
MANY_TO_ONE: "many-to-one",
|
MANY_TO_ONE: "many-to-one",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ALLOWABLE_STRING_OPTIONS = [FIELDS.STRING, FIELDS.OPTIONS]
|
||||||
|
|
||||||
|
export const ALLOWABLE_STRING_TYPES = ALLOWABLE_STRING_OPTIONS.map(
|
||||||
|
opt => opt.type
|
||||||
|
)
|
||||||
|
|
||||||
|
export const ALLOWABLE_NUMBER_OPTIONS = [FIELDS.NUMBER, FIELDS.BOOLEAN]
|
||||||
|
|
||||||
|
export const ALLOWABLE_NUMBER_TYPES = ALLOWABLE_NUMBER_OPTIONS.map(
|
||||||
|
opt => opt.type
|
||||||
|
)
|
||||||
|
|
||||||
|
export const SWITCHABLE_TYPES = ALLOWABLE_NUMBER_TYPES.concat(
|
||||||
|
ALLOWABLE_STRING_TYPES
|
||||||
|
)
|
||||||
|
|
|
@ -40,6 +40,8 @@ export const UNEDITABLE_USER_FIELDS = [
|
||||||
"lastName",
|
"lastName",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const UNSORTABLE_TYPES = ["formula", "attachment", "array", "link"]
|
||||||
|
|
||||||
export const LAYOUT_NAMES = {
|
export const LAYOUT_NAMES = {
|
||||||
MASTER: {
|
MASTER: {
|
||||||
PRIVATE: "layout_private_master",
|
PRIVATE: "layout_private_master",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { isActive, redirect } from "@roxi/routify"
|
import { isActive, redirect, params } from "@roxi/routify"
|
||||||
import { admin, auth } from "stores/portal"
|
import { admin, auth } from "stores/portal"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
|
@ -28,9 +28,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user && user.tenantId) {
|
if (user && user.tenantId) {
|
||||||
// no tenant in the url - send to account portal to fix this
|
|
||||||
if (!urlTenantId) {
|
if (!urlTenantId) {
|
||||||
window.location.href = $admin.accountPortalUrl
|
// redirect to correct tenantId subdomain
|
||||||
|
if (!window.location.host.includes("localhost")) {
|
||||||
|
let redirectUrl = window.location.href
|
||||||
|
redirectUrl = redirectUrl.replace("://", `://${user.tenantId}.`)
|
||||||
|
window.location.href = redirectUrl
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,6 +51,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
if ($params["?template"]) {
|
||||||
|
await auth.setInitInfo({ init_template: $params["?template"] })
|
||||||
|
}
|
||||||
|
|
||||||
await auth.checkAuth()
|
await auth.checkAuth()
|
||||||
await admin.init()
|
await admin.init()
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,10 @@
|
||||||
try {
|
try {
|
||||||
// Create datasource
|
// Create datasource
|
||||||
await datasources.save(datasource)
|
await datasources.save(datasource)
|
||||||
|
if (datasource?.plus) {
|
||||||
|
await tables.fetch()
|
||||||
|
}
|
||||||
|
await datasources.fetch()
|
||||||
notifications.success(`Datasource ${name} updated successfully.`)
|
notifications.success(`Datasource ${name} updated successfully.`)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notifications.error(`Error saving datasource: ${err}`)
|
notifications.error(`Error saving datasource: ${err}`)
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
selectedComponent,
|
selectedComponent,
|
||||||
allScreens,
|
allScreens,
|
||||||
} from "builderStore"
|
} from "builderStore"
|
||||||
|
import { Detail, Layout, Button, Icon } from "@budibase/bbui"
|
||||||
|
|
||||||
import CurrentItemPreview from "components/design/AppPreview"
|
import CurrentItemPreview from "components/design/AppPreview"
|
||||||
import PropertiesPanel from "components/design/PropertiesPanel/PropertiesPanel.svelte"
|
import PropertiesPanel from "components/design/PropertiesPanel/PropertiesPanel.svelte"
|
||||||
import ComponentSelectionList from "components/design/AppPreview/ComponentSelectionList.svelte"
|
import ComponentSelectionList from "components/design/AppPreview/ComponentSelectionList.svelte"
|
||||||
|
@ -16,6 +18,8 @@
|
||||||
import AppThemeSelect from "components/design/AppPreview/AppThemeSelect.svelte"
|
import AppThemeSelect from "components/design/AppPreview/AppThemeSelect.svelte"
|
||||||
import ThemeEditor from "components/design/AppPreview/ThemeEditor.svelte"
|
import ThemeEditor from "components/design/AppPreview/ThemeEditor.svelte"
|
||||||
import DevicePreviewSelect from "components/design/AppPreview/DevicePreviewSelect.svelte"
|
import DevicePreviewSelect from "components/design/AppPreview/DevicePreviewSelect.svelte"
|
||||||
|
import Logo from "assets/bb-space-man.svg"
|
||||||
|
import ScreenWizard from "components/design/NavigationPanel/ScreenWizard.svelte"
|
||||||
|
|
||||||
// Cache previous values so we don't update the URL more than necessary
|
// Cache previous values so we don't update the URL more than necessary
|
||||||
let previousType
|
let previousType
|
||||||
|
@ -23,6 +27,9 @@
|
||||||
let previousComponentId
|
let previousComponentId
|
||||||
let hydrationComplete = false
|
let hydrationComplete = false
|
||||||
|
|
||||||
|
// Manage the layout modal flow from here
|
||||||
|
let showModal
|
||||||
|
|
||||||
// Hydrate state from URL params
|
// Hydrate state from URL params
|
||||||
$: hydrateStateFromURL($params, $leftover)
|
$: hydrateStateFromURL($params, $leftover)
|
||||||
|
|
||||||
|
@ -145,7 +152,7 @@
|
||||||
<!-- routify:options index=1 -->
|
<!-- routify:options index=1 -->
|
||||||
<div class="root">
|
<div class="root">
|
||||||
<div class="ui-nav">
|
<div class="ui-nav">
|
||||||
<FrontendNavigatePane />
|
<FrontendNavigatePane {showModal} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="preview-pane">
|
<div class="preview-pane">
|
||||||
|
@ -166,6 +173,25 @@
|
||||||
<CurrentItemPreview />
|
<CurrentItemPreview />
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="centered">
|
||||||
|
<div class="main">
|
||||||
|
<Layout gap="S" justifyItems="center">
|
||||||
|
<img class="img-size" alt="logo" src={Logo} />
|
||||||
|
<div class="new-screen-text">
|
||||||
|
<Detail size="M">Let's add some life to this screen</Detail>
|
||||||
|
</div>
|
||||||
|
<Button on:click={() => showModal()} size="M" cta>
|
||||||
|
<div class="new-screen-button">
|
||||||
|
<div class="background-icon" style="color: white;">
|
||||||
|
<Icon name="Add" />
|
||||||
|
</div>
|
||||||
|
Add Screen
|
||||||
|
</div></Button
|
||||||
|
>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -178,6 +204,8 @@
|
||||||
|
|
||||||
<slot />
|
<slot />
|
||||||
|
|
||||||
|
<ScreenWizard bind:showModal />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.root {
|
.root {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
@ -186,7 +214,30 @@
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
|
.new-screen-text {
|
||||||
|
width: 160px;
|
||||||
|
text-align: center;
|
||||||
|
color: #2c2c2c;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-screen-button {
|
||||||
|
margin-left: 5px;
|
||||||
|
height: 20px;
|
||||||
|
width: 100px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background-icon {
|
||||||
|
margin-top: 4px;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-size {
|
||||||
|
width: 160px;
|
||||||
|
height: 160px;
|
||||||
|
}
|
||||||
.ui-nav {
|
.ui-nav {
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
background-color: var(--background);
|
background-color: var(--background);
|
||||||
|
@ -237,4 +288,21 @@
|
||||||
border-left: var(--border-light);
|
border-left: var(--border-light);
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.centered {
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 10%;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
ActionButton,
|
ActionButton,
|
||||||
ActionGroup,
|
ActionGroup,
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
|
Input,
|
||||||
Select,
|
Select,
|
||||||
Modal,
|
Modal,
|
||||||
Page,
|
Page,
|
||||||
|
@ -36,6 +37,7 @@
|
||||||
let loaded = false
|
let loaded = false
|
||||||
let searchTerm = ""
|
let searchTerm = ""
|
||||||
let cloud = $admin.cloud
|
let cloud = $admin.cloud
|
||||||
|
let appName = ""
|
||||||
|
|
||||||
$: enrichedApps = enrichApps($apps, $auth.user, sortBy)
|
$: enrichedApps = enrichApps($apps, $auth.user, sortBy)
|
||||||
$: filteredApps = enrichedApps.filter(app =>
|
$: filteredApps = enrichedApps.filter(app =>
|
||||||
|
@ -163,6 +165,7 @@
|
||||||
notifications.error(`Error deleting app: ${err}`)
|
notifications.error(`Error deleting app: ${err}`)
|
||||||
}
|
}
|
||||||
selectedApp = null
|
selectedApp = null
|
||||||
|
appName = null
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateApp = async app => {
|
const updateApp = async app => {
|
||||||
|
@ -184,9 +187,27 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createAppFromTemplateUrl(templateKey) {
|
||||||
|
// validate the template key just to make sure
|
||||||
|
const templateParts = templateKey.split("/")
|
||||||
|
if (templateParts.length === 2 && templateParts[0] === "app") {
|
||||||
|
template = {
|
||||||
|
key: templateKey,
|
||||||
|
}
|
||||||
|
initiateAppCreation()
|
||||||
|
} else {
|
||||||
|
notifications.error("Your Template URL is invalid. Please try another.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await apps.load()
|
await apps.load()
|
||||||
loaded = true
|
loaded = true
|
||||||
|
// if the portal is loaded from an external URL with a template param
|
||||||
|
const initInfo = await auth.getInitInfo()
|
||||||
|
if (initInfo.init_template) {
|
||||||
|
createAppFromTemplateUrl(initInfo.init_template)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -278,8 +299,13 @@
|
||||||
title="Confirm deletion"
|
title="Confirm deletion"
|
||||||
okText="Delete app"
|
okText="Delete app"
|
||||||
onOk={confirmDeleteApp}
|
onOk={confirmDeleteApp}
|
||||||
|
onCancel={() => (appName = null)}
|
||||||
|
disabled={appName !== selectedApp?.name}
|
||||||
>
|
>
|
||||||
Are you sure you want to delete the app <b>{selectedApp?.name}</b>?
|
Are you sure you want to delete the app <b>{selectedApp?.name}</b>?
|
||||||
|
|
||||||
|
<p>Please enter the app name below to confirm.</p>
|
||||||
|
<Input bind:value={appName} data-cy="delete-app-confirmation" />
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
bind:this={unpublishModal}
|
bind:this={unpublishModal}
|
||||||
|
|
|
@ -21,26 +21,25 @@
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
import { organisation, auth, admin } from "stores/portal"
|
import { organisation, admin } from "stores/portal"
|
||||||
import { uuid } from "builderStore/uuid"
|
import { uuid } from "builderStore/uuid"
|
||||||
import analytics, { Events } from "analytics"
|
import analytics, { Events } from "analytics"
|
||||||
|
|
||||||
$: tenantId = $auth.tenantId
|
|
||||||
$: multiTenancyEnabled = $admin.multiTenancy
|
|
||||||
|
|
||||||
const ConfigTypes = {
|
const ConfigTypes = {
|
||||||
Google: "google",
|
Google: "google",
|
||||||
OIDC: "oidc",
|
OIDC: "oidc",
|
||||||
}
|
}
|
||||||
|
|
||||||
function callbackUrl(tenantId, end) {
|
// Some older google configs contain a manually specified value - retain the functionality to edit the field
|
||||||
let url = `/api/global/auth`
|
// When there is no value or we are in the cloud - prohibit editing the field, must use platform url to change
|
||||||
if (multiTenancyEnabled && tenantId) {
|
$: googleCallbackUrl = undefined
|
||||||
url += `/${tenantId}`
|
$: googleCallbackReadonly = $admin.cloud || !googleCallbackUrl
|
||||||
}
|
|
||||||
url += end
|
// Indicate to user that callback is based on platform url
|
||||||
return url
|
// If there is an existing value, indicate that it may be removed to return to default behaviour
|
||||||
}
|
$: googleCallbackTooltip = googleCallbackReadonly
|
||||||
|
? "Vist the organisation page to update the platform URL"
|
||||||
|
: "Leave blank to use the default callback URL"
|
||||||
|
|
||||||
$: GoogleConfigFields = {
|
$: GoogleConfigFields = {
|
||||||
Google: [
|
Google: [
|
||||||
|
@ -49,8 +48,9 @@
|
||||||
{
|
{
|
||||||
name: "callbackURL",
|
name: "callbackURL",
|
||||||
label: "Callback URL",
|
label: "Callback URL",
|
||||||
readonly: true,
|
readonly: googleCallbackReadonly,
|
||||||
placeholder: callbackUrl(tenantId, "/google/callback"),
|
tooltip: googleCallbackTooltip,
|
||||||
|
placeholder: $organisation.googleCallbackUrl,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
@ -62,9 +62,10 @@
|
||||||
{ name: "clientSecret", label: "Client Secret" },
|
{ name: "clientSecret", label: "Client Secret" },
|
||||||
{
|
{
|
||||||
name: "callbackURL",
|
name: "callbackURL",
|
||||||
label: "Callback URL",
|
|
||||||
readonly: true,
|
readonly: true,
|
||||||
placeholder: callbackUrl(tenantId, "/oidc/callback"),
|
tooltip: "Vist the organisation page to update the platform URL",
|
||||||
|
label: "Callback URL",
|
||||||
|
placeholder: $organisation.oidcCallbackUrl,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
@ -241,6 +242,8 @@
|
||||||
providers.google = googleDoc
|
providers.google = googleDoc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
googleCallbackUrl = providers?.google?.config?.callbackURL
|
||||||
|
|
||||||
//Get the list of user uploaded logos and push it to the dropdown options.
|
//Get the list of user uploaded logos and push it to the dropdown options.
|
||||||
//This needs to be done before the config call so they're available when the dropdown renders
|
//This needs to be done before the config call so they're available when the dropdown renders
|
||||||
const res = await api.get(`/api/global/configs/logos_oidc`)
|
const res = await api.get(`/api/global/configs/logos_oidc`)
|
||||||
|
@ -308,7 +311,7 @@
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
{#each GoogleConfigFields.Google as field}
|
{#each GoogleConfigFields.Google as field}
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<Label size="L">{field.label}</Label>
|
<Label size="L" tooltip={field.tooltip}>{field.label}</Label>
|
||||||
<Input
|
<Input
|
||||||
bind:value={providers.google.config[field.name]}
|
bind:value={providers.google.config[field.name]}
|
||||||
readonly={field.readonly}
|
readonly={field.readonly}
|
||||||
|
@ -346,7 +349,7 @@
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
{#each OIDCConfigFields.Oidc as field}
|
{#each OIDCConfigFields.Oidc as field}
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<Label size="L">{field.label}</Label>
|
<Label size="L" tooltip={field.tooltip}>{field.label}</Label>
|
||||||
<Input
|
<Input
|
||||||
bind:value={providers.oidc.config.configs[0][field.name]}
|
bind:value={providers.oidc.config.configs[0][field.name]}
|
||||||
readonly={field.readonly}
|
readonly={field.readonly}
|
||||||
|
|
|
@ -116,7 +116,11 @@
|
||||||
</Layout>
|
</Layout>
|
||||||
<div class="fields">
|
<div class="fields">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<Label size="L">Platform URL</Label>
|
<Label
|
||||||
|
size="L"
|
||||||
|
tooltip={"Update the Platform URL to match your Budibase web URL. This keeps email templates and authentication configs up to date."}
|
||||||
|
>Platform URL</Label
|
||||||
|
>
|
||||||
<Input thin bind:value={$values.platformUrl} />
|
<Input thin bind:value={$values.platformUrl} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -135,6 +139,7 @@
|
||||||
.field {
|
.field {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 100px 1fr;
|
grid-template-columns: 100px 1fr;
|
||||||
|
grid-gap: var(--spacing-l);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.file {
|
.file {
|
||||||
|
|
|
@ -95,6 +95,7 @@ export function createDatasourcesStore() {
|
||||||
return { list: sources, selected: null }
|
return { list: sources, selected: null }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await queries.fetch()
|
||||||
return response
|
return response
|
||||||
},
|
},
|
||||||
removeSchemaError: () => {
|
removeSchemaError: () => {
|
||||||
|
|
|
@ -10,13 +10,11 @@ export function createPermissionStore() {
|
||||||
const response = await api.post(
|
const response = await api.post(
|
||||||
`/api/permission/${role}/${resource}/${level}`
|
`/api/permission/${role}/${resource}/${level}`
|
||||||
)
|
)
|
||||||
const json = await response.json()
|
return await response.json()
|
||||||
return json
|
|
||||||
},
|
},
|
||||||
forResource: async resourceId => {
|
forResource: async resourceId => {
|
||||||
const response = await api.get(`/api/permission/${resourceId}`)
|
const response = await api.get(`/api/permission/${resourceId}`)
|
||||||
const json = await response.json()
|
return await response.json()
|
||||||
return json
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { writable, get } from "svelte/store"
|
||||||
import { views, queries, datasources } from "./"
|
import { views, queries, datasources } from "./"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
|
import { SWITCHABLE_TYPES } from "../../constants/backend"
|
||||||
|
|
||||||
export function createTablesStore() {
|
export function createTablesStore() {
|
||||||
const store = writable({})
|
const store = writable({})
|
||||||
|
@ -47,7 +48,11 @@ export function createTablesStore() {
|
||||||
const field = updatedTable.schema[key]
|
const field = updatedTable.schema[key]
|
||||||
const oldField = oldTable?.schema[key]
|
const oldField = oldTable?.schema[key]
|
||||||
// if the type has changed then revert back to the old field
|
// if the type has changed then revert back to the old field
|
||||||
if (oldField != null && oldField?.type !== field.type) {
|
if (
|
||||||
|
oldField != null &&
|
||||||
|
oldField?.type !== field.type &&
|
||||||
|
SWITCHABLE_TYPES.indexOf(oldField?.type) === -1
|
||||||
|
) {
|
||||||
updatedTable.schema[key] = oldField
|
updatedTable.schema[key] = oldField
|
||||||
}
|
}
|
||||||
// field has been renamed
|
// field has been renamed
|
||||||
|
|
|
@ -57,11 +57,11 @@ export function createAuthStore() {
|
||||||
analytics.showChat({
|
analytics.showChat({
|
||||||
email: user.email,
|
email: user.email,
|
||||||
created_at: (user.createdAt || Date.now()) / 1000,
|
created_at: (user.createdAt || Date.now()) / 1000,
|
||||||
name: user.name,
|
name: user.account?.name,
|
||||||
user_id: user._id,
|
user_id: user._id,
|
||||||
tenant: user.tenantId,
|
tenant: user.tenantId,
|
||||||
"Company size": user.size,
|
"Company size": user.account?.size,
|
||||||
"Job role": user.profession,
|
"Job role": user.account?.profession,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -80,9 +80,30 @@ export function createAuthStore() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function setInitInfo(info) {
|
||||||
|
await api.post(`/api/global/auth/init`, info)
|
||||||
|
auth.update(store => {
|
||||||
|
store.initInfo = info
|
||||||
|
return store
|
||||||
|
})
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getInitInfo() {
|
||||||
|
const response = await api.get(`/api/global/auth/init`)
|
||||||
|
const json = response.json()
|
||||||
|
auth.update(store => {
|
||||||
|
store.initInfo = json
|
||||||
|
return store
|
||||||
|
})
|
||||||
|
return json
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe: store.subscribe,
|
subscribe: store.subscribe,
|
||||||
setOrganisation: setOrganisation,
|
setOrganisation,
|
||||||
|
getInitInfo,
|
||||||
|
setInitInfo,
|
||||||
checkQueryString: async () => {
|
checkQueryString: async () => {
|
||||||
const urlParams = new URLSearchParams(window.location.search)
|
const urlParams = new URLSearchParams(window.location.search)
|
||||||
if (urlParams.has("tenantId")) {
|
if (urlParams.has("tenantId")) {
|
||||||
|
@ -122,6 +143,7 @@ export function createAuthStore() {
|
||||||
throw "Unable to create logout"
|
throw "Unable to create logout"
|
||||||
}
|
}
|
||||||
await response.json()
|
await response.json()
|
||||||
|
await setInitInfo({})
|
||||||
setUser(null)
|
setUser(null)
|
||||||
},
|
},
|
||||||
updateSelf: async fields => {
|
updateSelf: async fields => {
|
||||||
|
|
|
@ -3,12 +3,14 @@ import api from "builderStore/api"
|
||||||
import { auth } from "stores/portal"
|
import { auth } from "stores/portal"
|
||||||
|
|
||||||
const DEFAULT_CONFIG = {
|
const DEFAULT_CONFIG = {
|
||||||
platformUrl: "http://localhost:10000",
|
platformUrl: "",
|
||||||
logoUrl: undefined,
|
logoUrl: undefined,
|
||||||
docsUrl: undefined,
|
docsUrl: undefined,
|
||||||
company: "Budibase",
|
company: "Budibase",
|
||||||
oidc: undefined,
|
oidc: undefined,
|
||||||
google: undefined,
|
google: undefined,
|
||||||
|
oidcCallbackUrl: "",
|
||||||
|
googleCallbackUrl: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createOrganisationStore() {
|
export function createOrganisationStore() {
|
||||||
|
@ -28,6 +30,13 @@ export function createOrganisationStore() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function save(config) {
|
async function save(config) {
|
||||||
|
// delete non-persisted fields
|
||||||
|
const storeConfig = get(store)
|
||||||
|
delete storeConfig.oidc
|
||||||
|
delete storeConfig.google
|
||||||
|
delete storeConfig.oidcCallbackUrl
|
||||||
|
delete storeConfig.googleCallbackUrl
|
||||||
|
|
||||||
const res = await api.post("/api/global/configs", {
|
const res = await api.post("/api/global/configs", {
|
||||||
type: "settings",
|
type: "settings",
|
||||||
config: { ...get(store), ...config },
|
config: { ...get(store), ...config },
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import svelte from "@sveltejs/vite-plugin-svelte"
|
import { svelte } from "@sveltejs/vite-plugin-svelte"
|
||||||
import replace from "@rollup/plugin-replace"
|
import replace from "@rollup/plugin-replace"
|
||||||
|
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
@ -6,6 +6,11 @@ import path from "path"
|
||||||
export default ({ mode }) => {
|
export default ({ mode }) => {
|
||||||
const isProduction = mode === "production"
|
const isProduction = mode === "production"
|
||||||
return {
|
return {
|
||||||
|
server: {
|
||||||
|
fs: {
|
||||||
|
strict: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
base: "/builder/",
|
base: "/builder/",
|
||||||
build: {
|
build: {
|
||||||
minify: isProduction,
|
minify: isProduction,
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "0.9.175",
|
"version": "0.9.185-alpha.10",
|
||||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -19,7 +19,7 @@ The object key is the name of the component, as exported by `index.js`.
|
||||||
- **bindable** - whether the components provides a bindable value or not
|
- **bindable** - whether the components provides a bindable value or not
|
||||||
- **settings** - array of settings displayed in the builder
|
- **settings** - array of settings displayed in the builder
|
||||||
|
|
||||||
###Settings Definitions
|
### Settings Definitions
|
||||||
|
|
||||||
The `type` field in each setting is used by the builder to know which component to use to display
|
The `type` field in each setting is used by the builder to know which component to use to display
|
||||||
the setting, so it's important that this field is correct. The valid options are:
|
the setting, so it's important that this field is correct. The valid options are:
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
"deviceAwareness": true,
|
"deviceAwareness": true,
|
||||||
"state": true,
|
"state": true,
|
||||||
"customThemes": true,
|
"customThemes": true,
|
||||||
"devicePreview": true
|
"devicePreview": true,
|
||||||
|
"messagePassing": true
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
"name": "Layout",
|
"name": "Layout",
|
||||||
|
@ -2457,12 +2458,12 @@
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "dataProvider",
|
"type": "dataProvider",
|
||||||
"label": "Provider",
|
"label": "Data provider",
|
||||||
"key": "dataProvider"
|
"key": "dataProvider"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"label": "Row Count",
|
"label": "Row count",
|
||||||
"key": "rowCount",
|
"key": "rowCount",
|
||||||
"defaultValue": 8
|
"defaultValue": 8
|
||||||
},
|
},
|
||||||
|
@ -2496,9 +2497,36 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Auto Columns",
|
"label": "Show auto columns",
|
||||||
"key": "showAutoColumns",
|
"key": "showAutoColumns",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Link table rows",
|
||||||
|
"key": "linkRows"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Open link screens in modal",
|
||||||
|
"key": "linkPeek"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "url",
|
||||||
|
"label": "Link screen",
|
||||||
|
"key": "linkURL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"section": true,
|
||||||
|
"name": "Advanced",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"label": "ID column for linking (appended to URL)",
|
||||||
|
"key": "linkColumn",
|
||||||
|
"placeholder": "Default"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"context": {
|
"context": {
|
||||||
|
@ -2568,10 +2596,539 @@
|
||||||
"key": "linkURL",
|
"key": "linkURL",
|
||||||
"label": "Link URL"
|
"label": "Link URL"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"key": "linkPeek",
|
||||||
|
"label": "Open link in modal"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"key": "horizontal",
|
"key": "horizontal",
|
||||||
"label": "Horizontal"
|
"label": "Horizontal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Show button",
|
||||||
|
"key": "showButton"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"key": "buttonText",
|
||||||
|
"label": "Button text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"label": "Button action",
|
||||||
|
"key": "buttonOnClick"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"tableblock": {
|
||||||
|
"block": true,
|
||||||
|
"name": "Table block",
|
||||||
|
"icon": "Table",
|
||||||
|
"styles": ["size"],
|
||||||
|
"info": "Only the first 3 search columns will be used.",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Title",
|
||||||
|
"key": "title"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dataSource",
|
||||||
|
"label": "Data",
|
||||||
|
"key": "dataSource"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "multifield",
|
||||||
|
"label": "Search Columns",
|
||||||
|
"key": "searchColumns",
|
||||||
|
"placeholder": "Choose search columns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "filter",
|
||||||
|
"label": "Filtering",
|
||||||
|
"key": "filter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"label": "Sort Column",
|
||||||
|
"key": "sortColumn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Sort Order",
|
||||||
|
"key": "sortOrder",
|
||||||
|
"options": ["Ascending", "Descending"],
|
||||||
|
"defaultValue": "Descending"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Size",
|
||||||
|
"key": "size",
|
||||||
|
"defaultValue": "spectrum--medium",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "Medium",
|
||||||
|
"value": "spectrum--medium"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Large",
|
||||||
|
"value": "spectrum--large"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Paginate",
|
||||||
|
"key": "paginate",
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"section": true,
|
||||||
|
"name": "Table",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"label": "Row Count",
|
||||||
|
"key": "rowCount",
|
||||||
|
"defaultValue": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "multifield",
|
||||||
|
"label": "Table Columns",
|
||||||
|
"key": "tableColumns",
|
||||||
|
"dependsOn": "dataSource",
|
||||||
|
"placeholder": "All columns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Quiet table variant",
|
||||||
|
"key": "quiet"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Show auto columns",
|
||||||
|
"key": "showAutoColumns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Link table rows",
|
||||||
|
"key": "linkRows"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Open link in modal",
|
||||||
|
"key": "linkPeek"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "url",
|
||||||
|
"label": "Link screen",
|
||||||
|
"key": "linkURL"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"section": true,
|
||||||
|
"name": "Title button",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"key": "showTitleButton",
|
||||||
|
"label": "Show link button",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Open link in modal",
|
||||||
|
"key": "titleButtonPeek"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"key": "titleButtonText",
|
||||||
|
"label": "Button text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "url",
|
||||||
|
"label": "Button link",
|
||||||
|
"key": "titleButtonURL"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"section": true,
|
||||||
|
"name": "Advanced",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"label": "ID column for linking (appended to URL)",
|
||||||
|
"key": "linkColumn",
|
||||||
|
"placeholder": "Default"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cardsblock": {
|
||||||
|
"block": true,
|
||||||
|
"name": "Cards block",
|
||||||
|
"icon": "Table",
|
||||||
|
"styles": ["size"],
|
||||||
|
"info": "Only the first 3 search columns will be used.",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Title",
|
||||||
|
"key": "title"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dataSource",
|
||||||
|
"label": "Data",
|
||||||
|
"key": "dataSource"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "multifield",
|
||||||
|
"label": "Search Columns",
|
||||||
|
"key": "searchColumns",
|
||||||
|
"placeholder": "Choose search columns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "filter",
|
||||||
|
"label": "Filtering",
|
||||||
|
"key": "filter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"label": "Sort Column",
|
||||||
|
"key": "sortColumn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Sort Order",
|
||||||
|
"key": "sortOrder",
|
||||||
|
"options": ["Ascending", "Descending"],
|
||||||
|
"defaultValue": "Descending"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"label": "Limit",
|
||||||
|
"key": "limit",
|
||||||
|
"defaultValue": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Paginate",
|
||||||
|
"key": "paginate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"section": true,
|
||||||
|
"name": "Cards",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"key": "cardTitle",
|
||||||
|
"label": "Title",
|
||||||
|
"nested": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"key": "cardSubtitle",
|
||||||
|
"label": "Subtitle",
|
||||||
|
"nested": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"key": "cardDescription",
|
||||||
|
"label": "Description",
|
||||||
|
"nested": true
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"key": "cardImageURL",
|
||||||
|
"label": "Image URL",
|
||||||
|
"nested": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"key": "linkCardTitle",
|
||||||
|
"label": "Link card title"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"key": "cardPeek",
|
||||||
|
"label": "Open link in modal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "url",
|
||||||
|
"label": "Link screen",
|
||||||
|
"key": "cardURL",
|
||||||
|
"nested": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"key": "cardHorizontal",
|
||||||
|
"label": "Horizontal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Show button",
|
||||||
|
"key": "showCardButton"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"key": "cardButtonText",
|
||||||
|
"label": "Button text",
|
||||||
|
"nested": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"label": "Button action",
|
||||||
|
"key": "cardButtonOnClick",
|
||||||
|
"nested": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"section": true,
|
||||||
|
"name": "Title button",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"key": "showTitleButton",
|
||||||
|
"label": "Show link button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Open link in modal",
|
||||||
|
"key": "titleButtonPeek"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"key": "titleButtonText",
|
||||||
|
"label": "Button text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "url",
|
||||||
|
"label": "Button link",
|
||||||
|
"key": "titleButtonURL"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"section": true,
|
||||||
|
"name": "Advanced",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"label": "ID column for linking (appended to URL)",
|
||||||
|
"key": "linkColumn",
|
||||||
|
"placeholder": "Default"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"context": {
|
||||||
|
"type": "schema",
|
||||||
|
"suffix": "repeater"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeaterblock": {
|
||||||
|
"name": "Repeater block",
|
||||||
|
"icon": "ViewList",
|
||||||
|
"illegalChildren": ["section"],
|
||||||
|
"hasChildren": true,
|
||||||
|
"showSettingsBar": true,
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "dataSource",
|
||||||
|
"label": "Data",
|
||||||
|
"key": "dataSource"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "filter",
|
||||||
|
"label": "Filtering",
|
||||||
|
"key": "filter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"label": "Sort Column",
|
||||||
|
"key": "sortColumn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Sort Order",
|
||||||
|
"key": "sortOrder",
|
||||||
|
"options": ["Ascending", "Descending"],
|
||||||
|
"defaultValue": "Descending"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"label": "Limit",
|
||||||
|
"key": "limit",
|
||||||
|
"defaultValue": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Paginate",
|
||||||
|
"key": "paginate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"section": true,
|
||||||
|
"name": "Layout settings",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Empty Text",
|
||||||
|
"key": "noRowsMessage",
|
||||||
|
"defaultValue": "No rows found"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Direction",
|
||||||
|
"key": "direction",
|
||||||
|
"showInBar": true,
|
||||||
|
"barStyle": "buttons",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "Column",
|
||||||
|
"value": "column",
|
||||||
|
"barIcon": "ViewRow",
|
||||||
|
"barTitle": "Column layout"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Row",
|
||||||
|
"value": "row",
|
||||||
|
"barIcon": "ViewColumn",
|
||||||
|
"barTitle": "Row layout"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"defaultValue": "column"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Horiz. Align",
|
||||||
|
"key": "hAlign",
|
||||||
|
"showInBar": true,
|
||||||
|
"barStyle": "buttons",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "Left",
|
||||||
|
"value": "left",
|
||||||
|
"barIcon": "AlignLeft",
|
||||||
|
"barTitle": "Align left"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Center",
|
||||||
|
"value": "center",
|
||||||
|
"barIcon": "AlignCenter",
|
||||||
|
"barTitle": "Align center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Right",
|
||||||
|
"value": "right",
|
||||||
|
"barIcon": "AlignRight",
|
||||||
|
"barTitle": "Align right"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Stretch",
|
||||||
|
"value": "stretch",
|
||||||
|
"barIcon": "MoveLeftRight",
|
||||||
|
"barTitle": "Align stretched horizontally"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"defaultValue": "stretch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Vert. Align",
|
||||||
|
"key": "vAlign",
|
||||||
|
"showInBar": true,
|
||||||
|
"barStyle": "buttons",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "Top",
|
||||||
|
"value": "top",
|
||||||
|
"barIcon": "AlignTop",
|
||||||
|
"barTitle": "Align top"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Middle",
|
||||||
|
"value": "middle",
|
||||||
|
"barIcon": "AlignMiddle",
|
||||||
|
"barTitle": "Align middle"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Bottom",
|
||||||
|
"value": "bottom",
|
||||||
|
"barIcon": "AlignBottom",
|
||||||
|
"barTitle": "Align bottom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Stretch",
|
||||||
|
"value": "stretch",
|
||||||
|
"barIcon": "MoveUpDown",
|
||||||
|
"barTitle": "Align stretched vertically"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"defaultValue": "top"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Gap",
|
||||||
|
"key": "gap",
|
||||||
|
"showInBar": true,
|
||||||
|
"barStyle": "picker",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "None",
|
||||||
|
"value": "N"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Small",
|
||||||
|
"value": "S"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Medium",
|
||||||
|
"value": "M"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Large",
|
||||||
|
"value": "L"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"defaultValue": "M"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"context": [
|
||||||
|
{
|
||||||
|
"type": "static",
|
||||||
|
"suffix": "provider",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"label": "Rows",
|
||||||
|
"key": "rows"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Rows Length",
|
||||||
|
"key": "rowsLength"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Schema",
|
||||||
|
"key": "schema"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Page Number",
|
||||||
|
"key": "pageNumber"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "schema",
|
||||||
|
"suffix": "repeater"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "0.9.175",
|
"version": "0.9.185-alpha.10",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"module": "dist/budibase-client.js",
|
"module": "dist/budibase-client.js",
|
||||||
"main": "dist/budibase-client.js",
|
"main": "dist/budibase-client.js",
|
||||||
|
@ -19,9 +19,9 @@
|
||||||
"dev:builder": "rollup -cw"
|
"dev:builder": "rollup -cw"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^0.9.175",
|
"@budibase/bbui": "^0.9.185-alpha.10",
|
||||||
"@budibase/standard-components": "^0.9.139",
|
"@budibase/standard-components": "^0.9.139",
|
||||||
"@budibase/string-templates": "^0.9.175",
|
"@budibase/string-templates": "^0.9.185-alpha.10",
|
||||||
"regexparam": "^1.3.0",
|
"regexparam": "^1.3.0",
|
||||||
"shortid": "^2.2.15",
|
"shortid": "^2.2.15",
|
||||||
"svelte-spa-router": "^3.0.5"
|
"svelte-spa-router": "^3.0.5"
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { fetchTableData } from "./tables"
|
import { fetchTableData, fetchTableDefinition } from "./tables"
|
||||||
import { fetchViewData } from "./views"
|
import { fetchViewData } from "./views"
|
||||||
import { fetchRelationshipData } from "./relationships"
|
import { fetchRelationshipData } from "./relationships"
|
||||||
import { executeQuery } from "./queries"
|
import { FieldTypes } from "../constants"
|
||||||
|
import { executeQuery, fetchQueryDefinition } from "./queries"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches all rows for a particular Budibase data source.
|
* Fetches all rows for a particular Budibase data source.
|
||||||
|
@ -28,7 +29,7 @@ export const fetchDatasource = async dataSource => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rows = await executeQuery({ queryId: dataSource._id, parameters })
|
rows = await executeQuery({ queryId: dataSource._id, parameters })
|
||||||
} else if (type === "link") {
|
} else if (type === FieldTypes.LINK) {
|
||||||
rows = await fetchRelationshipData({
|
rows = await fetchRelationshipData({
|
||||||
rowId: dataSource.rowId,
|
rowId: dataSource.rowId,
|
||||||
tableId: dataSource.rowTableId,
|
tableId: dataSource.rowTableId,
|
||||||
|
@ -39,3 +40,55 @@ export const fetchDatasource = async dataSource => {
|
||||||
// Enrich the result is always an array
|
// Enrich the result is always an array
|
||||||
return Array.isArray(rows) ? rows : []
|
return Array.isArray(rows) ? rows : []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the schema of any kind of datasource.
|
||||||
|
*/
|
||||||
|
export const fetchDatasourceSchema = async dataSource => {
|
||||||
|
if (!dataSource) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const { type } = dataSource
|
||||||
|
|
||||||
|
// Nested providers should already have exposed their own schema
|
||||||
|
if (type === "provider") {
|
||||||
|
return dataSource.value?.schema
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field sources have their schema statically defined
|
||||||
|
if (type === "field") {
|
||||||
|
if (dataSource.fieldType === "attachment") {
|
||||||
|
return {
|
||||||
|
url: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else if (dataSource.fieldType === "array") {
|
||||||
|
return {
|
||||||
|
value: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tables, views and links can be fetched by table ID
|
||||||
|
if (
|
||||||
|
(type === "table" || type === "view" || type === "link") &&
|
||||||
|
dataSource.tableId
|
||||||
|
) {
|
||||||
|
const table = await fetchTableDefinition(dataSource.tableId)
|
||||||
|
return table?.schema
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queries can be fetched by query ID
|
||||||
|
if (type === "query" && dataSource._id) {
|
||||||
|
const definition = await fetchQueryDefinition(dataSource._id)
|
||||||
|
return definition?.schema
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import API from "./api"
|
||||||
* Executes a query against an external data connector.
|
* Executes a query against an external data connector.
|
||||||
*/
|
*/
|
||||||
export const executeQuery = async ({ queryId, parameters }) => {
|
export const executeQuery = async ({ queryId, parameters }) => {
|
||||||
const query = await API.get({ url: `/api/queries/${queryId}` })
|
const query = await fetchQueryDefinition(queryId)
|
||||||
if (query?.datasourceId == null) {
|
if (query?.datasourceId == null) {
|
||||||
notificationStore.actions.error("That query couldn't be found")
|
notificationStore.actions.error("That query couldn't be found")
|
||||||
return
|
return
|
||||||
|
@ -24,3 +24,10 @@ export const executeQuery = async ({ queryId, parameters }) => {
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the definition of an external query.
|
||||||
|
*/
|
||||||
|
export const fetchQueryDefinition = async queryId => {
|
||||||
|
return await API.get({ url: `/api/queries/${queryId}`, cache: true })
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { notificationStore, dataSourceStore } from "stores"
|
import { notificationStore, dataSourceStore } from "stores"
|
||||||
import API from "./api"
|
import API from "./api"
|
||||||
import { fetchTableDefinition } from "./tables"
|
import { fetchTableDefinition } from "./tables"
|
||||||
|
import { FieldTypes } from "../constants"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches data about a certain row in a table.
|
* Fetches data about a certain row in a table.
|
||||||
|
@ -107,6 +108,8 @@ export const deleteRows = async ({ tableId, rows }) => {
|
||||||
/**
|
/**
|
||||||
* Enriches rows which contain certain field types so that they can
|
* Enriches rows which contain certain field types so that they can
|
||||||
* be properly displayed.
|
* be properly displayed.
|
||||||
|
* The ability to create these bindings has been removed, but they will still
|
||||||
|
* exist in client apps to support backwards compatibility.
|
||||||
*/
|
*/
|
||||||
export const enrichRows = async (rows, tableId) => {
|
export const enrichRows = async (rows, tableId) => {
|
||||||
if (!Array.isArray(rows)) {
|
if (!Array.isArray(rows)) {
|
||||||
|
@ -129,7 +132,7 @@ export const enrichRows = async (rows, tableId) => {
|
||||||
const keys = Object.keys(schema)
|
const keys = Object.keys(schema)
|
||||||
for (let key of keys) {
|
for (let key of keys) {
|
||||||
const type = schema[key].type
|
const type = schema[key].type
|
||||||
if (type === "link" && Array.isArray(row[key])) {
|
if (type === FieldTypes.LINK && Array.isArray(row[key])) {
|
||||||
// Enrich row a string join of relationship fields
|
// Enrich row a string join of relationship fields
|
||||||
row[`${key}_text`] =
|
row[`${key}_text`] =
|
||||||
row[key]
|
row[key]
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
<script>
|
||||||
|
import { getContext, setContext } from "svelte"
|
||||||
|
|
||||||
|
const component = getContext("component")
|
||||||
|
|
||||||
|
// We need to set a block context to know we're inside a block, but also
|
||||||
|
// to be able to reference the actual component ID of the block from
|
||||||
|
// any depth
|
||||||
|
setContext("block", { id: $component.id })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<slot />
|
|
@ -0,0 +1,35 @@
|
||||||
|
<script>
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
import { generate } from "shortid"
|
||||||
|
import Component from "components/Component.svelte"
|
||||||
|
|
||||||
|
export let type
|
||||||
|
export let props
|
||||||
|
export let styles
|
||||||
|
export let context
|
||||||
|
|
||||||
|
// ID is only exposed as a prop so that it can be bound to from parent
|
||||||
|
// block components
|
||||||
|
export let id
|
||||||
|
|
||||||
|
const block = getContext("block")
|
||||||
|
const rand = generate()
|
||||||
|
|
||||||
|
// Create a fake component instance so that we can use the core Component
|
||||||
|
// to render this part of the block, taking advantage of binding enrichment
|
||||||
|
$: id = `${block.id}-${context ?? rand}`
|
||||||
|
$: instance = {
|
||||||
|
_component: `@budibase/standard-components/${type}`,
|
||||||
|
_id: id,
|
||||||
|
_styles: {
|
||||||
|
normal: {
|
||||||
|
...styles,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Component {instance} isBlock>
|
||||||
|
<slot />
|
||||||
|
</Component>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue