Merge remote-tracking branch 'origin/develop' into fix/pc-generic-fixes
This commit is contained in:
commit
1da1331b3a
|
@ -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 />
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "0.9.185-alpha.0",
|
"version": "0.9.185-alpha.14",
|
||||||
"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.185-alpha.0",
|
"version": "0.9.185-alpha.14",
|
||||||
"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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,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 => {
|
||||||
|
@ -351,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.185-alpha.0",
|
"version": "0.9.185-alpha.14",
|
||||||
"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",
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
|
export let dataCy
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
let focus = false
|
let focus = false
|
||||||
|
@ -78,6 +79,7 @@
|
||||||
{disabled}
|
{disabled}
|
||||||
{readonly}
|
{readonly}
|
||||||
{id}
|
{id}
|
||||||
|
data-cy={dataCy}
|
||||||
value={value || ""}
|
value={value || ""}
|
||||||
placeholder={placeholder || ""}
|
placeholder={placeholder || ""}
|
||||||
on:click
|
on:click
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
export let error = null
|
export let error = null
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
|
export let dataCy
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -23,6 +24,7 @@
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {label} {labelPosition} {error}>
|
||||||
<TextField
|
<TextField
|
||||||
|
{dataCy}
|
||||||
{updateOnChange}
|
{updateOnChange}
|
||||||
{error}
|
{error}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
export let onConfirm = undefined
|
export let onConfirm = undefined
|
||||||
|
|
||||||
$: icon = selectIcon(type)
|
$: icon = selectIcon(type)
|
||||||
|
// if newlines used, convert them to different elements
|
||||||
|
$: split = message.split("\n")
|
||||||
|
|
||||||
function selectIcon(alertType) {
|
function selectIcon(alertType) {
|
||||||
switch (alertType) {
|
switch (alertType) {
|
||||||
|
@ -33,7 +35,9 @@
|
||||||
<use xlink:href="#spectrum-icon-18-{icon}" />
|
<use xlink:href="#spectrum-icon-18-{icon}" />
|
||||||
</svg>
|
</svg>
|
||||||
<div class="spectrum-InLineAlert-header">{header}</div>
|
<div class="spectrum-InLineAlert-header">{header}</div>
|
||||||
<div class="spectrum-InLineAlert-content">{message}</div>
|
{#each split as splitMsg}
|
||||||
|
<div class="spectrum-InLineAlert-content">{splitMsg}</div>
|
||||||
|
{/each}
|
||||||
{#if onConfirm}
|
{#if onConfirm}
|
||||||
<div class="spectrum-InLineAlert-footer">
|
<div class="spectrum-InLineAlert-footer">
|
||||||
<Button secondary on:click={onConfirm}>OK</Button>
|
<Button secondary on:click={onConfirm}>OK</Button>
|
||||||
|
@ -47,5 +51,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 |
|
@ -53,6 +53,7 @@ context("Create a Table", () => {
|
||||||
cy.get(".spectrum-Table-editIcon > use").click()
|
cy.get(".spectrum-Table-editIcon > use").click()
|
||||||
cy.contains("Delete").click()
|
cy.contains("Delete").click()
|
||||||
cy.wait(50)
|
cy.wait(50)
|
||||||
|
cy.get(`[data-cy="delete-column-confirm"]`).type("nameupdated")
|
||||||
cy.contains("Delete Column").click()
|
cy.contains("Delete Column").click()
|
||||||
cy.contains("nameupdated").should("not.exist")
|
cy.contains("nameupdated").should("not.exist")
|
||||||
})
|
})
|
||||||
|
@ -66,6 +67,7 @@ context("Create a Table", () => {
|
||||||
cy.get(".actions .spectrum-Icon").click({ force: true })
|
cy.get(".actions .spectrum-Icon").click({ force: true })
|
||||||
})
|
})
|
||||||
cy.get(".spectrum-Menu > :nth-child(2)").click()
|
cy.get(".spectrum-Menu > :nth-child(2)").click()
|
||||||
|
cy.get(`[data-cy="delete-table-confirm"]`).type("dog")
|
||||||
cy.contains("Delete Table").click()
|
cy.contains("Delete Table").click()
|
||||||
cy.contains("dog").should("not.exist")
|
cy.contains("dog").should("not.exist")
|
||||||
})
|
})
|
||||||
|
|
|
@ -17,6 +17,7 @@ process.env.JWT_SECRET = cypressConfig.env.JWT_SECRET
|
||||||
process.env.COUCH_URL = `leveldb://${tmpdir}/.data/`
|
process.env.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"
|
||||||
|
|
|
@ -188,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.185-alpha.0",
|
"version": "0.9.185-alpha.14",
|
||||||
"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.185-alpha.0",
|
"@budibase/bbui": "^0.9.185-alpha.14",
|
||||||
"@budibase/client": "^0.9.185-alpha.0",
|
"@budibase/client": "^0.9.185-alpha.14",
|
||||||
"@budibase/colorpicker": "1.1.2",
|
"@budibase/colorpicker": "1.1.2",
|
||||||
"@budibase/string-templates": "^0.9.185-alpha.0",
|
"@budibase/string-templates": "^0.9.185-alpha.14",
|
||||||
"@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",
|
||||||
|
|
|
@ -207,11 +207,11 @@ const getProviderContextBindings = (asset, dataProviders) => {
|
||||||
const keys = Object.keys(schema).sort()
|
const keys = Object.keys(schema).sort()
|
||||||
|
|
||||||
// Generate safe unique runtime prefix
|
// Generate safe unique runtime prefix
|
||||||
let runtimeId = component._id
|
let providerId = component._id
|
||||||
if (runtimeSuffix) {
|
if (runtimeSuffix) {
|
||||||
runtimeId += `-${runtimeSuffix}`
|
providerId += `-${runtimeSuffix}`
|
||||||
}
|
}
|
||||||
const safeComponentId = makePropSafe(runtimeId)
|
const safeComponentId = makePropSafe(providerId)
|
||||||
|
|
||||||
// Create bindable properties for each schema field
|
// Create bindable properties for each schema field
|
||||||
keys.forEach(key => {
|
keys.forEach(key => {
|
||||||
|
@ -235,7 +235,7 @@ const getProviderContextBindings = (asset, dataProviders) => {
|
||||||
// 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,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -333,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)
|
||||||
|
@ -342,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) {
|
||||||
|
@ -525,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]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -620,6 +620,9 @@ export const getFrontendStore = () => {
|
||||||
if (!name || !component) {
|
if (!name || !component) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (component[name] === value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
component[name] = value
|
component[name] = value
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.selectedComponentId = component._id
|
state.selectedComponentId = component._id
|
||||||
|
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>
|
|
@ -62,6 +62,7 @@
|
||||||
let indexes = [...($tables.selected.indexes || [])]
|
let indexes = [...($tables.selected.indexes || [])]
|
||||||
let confirmDeleteDialog
|
let confirmDeleteDialog
|
||||||
let deletion
|
let deletion
|
||||||
|
let deleteColName
|
||||||
|
|
||||||
$: checkConstraints(field)
|
$: checkConstraints(field)
|
||||||
$: required = !!field?.constraints?.presence || primaryDisplay
|
$: required = !!field?.constraints?.presence || primaryDisplay
|
||||||
|
@ -179,6 +180,7 @@
|
||||||
|
|
||||||
function hideDeleteDialog() {
|
function hideDeleteDialog() {
|
||||||
confirmDeleteDialog.hide()
|
confirmDeleteDialog.hide()
|
||||||
|
deleteColName = ""
|
||||||
deletion = false
|
deletion = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -408,9 +410,20 @@
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
bind:this={confirmDeleteDialog}
|
bind:this={confirmDeleteDialog}
|
||||||
body={`Are you sure you wish to delete this column? Your data will be deleted and this action cannot be undone.`}
|
|
||||||
okText="Delete Column"
|
okText="Delete Column"
|
||||||
onOk={deleteColumn}
|
onOk={deleteColumn}
|
||||||
onCancel={hideDeleteDialog}
|
onCancel={hideDeleteDialog}
|
||||||
title="Confirm Deletion"
|
title="Confirm Deletion"
|
||||||
/>
|
disabled={deleteColName !== field.name}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Are you sure you wish to delete the column <b>{field.name}?</b>
|
||||||
|
Your data will be deleted and this action cannot be undone - enter the column
|
||||||
|
name to confirm.
|
||||||
|
</p>
|
||||||
|
<Input
|
||||||
|
dataCy="delete-column-confirm"
|
||||||
|
bind:value={deleteColName}
|
||||||
|
placeholder={field.name}
|
||||||
|
/>
|
||||||
|
</ConfirmDialog>
|
||||||
|
|
|
@ -69,6 +69,7 @@
|
||||||
({ _id }) => _id === $views.selected?.tableId
|
({ _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}
|
||||||
|
|
|
@ -21,8 +21,10 @@
|
||||||
let originalName = table.name
|
let originalName = table.name
|
||||||
let templateScreens
|
let templateScreens
|
||||||
let willBeDeleted
|
let willBeDeleted
|
||||||
|
let deleteTableName
|
||||||
|
|
||||||
$: internal = table?.type === "internal"
|
$: external = table?.type === "external"
|
||||||
|
$: allowDeletion = !external || table?.created
|
||||||
|
|
||||||
function showDeleteModal() {
|
function showDeleteModal() {
|
||||||
templateScreens = $allScreens.filter(
|
templateScreens = $allScreens.filter(
|
||||||
|
@ -36,18 +38,26 @@
|
||||||
|
|
||||||
async function deleteTable() {
|
async function deleteTable() {
|
||||||
const wasSelectedTable = $tables.selected
|
const wasSelectedTable = $tables.selected
|
||||||
await tables.delete(table)
|
try {
|
||||||
store.actions.screens.delete(templateScreens)
|
await tables.delete(table)
|
||||||
await tables.fetch()
|
await store.actions.screens.delete(templateScreens)
|
||||||
notifications.success("Table deleted")
|
await tables.fetch()
|
||||||
if (table.type === "external") {
|
if (table.type === "external") {
|
||||||
await datasources.fetch()
|
await datasources.fetch()
|
||||||
}
|
}
|
||||||
if (wasSelectedTable && wasSelectedTable._id === table._id) {
|
notifications.success("Table deleted")
|
||||||
$goto("./table")
|
if (wasSelectedTable && wasSelectedTable._id === table._id) {
|
||||||
|
$goto("./table")
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
notifications.error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hideDeleteDialog() {
|
||||||
|
deleteTableName = ""
|
||||||
|
}
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
await tables.save(table)
|
await tables.save(table)
|
||||||
notifications.success("Table renamed successfully")
|
notifications.success("Table renamed successfully")
|
||||||
|
@ -66,10 +76,12 @@
|
||||||
<div slot="control" class="icon">
|
<div slot="control" class="icon">
|
||||||
<Icon s hoverable name="MoreSmallList" />
|
<Icon s hoverable name="MoreSmallList" />
|
||||||
</div>
|
</div>
|
||||||
{#if internal}
|
{#if external}
|
||||||
<MenuItem icon="Edit" on:click={editorModal.show}>Edit</MenuItem>
|
<MenuItem icon="Edit" on:click={editorModal.show}>Edit</MenuItem>
|
||||||
{/if}
|
{/if}
|
||||||
<MenuItem icon="Delete" on:click={showDeleteModal}>Delete</MenuItem>
|
{#if allowDeletion}
|
||||||
|
<MenuItem icon="Delete" on:click={showDeleteModal}>Delete</MenuItem>
|
||||||
|
{/if}
|
||||||
</ActionMenu>
|
</ActionMenu>
|
||||||
|
|
||||||
<Modal bind:this={editorModal}>
|
<Modal bind:this={editorModal}>
|
||||||
|
@ -92,11 +104,15 @@
|
||||||
bind:this={confirmDeleteDialog}
|
bind:this={confirmDeleteDialog}
|
||||||
okText="Delete Table"
|
okText="Delete Table"
|
||||||
onOk={deleteTable}
|
onOk={deleteTable}
|
||||||
|
onCancel={hideDeleteDialog}
|
||||||
title="Confirm Deletion"
|
title="Confirm Deletion"
|
||||||
|
disabled={deleteTableName !== table.name}
|
||||||
>
|
>
|
||||||
Are you sure you wish to delete the table
|
<p>
|
||||||
<i>{table.name}?</i>
|
Are you sure you wish to delete the table
|
||||||
The following will also be deleted:
|
<b>{table.name}?</b>
|
||||||
|
The following will also be deleted:
|
||||||
|
</p>
|
||||||
<b>
|
<b>
|
||||||
<div class="delete-items">
|
<div class="delete-items">
|
||||||
{#each willBeDeleted as item}
|
{#each willBeDeleted as item}
|
||||||
|
@ -104,7 +120,15 @@
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</b>
|
</b>
|
||||||
This action cannot be undone.
|
<p>
|
||||||
|
This action cannot be undone - to continue please enter the table name below
|
||||||
|
to confirm.
|
||||||
|
</p>
|
||||||
|
<Input
|
||||||
|
bind:value={deleteTableName}
|
||||||
|
placeholder={table.name}
|
||||||
|
dataCy="delete-table-confirm"
|
||||||
|
/>
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -32,15 +32,13 @@
|
||||||
.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
|
// Messages that can be sent from the iframe preview to the builder
|
||||||
// Budibase events are and initalisation events
|
// Budibase events are and initalisation events
|
||||||
const MessageTypes = {
|
const MessageTypes = {
|
||||||
IFRAME_LOADED: "iframe-loaded",
|
|
||||||
READY: "ready",
|
READY: "ready",
|
||||||
ERROR: "error",
|
ERROR: "error",
|
||||||
BUDIBASE: "type",
|
BUDIBASE: "type",
|
||||||
KEYDOWN: "keydown"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct iframe template
|
// Construct iframe template
|
||||||
|
@ -69,7 +67,7 @@
|
||||||
theme: $store.theme,
|
theme: $store.theme,
|
||||||
customTheme: $store.customTheme,
|
customTheme: $store.customTheme,
|
||||||
previewDevice: $store.previewDevice,
|
previewDevice: $store.previewDevice,
|
||||||
messagePassing: $store.clientFeatures.messagePassing
|
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.
|
||||||
|
@ -111,7 +109,6 @@
|
||||||
loading = false
|
loading = false
|
||||||
error = event.error || "An unknown error occurred"
|
error = event.error || "An unknown error occurred"
|
||||||
},
|
},
|
||||||
[MessageTypes.KEYDOWN]: handleKeydownEvent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageHandler = handlers[message.data.type] || handleBudibaseEvent
|
const messageHandler = handlers[message.data.type] || handleBudibaseEvent
|
||||||
|
@ -122,16 +119,25 @@
|
||||||
window.addEventListener("message", receiveMessage)
|
window.addEventListener("message", receiveMessage)
|
||||||
if (!$store.clientFeatures.messagePassing) {
|
if (!$store.clientFeatures.messagePassing) {
|
||||||
// Legacy - remove in later versions of BB
|
// Legacy - remove in later versions of BB
|
||||||
iframe.contentWindow.addEventListener("ready", () => {
|
iframe.contentWindow.addEventListener(
|
||||||
receiveMessage({ data: { type: MessageTypes.READY }})
|
"ready",
|
||||||
}, { once: true })
|
() => {
|
||||||
iframe.contentWindow.addEventListener("error", event => {
|
receiveMessage({ data: { type: MessageTypes.READY } })
|
||||||
receiveMessage({ data: { type: MessageTypes.ERROR, error: event.detail }})
|
},
|
||||||
}, { once: true })
|
{ 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
|
// Add listener for events sent by client library in preview
|
||||||
iframe.contentWindow.addEventListener("bb-event", handleBudibaseEvent)
|
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
|
||||||
|
@ -140,14 +146,20 @@
|
||||||
window.removeEventListener("message", receiveMessage)
|
window.removeEventListener("message", receiveMessage)
|
||||||
if (!$store.clientFeatures.messagePassing) {
|
if (!$store.clientFeatures.messagePassing) {
|
||||||
// Legacy - remove in later versions of BB
|
// Legacy - remove in later versions of BB
|
||||||
iframe.contentWindow.removeEventListener("bb-event", handleBudibaseEvent)
|
iframe.contentWindow.removeEventListener(
|
||||||
iframe.contentWindow.removeEventListener("keydown", handleKeydownEvent)
|
"bb-event",
|
||||||
|
handleBudibaseEvent
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleBudibaseEvent = event => {
|
const handleBudibaseEvent = event => {
|
||||||
const { type, data } = event.data || event.detail
|
const { type, data } = event.data || event.detail
|
||||||
|
if (!type) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
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") {
|
||||||
|
@ -183,19 +195,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleKeydownEvent = event => {
|
|
||||||
const { key } = event.data || event
|
|
||||||
if (
|
|
||||||
(key === "Delete" || key === "Backspace") &&
|
|
||||||
selectedComponentId &&
|
|
||||||
["input", "textarea"].indexOf(
|
|
||||||
iframe.contentWindow.document.activeElement?.tagName.toLowerCase()
|
|
||||||
) === -1
|
|
||||||
) {
|
|
||||||
confirmDeleteComponent(selectedComponentId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirmDeleteComponent = componentId => {
|
const confirmDeleteComponent = componentId => {
|
||||||
idToDelete = componentId
|
idToDelete = componentId
|
||||||
confirmDeleteDialog.show()
|
confirmDeleteDialog.show()
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
"icon": "Article",
|
"icon": "Article",
|
||||||
"children": [
|
"children": [
|
||||||
"tableblock",
|
"tableblock",
|
||||||
"cardsblock"
|
"cardsblock",
|
||||||
|
"repeaterblock"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"section",
|
"section",
|
||||||
|
|
|
@ -84,7 +84,6 @@ export default `
|
||||||
if (window.loadBudibase) {
|
if (window.loadBudibase) {
|
||||||
window.loadBudibase()
|
window.loadBudibase()
|
||||||
document.documentElement.classList.add("loaded")
|
document.documentElement.classList.add("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"
|
||||||
}
|
}
|
||||||
|
@ -94,10 +93,6 @@ export default `
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("message", receiveMessage)
|
window.addEventListener("message", receiveMessage)
|
||||||
window.addEventListener("keydown", evt => {
|
|
||||||
window.parent.postMessage({ type: "keydown", key: event.key })
|
|
||||||
})
|
|
||||||
|
|
||||||
window.parent.postMessage({ type: "ready" })
|
window.parent.postMessage({ type: "ready" })
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -13,6 +13,12 @@
|
||||||
$: noChildrenAllowed = !component || !definition?.hasChildren
|
$: noChildrenAllowed = !component || !definition?.hasChildren
|
||||||
$: noPaste = !$store.componentToPaste
|
$: noPaste = !$store.componentToPaste
|
||||||
|
|
||||||
|
// "editable" has been repurposed for inline text editing.
|
||||||
|
// It remains here for legacy compatibility.
|
||||||
|
// Future components should define "static": true for indicate they should
|
||||||
|
// not show a context menu.
|
||||||
|
$: showMenu = definition?.editable !== false && definition?.static !== true
|
||||||
|
|
||||||
const moveUpComponent = () => {
|
const moveUpComponent = () => {
|
||||||
const asset = get(currentAsset)
|
const asset = get(currentAsset)
|
||||||
const parent = findComponentParent(asset.props, component._id)
|
const parent = findComponentParent(asset.props, component._id)
|
||||||
|
@ -69,7 +75,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if definition?.editable !== false}
|
{#if showMenu}
|
||||||
<ActionMenu>
|
<ActionMenu>
|
||||||
<div slot="control" class="icon">
|
<div slot="control" class="icon">
|
||||||
<Icon size="S" hoverable name="MoreSmallList" />
|
<Icon size="S" hoverable name="MoreSmallList" />
|
||||||
|
|
|
@ -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>
|
|
@ -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">
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
} from "builderStore/dataBinding"
|
} from "builderStore/dataBinding"
|
||||||
|
|
||||||
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 = ""
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
$: setupComplete =
|
$: setupComplete =
|
||||||
$datasources.list.find(x => (x._id = "bb_internal")).entities.length > 1 ||
|
$datasources.list.find(x => (x._id = "bb_internal"))?.entities.length > 1 ||
|
||||||
$datasources.list.length > 1
|
$datasources.list.length > 1
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -165,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 => {
|
||||||
|
@ -298,12 +299,17 @@
|
||||||
title="Confirm deletion"
|
title="Confirm deletion"
|
||||||
okText="Delete app"
|
okText="Delete app"
|
||||||
onOk={confirmDeleteApp}
|
onOk={confirmDeleteApp}
|
||||||
|
onCancel={() => (appName = null)}
|
||||||
disabled={appName !== selectedApp?.name}
|
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>
|
<p>Please enter the app name below to confirm.</p>
|
||||||
<Input bind:value={appName} data-cy="delete-app-confirmation" />
|
<Input
|
||||||
|
bind:value={appName}
|
||||||
|
data-cy="delete-app-confirmation"
|
||||||
|
placeholder={selectedApp?.name}
|
||||||
|
/>
|
||||||
</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
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { writable, get } from "svelte/store"
|
import { get, writable } from "svelte/store"
|
||||||
import { views, queries, datasources } from "./"
|
import { datasources, queries, views } 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"
|
import { SWITCHABLE_TYPES } from "../../constants/backend"
|
||||||
|
@ -97,7 +97,12 @@ export function createTablesStore() {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
delete: async table => {
|
delete: async table => {
|
||||||
await api.delete(`/api/tables/${table._id}/${table._rev}`)
|
const response = await api.delete(
|
||||||
|
`/api/tables/${table._id}/${table._rev}`
|
||||||
|
)
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw (await response.json()).message
|
||||||
|
}
|
||||||
update(state => ({
|
update(state => ({
|
||||||
...state,
|
...state,
|
||||||
list: state.list.filter(existing => existing._id !== table._id),
|
list: state.list.filter(existing => existing._id !== table._id),
|
||||||
|
|
|
@ -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.185-alpha.0",
|
"version": "0.9.185-alpha.14",
|
||||||
"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
|
@ -240,13 +240,15 @@
|
||||||
"name": "Screenslot",
|
"name": "Screenslot",
|
||||||
"icon": "WebPage",
|
"icon": "WebPage",
|
||||||
"description": "Contains your app screens",
|
"description": "Contains your app screens",
|
||||||
"editable": false
|
"static": true
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
"name": "Button",
|
"name": "Button",
|
||||||
"description": "A basic html button that is ready for styling",
|
"description": "A basic html button that is ready for styling",
|
||||||
"icon": "Button",
|
"icon": "Button",
|
||||||
|
"editable": true,
|
||||||
"illegalChildren": ["section"],
|
"illegalChildren": ["section"],
|
||||||
|
"showSettingsBar": true,
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -255,6 +257,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
|
"showInBar": true,
|
||||||
"label": "Variant",
|
"label": "Variant",
|
||||||
"key": "type",
|
"key": "type",
|
||||||
"options": [
|
"options": [
|
||||||
|
@ -283,6 +286,7 @@
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"label": "Size",
|
"label": "Size",
|
||||||
|
"showInBar": true,
|
||||||
"key": "size",
|
"key": "size",
|
||||||
"options": [
|
"options": [
|
||||||
{
|
{
|
||||||
|
@ -307,11 +311,18 @@
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Quiet",
|
"label": "Quiet",
|
||||||
"key": "quiet"
|
"key": "quiet",
|
||||||
|
"showInBar": true,
|
||||||
|
"barIcon": "VisibilityOff",
|
||||||
|
"barTitle": "Quiet variant",
|
||||||
|
"barSeparator": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Disabled",
|
"label": "Disabled",
|
||||||
|
"showInBar": true,
|
||||||
|
"barIcon": "NoEdit",
|
||||||
|
"barTitle": "Disable button",
|
||||||
"key": "disabled"
|
"key": "disabled"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -590,6 +601,7 @@
|
||||||
"icon": "TextParagraph",
|
"icon": "TextParagraph",
|
||||||
"illegalChildren": ["section"],
|
"illegalChildren": ["section"],
|
||||||
"showSettingsBar": true,
|
"showSettingsBar": true,
|
||||||
|
"editable": true,
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -696,6 +708,7 @@
|
||||||
"description": "A component for displaying heading text",
|
"description": "A component for displaying heading text",
|
||||||
"illegalChildren": ["section"],
|
"illegalChildren": ["section"],
|
||||||
"showSettingsBar": true,
|
"showSettingsBar": true,
|
||||||
|
"editable": true,
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -940,6 +953,7 @@
|
||||||
"description": "A basic link component for internal and external links",
|
"description": "A basic link component for internal and external links",
|
||||||
"icon": "Link",
|
"icon": "Link",
|
||||||
"showSettingsBar": true,
|
"showSettingsBar": true,
|
||||||
|
"editable": true,
|
||||||
"illegalChildren": ["section"],
|
"illegalChildren": ["section"],
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
|
@ -1831,6 +1845,7 @@
|
||||||
"icon": "Text",
|
"icon": "Text",
|
||||||
"illegalChildren": ["section"],
|
"illegalChildren": ["section"],
|
||||||
"styles": ["size"],
|
"styles": ["size"],
|
||||||
|
"editable": true,
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "field/string",
|
"type": "field/string",
|
||||||
|
@ -1869,6 +1884,7 @@
|
||||||
"name": "Number Field",
|
"name": "Number Field",
|
||||||
"icon": "123",
|
"icon": "123",
|
||||||
"styles": ["size"],
|
"styles": ["size"],
|
||||||
|
"editable": true,
|
||||||
"illegalChildren": ["section"],
|
"illegalChildren": ["section"],
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
|
@ -1908,6 +1924,7 @@
|
||||||
"name": "Password Field",
|
"name": "Password Field",
|
||||||
"icon": "LockClosed",
|
"icon": "LockClosed",
|
||||||
"styles": ["size"],
|
"styles": ["size"],
|
||||||
|
"editable": true,
|
||||||
"illegalChildren": ["section"],
|
"illegalChildren": ["section"],
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
|
@ -1947,6 +1964,7 @@
|
||||||
"name": "Options Picker",
|
"name": "Options Picker",
|
||||||
"icon": "ViewList",
|
"icon": "ViewList",
|
||||||
"styles": ["size"],
|
"styles": ["size"],
|
||||||
|
"editable": true,
|
||||||
"illegalChildren": ["section"],
|
"illegalChildren": ["section"],
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
|
@ -2070,6 +2088,7 @@
|
||||||
"name": "Multi-select Picker",
|
"name": "Multi-select Picker",
|
||||||
"icon": "ViewList",
|
"icon": "ViewList",
|
||||||
"styles": ["size"],
|
"styles": ["size"],
|
||||||
|
"editable": true,
|
||||||
"illegalChildren": ["section"],
|
"illegalChildren": ["section"],
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
|
@ -2171,6 +2190,7 @@
|
||||||
"booleanfield": {
|
"booleanfield": {
|
||||||
"name": "Checkbox",
|
"name": "Checkbox",
|
||||||
"icon": "Checkmark",
|
"icon": "Checkmark",
|
||||||
|
"editable": true,
|
||||||
"illegalChildren": ["section"],
|
"illegalChildren": ["section"],
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
|
@ -2234,6 +2254,7 @@
|
||||||
"name": "Rich Text",
|
"name": "Rich Text",
|
||||||
"icon": "TextParagraph",
|
"icon": "TextParagraph",
|
||||||
"styles": ["size"],
|
"styles": ["size"],
|
||||||
|
"editable": true,
|
||||||
"illegalChildren": ["section"],
|
"illegalChildren": ["section"],
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
|
@ -2274,6 +2295,7 @@
|
||||||
"name": "Date Picker",
|
"name": "Date Picker",
|
||||||
"icon": "DateInput",
|
"icon": "DateInput",
|
||||||
"styles": ["size"],
|
"styles": ["size"],
|
||||||
|
"editable": true,
|
||||||
"illegalChildren": ["section"],
|
"illegalChildren": ["section"],
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
|
@ -2319,6 +2341,7 @@
|
||||||
"name": "Attachment",
|
"name": "Attachment",
|
||||||
"icon": "Attach",
|
"icon": "Attach",
|
||||||
"styles": ["size"],
|
"styles": ["size"],
|
||||||
|
"editable": true,
|
||||||
"illegalChildren": ["section"],
|
"illegalChildren": ["section"],
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
|
@ -2348,6 +2371,7 @@
|
||||||
"name": "Relationship Picker",
|
"name": "Relationship Picker",
|
||||||
"icon": "TaskList",
|
"icon": "TaskList",
|
||||||
"styles": ["size"],
|
"styles": ["size"],
|
||||||
|
"editable": true,
|
||||||
"illegalChildren": ["section"],
|
"illegalChildren": ["section"],
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
|
@ -2933,5 +2957,203 @@
|
||||||
"type": "schema",
|
"type": "schema",
|
||||||
"suffix": "repeater"
|
"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.185-alpha.0",
|
"version": "0.9.185-alpha.14",
|
||||||
"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.185-alpha.0",
|
"@budibase/bbui": "^0.9.185-alpha.14",
|
||||||
"@budibase/standard-components": "^0.9.139",
|
"@budibase/standard-components": "^0.9.139",
|
||||||
"@budibase/string-templates": "^0.9.185-alpha.0",
|
"@budibase/string-templates": "^0.9.185-alpha.14",
|
||||||
"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"
|
||||||
|
|
|
@ -55,6 +55,26 @@ export const fetchDatasourceSchema = async dataSource => {
|
||||||
return dataSource.value?.schema
|
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
|
// Tables, views and links can be fetched by table ID
|
||||||
if (
|
if (
|
||||||
(type === "table" || type === "view" || type === "link") &&
|
(type === "table" || type === "view" || type === "link") &&
|
||||||
|
|
|
@ -30,6 +30,6 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Component {instance}>
|
<Component {instance} isBlock>
|
||||||
<slot />
|
<slot />
|
||||||
</Component>
|
</Component>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { writable } from "svelte/store"
|
import { writable, get } from "svelte/store"
|
||||||
import { setContext, onMount } from "svelte"
|
import { setContext, onMount } from "svelte"
|
||||||
import { Layout, Heading, Body } from "@budibase/bbui"
|
import { Layout, Heading, Body } from "@budibase/bbui"
|
||||||
import Component from "./Component.svelte"
|
import Component from "./Component.svelte"
|
||||||
|
@ -25,6 +25,7 @@
|
||||||
import CustomThemeWrapper from "./CustomThemeWrapper.svelte"
|
import CustomThemeWrapper from "./CustomThemeWrapper.svelte"
|
||||||
import DNDHandler from "components/preview/DNDHandler.svelte"
|
import DNDHandler from "components/preview/DNDHandler.svelte"
|
||||||
import ErrorSVG from "builder/assets/error.svg"
|
import ErrorSVG from "builder/assets/error.svg"
|
||||||
|
import KeyboardManager from "components/preview/KeyboardManager.svelte"
|
||||||
|
|
||||||
// Provide contexts
|
// Provide contexts
|
||||||
setContext("sdk", SDK)
|
setContext("sdk", SDK)
|
||||||
|
@ -39,7 +40,7 @@
|
||||||
await initialise()
|
await initialise()
|
||||||
await authStore.actions.fetchUser()
|
await authStore.actions.fetchUser()
|
||||||
dataLoaded = true
|
dataLoaded = true
|
||||||
if ($builderStore.inBuilder) {
|
if (get(builderStore).inBuilder) {
|
||||||
builderStore.actions.notifyLoaded()
|
builderStore.actions.notifyLoaded()
|
||||||
} else {
|
} else {
|
||||||
builderStore.actions.pingEndUser()
|
builderStore.actions.pingEndUser()
|
||||||
|
@ -143,6 +144,7 @@
|
||||||
</UserBindingsProvider>
|
</UserBindingsProvider>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
<KeyboardManager />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getContext, setContext } from "svelte"
|
import { getContext, setContext } from "svelte"
|
||||||
import { writable, get } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
import * as AppComponents from "components/app"
|
import * as AppComponents from "components/app"
|
||||||
import Router from "./Router.svelte"
|
import Router from "./Router.svelte"
|
||||||
import { enrichProps, propsAreSame } from "utils/componentProps"
|
import { enrichProps, propsAreSame } from "utils/componentProps"
|
||||||
|
@ -17,16 +17,24 @@
|
||||||
export let instance = {}
|
export let instance = {}
|
||||||
export let isLayout = false
|
export let isLayout = false
|
||||||
export let isScreen = false
|
export let isScreen = false
|
||||||
|
export let isBlock = false
|
||||||
|
|
||||||
|
// Component settings are the un-enriched settings for this component that
|
||||||
|
// need to be enriched at this level.
|
||||||
|
// Nested settings are the un-enriched block settings that are to be passed on
|
||||||
|
// and enriched at a deeper level.
|
||||||
|
let componentSettings
|
||||||
|
let nestedSettings
|
||||||
|
|
||||||
// The enriched component settings
|
// The enriched component settings
|
||||||
let enrichedSettings
|
let enrichedSettings
|
||||||
|
|
||||||
// Any prop overrides that need to be applied due to conditional UI
|
// Any setting overrides that need to be applied due to conditional UI
|
||||||
let conditionalSettings
|
let conditionalSettings
|
||||||
|
|
||||||
// Settings are hashed when inside the builder preview and used as a key,
|
// Resultant cached settings which will be passed to the component instance.
|
||||||
// so that components fully remount whenever any settings change
|
// These are a combination of the enriched, nested and conditional settings.
|
||||||
let hash = 0
|
let cachedSettings
|
||||||
|
|
||||||
// Latest timestamp that we started a props update.
|
// Latest timestamp that we started a props update.
|
||||||
// Due to enrichment now being async, we need to avoid overwriting newer
|
// Due to enrichment now being async, we need to avoid overwriting newer
|
||||||
|
@ -44,7 +52,6 @@
|
||||||
// Get contexts
|
// Get contexts
|
||||||
const context = getContext("context")
|
const context = getContext("context")
|
||||||
const insideScreenslot = !!getContext("screenslot")
|
const insideScreenslot = !!getContext("screenslot")
|
||||||
const insideBlock = !!getContext("block")
|
|
||||||
|
|
||||||
// Create component context
|
// Create component context
|
||||||
const componentStore = writable({})
|
const componentStore = writable({})
|
||||||
|
@ -63,14 +70,17 @@
|
||||||
$: selected =
|
$: selected =
|
||||||
$builderStore.inBuilder && $builderStore.selectedComponentId === id
|
$builderStore.inBuilder && $builderStore.selectedComponentId === id
|
||||||
$: inSelectedPath = $builderStore.selectedComponentPath?.includes(id)
|
$: inSelectedPath = $builderStore.selectedComponentPath?.includes(id)
|
||||||
|
$: inDragPath = inSelectedPath && $builderStore.editMode
|
||||||
|
|
||||||
// Interactive components can be selected, dragged and highlighted inside
|
// Interactive components can be selected, dragged and highlighted inside
|
||||||
// the builder preview
|
// the builder preview
|
||||||
$: interactive =
|
$: interactive =
|
||||||
$builderStore.inBuilder &&
|
$builderStore.inBuilder &&
|
||||||
($builderStore.previewType === "layout" || insideScreenslot) &&
|
($builderStore.previewType === "layout" || insideScreenslot) &&
|
||||||
!insideBlock
|
!isBlock
|
||||||
$: draggable = interactive && !isLayout && !isScreen
|
$: editable = definition?.editable
|
||||||
|
$: editing = editable && selected && $builderStore.editMode
|
||||||
|
$: draggable = !inDragPath && interactive && !isLayout && !isScreen
|
||||||
$: droppable = interactive && !isLayout && !isScreen
|
$: droppable = interactive && !isLayout && !isScreen
|
||||||
|
|
||||||
// Empty components are those which accept children but do not have any.
|
// Empty components are those which accept children but do not have any.
|
||||||
|
@ -79,44 +89,39 @@
|
||||||
$: empty = interactive && !children.length && definition?.hasChildren
|
$: empty = interactive && !children.length && definition?.hasChildren
|
||||||
$: emptyState = empty && definition?.showEmptyState !== false
|
$: emptyState = empty && definition?.showEmptyState !== false
|
||||||
|
|
||||||
// Raw props are all props excluding internal props and children
|
// Raw settings are all settings excluding internal props and children
|
||||||
$: rawSettings = getRawSettings(instance)
|
$: rawSettings = getRawSettings(instance)
|
||||||
$: instanceKey = hashString(JSON.stringify(rawSettings))
|
$: instanceKey = hashString(JSON.stringify(rawSettings))
|
||||||
|
|
||||||
// Component settings are those which are intended for this component and
|
// Update and enrich component settings
|
||||||
// which need to be enriched
|
$: updateSettings(rawSettings, instanceKey, settingsDefinition, $context)
|
||||||
$: componentSettings = getComponentSettings(rawSettings, settingsDefinition)
|
|
||||||
$: enrichComponentSettings(rawSettings, instanceKey, $context)
|
|
||||||
|
|
||||||
// Nested settings are those which are intended for child components inside
|
|
||||||
// blocks and which should not be enriched at this level
|
|
||||||
$: nestedSettings = getNestedSettings(rawSettings, settingsDefinition)
|
|
||||||
|
|
||||||
// Evaluate conditional UI settings and store any component setting changes
|
// Evaluate conditional UI settings and store any component setting changes
|
||||||
// which need to be made
|
// which need to be made
|
||||||
$: evaluateConditions(enrichedSettings?._conditions)
|
$: evaluateConditions(enrichedSettings?._conditions)
|
||||||
|
|
||||||
// Build up the final settings object to be passed to the component
|
// Build up the final settings object to be passed to the component
|
||||||
$: settings = {
|
$: cacheSettings(enrichedSettings, nestedSettings, conditionalSettings)
|
||||||
...enrichedSettings,
|
|
||||||
...nestedSettings,
|
|
||||||
...conditionalSettings,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render key is used when in the builder preview to fully remount
|
|
||||||
// components when settings are changed
|
|
||||||
$: renderKey = `${hash}-${emptyState}`
|
|
||||||
|
|
||||||
// Update component context
|
// Update component context
|
||||||
$: componentStore.set({
|
$: componentStore.set({
|
||||||
id,
|
id,
|
||||||
children: children.length,
|
children: children.length,
|
||||||
styles: { ...instance._styles, id, empty: emptyState, interactive },
|
styles: {
|
||||||
|
...instance._styles,
|
||||||
|
id,
|
||||||
|
empty: emptyState,
|
||||||
|
interactive,
|
||||||
|
draggable,
|
||||||
|
editable,
|
||||||
|
},
|
||||||
empty: emptyState,
|
empty: emptyState,
|
||||||
selected,
|
selected,
|
||||||
name,
|
name,
|
||||||
|
editing,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Extracts all settings from the component instance
|
||||||
const getRawSettings = instance => {
|
const getRawSettings = instance => {
|
||||||
let validSettings = {}
|
let validSettings = {}
|
||||||
Object.entries(instance)
|
Object.entries(instance)
|
||||||
|
@ -137,12 +142,14 @@
|
||||||
return AppComponents[name]
|
return AppComponents[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gets this component's definition from the manifest
|
||||||
const getComponentDefinition = component => {
|
const getComponentDefinition = component => {
|
||||||
const prefix = "@budibase/standard-components/"
|
const prefix = "@budibase/standard-components/"
|
||||||
const type = component?.replace(prefix, "")
|
const type = component?.replace(prefix, "")
|
||||||
return type ? Manifest[type] : null
|
return type ? Manifest[type] : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gets the definition of this component's settings from the manifest
|
||||||
const getSettingsDefinition = definition => {
|
const getSettingsDefinition = definition => {
|
||||||
if (!definition) {
|
if (!definition) {
|
||||||
return []
|
return []
|
||||||
|
@ -162,35 +169,50 @@
|
||||||
return settings
|
return settings
|
||||||
}
|
}
|
||||||
|
|
||||||
const getComponentSettings = (rawSettings, settingsDefinition) => {
|
// Updates and enriches component settings when raw settings change
|
||||||
let clone = { ...rawSettings }
|
const updateSettings = (settings, key, settingsDefinition, context) => {
|
||||||
settingsDefinition?.forEach(setting => {
|
const instanceChanged = key !== lastInstanceKey
|
||||||
if (setting.nested) {
|
|
||||||
delete clone[setting.key]
|
// Derive component and nested settings if the instance changed
|
||||||
}
|
if (instanceChanged) {
|
||||||
})
|
splitRawSettings(settings, settingsDefinition)
|
||||||
return clone
|
}
|
||||||
|
|
||||||
|
// Enrich component settings
|
||||||
|
enrichComponentSettings(componentSettings, context, instanceChanged)
|
||||||
|
|
||||||
|
// Update instance key
|
||||||
|
if (instanceChanged) {
|
||||||
|
lastInstanceKey = key
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getNestedSettings = (rawSettings, settingsDefinition) => {
|
// Splits the raw settings into those destined for the component itself
|
||||||
let clone = { ...rawSettings }
|
// and nexted settings for child components inside blocks
|
||||||
|
const splitRawSettings = (rawSettings, settingsDefinition) => {
|
||||||
|
let newComponentSettings = { ...rawSettings }
|
||||||
|
let newNestedSettings = { ...rawSettings }
|
||||||
settingsDefinition?.forEach(setting => {
|
settingsDefinition?.forEach(setting => {
|
||||||
if (!setting.nested) {
|
if (setting.nested) {
|
||||||
delete clone[setting.key]
|
delete newComponentSettings[setting.key]
|
||||||
|
} else {
|
||||||
|
delete newNestedSettings[setting.key]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return clone
|
componentSettings = newComponentSettings
|
||||||
|
nestedSettings = newNestedSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enriches any string component props using handlebars
|
// Enriches any string component props using handlebars
|
||||||
const enrichComponentSettings = (rawSettings, instanceKey, context) => {
|
const enrichComponentSettings = (rawSettings, context, instanceChanged) => {
|
||||||
const instanceSame = instanceKey === lastInstanceKey
|
const contextChanged = context.key !== lastContextKey
|
||||||
const contextSame = context.key === lastContextKey
|
|
||||||
|
|
||||||
if (instanceSame && contextSame) {
|
// Skip enrichment if the context and instance are unchanged
|
||||||
return
|
if (!contextChanged) {
|
||||||
|
if (!instanceChanged) {
|
||||||
|
return
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
lastInstanceKey = instanceKey
|
|
||||||
lastContextKey = context.key
|
lastContextKey = context.key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,31 +228,11 @@
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the component props.
|
enrichedSettings = newEnrichedSettings
|
||||||
// Most props are deeply compared so that svelte will only trigger reactive
|
|
||||||
// statements on props that have actually changed.
|
|
||||||
if (!newEnrichedSettings) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let propsChanged = false
|
|
||||||
if (!enrichedSettings) {
|
|
||||||
enrichedSettings = {}
|
|
||||||
propsChanged = true
|
|
||||||
}
|
|
||||||
Object.keys(newEnrichedSettings).forEach(key => {
|
|
||||||
if (!propsAreSame(newEnrichedSettings[key], enrichedSettings[key])) {
|
|
||||||
propsChanged = true
|
|
||||||
enrichedSettings[key] = newEnrichedSettings[key]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Update the hash if we're in the builder so we can fully remount this
|
|
||||||
// component
|
|
||||||
if (get(builderStore).inBuilder && propsChanged) {
|
|
||||||
hash = hashString(JSON.stringify(enrichedSettings))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Evaluates the list of conditional UI conditions and determines any setting
|
||||||
|
// or visibility changes required
|
||||||
const evaluateConditions = conditions => {
|
const evaluateConditions = conditions => {
|
||||||
if (!conditions?.length) {
|
if (!conditions?.length) {
|
||||||
return
|
return
|
||||||
|
@ -250,35 +252,51 @@
|
||||||
conditionalSettings = result.settingUpdates
|
conditionalSettings = result.settingUpdates
|
||||||
visible = nextVisible
|
visible = nextVisible
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Combines and caches all settings which will be passed to the component
|
||||||
|
// instance. Settings are aggressively memoized to avoid triggering svelte
|
||||||
|
// reactive statements as much as possible.
|
||||||
|
const cacheSettings = (enriched, nested, conditional) => {
|
||||||
|
const allSettings = { ...enriched, ...nested, ...conditional }
|
||||||
|
if (!cachedSettings) {
|
||||||
|
cachedSettings = allSettings
|
||||||
|
} else {
|
||||||
|
Object.keys(allSettings).forEach(key => {
|
||||||
|
if (!propsAreSame(allSettings[key], cachedSettings[key])) {
|
||||||
|
cachedSettings[key] = allSettings[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#key renderKey}
|
{#if constructor && cachedSettings && (visible || inSelectedPath)}
|
||||||
{#if constructor && settings && (visible || inSelectedPath)}
|
<!-- The ID is used as a class because getElementsByClassName is O(1) -->
|
||||||
<!-- The ID is used as a class because getElementsByClassName is O(1) -->
|
<!-- and the performance matters for the selection indicators -->
|
||||||
<!-- and the performance matters for the selection indicators -->
|
<div
|
||||||
<div
|
class={`component ${id}`}
|
||||||
class={`component ${id}`}
|
class:draggable
|
||||||
class:draggable
|
class:droppable
|
||||||
class:droppable
|
class:empty
|
||||||
class:empty
|
class:interactive
|
||||||
class:interactive
|
class:editing
|
||||||
data-id={id}
|
class:block={isBlock}
|
||||||
data-name={name}
|
data-id={id}
|
||||||
>
|
data-name={name}
|
||||||
<svelte:component this={constructor} {...settings}>
|
>
|
||||||
{#if children.length}
|
<svelte:component this={constructor} {...cachedSettings}>
|
||||||
{#each children as child (child._id)}
|
{#if children.length}
|
||||||
<svelte:self instance={child} />
|
{#each children as child (child._id)}
|
||||||
{/each}
|
<svelte:self instance={child} />
|
||||||
{:else if emptyState}
|
{/each}
|
||||||
<Placeholder />
|
{:else if emptyState}
|
||||||
{:else if insideBlock}
|
<Placeholder />
|
||||||
<slot />
|
{:else if isBlock}
|
||||||
{/if}
|
<slot />
|
||||||
</svelte:component>
|
{/if}
|
||||||
</div>
|
</svelte:component>
|
||||||
{/if}
|
</div>
|
||||||
{/key}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.component {
|
.component {
|
||||||
|
@ -290,4 +308,7 @@
|
||||||
.draggable :global(*:hover) {
|
.draggable :global(*:hover) {
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
}
|
}
|
||||||
|
.editing :global(*:hover) {
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -79,4 +79,9 @@
|
||||||
scrollbar-color: var(--spectrum-global-color-gray-400)
|
scrollbar-color: var(--spectrum-global-color-gray-400)
|
||||||
var(--spectrum-alias-background-color-default);
|
var(--spectrum-alias-background-color-default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Remove border when editing contenteditable components */
|
||||||
|
:global(*[contenteditable="true"]:focus) {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import "@spectrum-css/button/dist/index-vars.css"
|
import "@spectrum-css/button/dist/index-vars.css"
|
||||||
|
|
||||||
const { styleable } = getContext("sdk")
|
const { styleable, builderStore } = getContext("sdk")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
|
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
|
@ -11,16 +11,35 @@
|
||||||
export let size = "M"
|
export let size = "M"
|
||||||
export let type = "primary"
|
export let type = "primary"
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
|
|
||||||
|
let node
|
||||||
|
|
||||||
|
$: $component.editing && node?.focus()
|
||||||
|
$: componentText = getComponentText(text, $builderStore, $component)
|
||||||
|
|
||||||
|
const getComponentText = (text, builderState, componentState) => {
|
||||||
|
if (!builderState.inBuilder || componentState.editing) {
|
||||||
|
return text || " "
|
||||||
|
}
|
||||||
|
return text || componentState.name || "Placeholder text"
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateText = e => {
|
||||||
|
builderStore.actions.updateProp("text", e.target.textContent)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class={`spectrum-Button spectrum-Button--size${size} spectrum-Button--${type}`}
|
class={`spectrum-Button spectrum-Button--size${size} spectrum-Button--${type}`}
|
||||||
class:spectrum-Button--quiet={quiet}
|
class:spectrum-Button--quiet={quiet}
|
||||||
disabled={disabled || false}
|
{disabled}
|
||||||
use:styleable={$component.styles}
|
use:styleable={$component.styles}
|
||||||
on:click={onClick}
|
on:click={onClick}
|
||||||
|
contenteditable={$component.editing}
|
||||||
|
on:blur={$component.editing ? updateText : null}
|
||||||
|
bind:this={node}
|
||||||
>
|
>
|
||||||
{text || ""}
|
{componentText}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -34,12 +34,18 @@
|
||||||
let bookmarks = [null]
|
let bookmarks = [null]
|
||||||
let pageNumber = 0
|
let pageNumber = 0
|
||||||
let query = null
|
let query = null
|
||||||
|
let queryExtensions = {}
|
||||||
|
|
||||||
// Sorting can be overridden at run time, so we can't use the prop directly
|
// Sorting can be overridden at run time, so we can't use the prop directly
|
||||||
let currentSortColumn = sortColumn
|
let currentSortColumn = sortColumn
|
||||||
let currentSortOrder = sortOrder
|
let currentSortOrder = sortOrder
|
||||||
|
|
||||||
$: query = buildLuceneQuery(filter)
|
// Reset the current sort state to props if props change
|
||||||
|
$: currentSortColumn = sortColumn
|
||||||
|
$: currentSortOrder = sortOrder
|
||||||
|
|
||||||
|
$: defaultQuery = buildLuceneQuery(filter)
|
||||||
|
$: extendQuery(defaultQuery, queryExtensions)
|
||||||
$: internalTable = dataSource?.type === "table"
|
$: internalTable = dataSource?.type === "table"
|
||||||
$: nestedProvider = dataSource?.type === "provider"
|
$: nestedProvider = dataSource?.type === "provider"
|
||||||
$: hasNextPage = bookmarks[pageNumber + 1] != null
|
$: hasNextPage = bookmarks[pageNumber + 1] != null
|
||||||
|
@ -91,8 +97,12 @@
|
||||||
metadata: { dataSource },
|
metadata: { dataSource },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ActionTypes.SetDataProviderQuery,
|
type: ActionTypes.AddDataProviderQueryExtension,
|
||||||
callback: newQuery => (query = newQuery),
|
callback: addQueryExtension,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: ActionTypes.RemoveDataProviderQueryExtension,
|
||||||
|
callback: removeQueryExtension,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ActionTypes.SetDataProviderSorting,
|
type: ActionTypes.SetDataProviderSorting,
|
||||||
|
@ -183,7 +193,16 @@
|
||||||
} else if (dataSource?.type === "provider") {
|
} else if (dataSource?.type === "provider") {
|
||||||
// For providers referencing another provider, just use the rows it
|
// For providers referencing another provider, just use the rows it
|
||||||
// provides
|
// provides
|
||||||
allRows = dataSource?.value?.rows ?? []
|
allRows = dataSource?.value?.rows || []
|
||||||
|
} else if (dataSource?.type === "field") {
|
||||||
|
// Field sources will be available from context.
|
||||||
|
// Enrich non object elements into object to ensure a valid schema.
|
||||||
|
const data = dataSource?.value || []
|
||||||
|
if (Array.isArray(data) && data[0] && typeof data[0] !== "object") {
|
||||||
|
allRows = data.map(value => ({ value }))
|
||||||
|
} else {
|
||||||
|
allRows = data
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// For other data sources like queries or views, fetch all rows from the
|
// For other data sources like queries or views, fetch all rows from the
|
||||||
// server
|
// server
|
||||||
|
@ -255,6 +274,38 @@
|
||||||
pageNumber--
|
pageNumber--
|
||||||
allRows = res.rows
|
allRows = res.rows
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const addQueryExtension = (key, operator, field, value) => {
|
||||||
|
if (!key || !operator || !field) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const extension = { operator, field, value }
|
||||||
|
queryExtensions = { ...queryExtensions, [key]: extension }
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeQueryExtension = key => {
|
||||||
|
if (!key) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const newQueryExtensions = { ...queryExtensions }
|
||||||
|
delete newQueryExtensions[key]
|
||||||
|
queryExtensions = newQueryExtensions
|
||||||
|
}
|
||||||
|
|
||||||
|
const extendQuery = (defaultQuery, extensions) => {
|
||||||
|
const extensionValues = Object.values(extensions || {})
|
||||||
|
let extendedQuery = { ...defaultQuery }
|
||||||
|
extensionValues.forEach(({ operator, field, value }) => {
|
||||||
|
extendedQuery[operator] = {
|
||||||
|
...extendedQuery[operator],
|
||||||
|
[field]: value,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (JSON.stringify(query) !== JSON.stringify(extendedQuery)) {
|
||||||
|
query = extendedQuery
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div use:styleable={$component.styles} class="container">
|
<div use:styleable={$component.styles} class="container">
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import dayjs from "dayjs"
|
import dayjs from "dayjs"
|
||||||
import utc from "dayjs/plugin/utc"
|
import utc from "dayjs/plugin/utc"
|
||||||
import { onMount } from "svelte"
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
|
@ -14,7 +14,14 @@
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
const { styleable, ActionTypes, getAction } = getContext("sdk")
|
const { styleable, ActionTypes, getAction } = getContext("sdk")
|
||||||
|
|
||||||
const setQuery = getAction(dataProvider?.id, ActionTypes.SetDataProviderQuery)
|
$: addExtension = getAction(
|
||||||
|
dataProvider?.id,
|
||||||
|
ActionTypes.AddDataProviderQueryExtension
|
||||||
|
)
|
||||||
|
$: removeExtension = getAction(
|
||||||
|
dataProvider?.id,
|
||||||
|
ActionTypes.RemoveDataProviderQueryExtension
|
||||||
|
)
|
||||||
const options = [
|
const options = [
|
||||||
"Last 1 day",
|
"Last 1 day",
|
||||||
"Last 7 days",
|
"Last 7 days",
|
||||||
|
@ -25,44 +32,30 @@
|
||||||
]
|
]
|
||||||
let value = options.includes(defaultValue) ? defaultValue : "Last 30 days"
|
let value = options.includes(defaultValue) ? defaultValue : "Last 30 days"
|
||||||
|
|
||||||
const updateDateRange = option => {
|
$: queryExtension = getQueryExtension(value)
|
||||||
const query = dataProvider?.state?.query
|
$: addExtension?.($component.id, "range", field, queryExtension)
|
||||||
if (!query || !setQuery) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
value = option
|
const getQueryExtension = value => {
|
||||||
let low = dayjs.utc().subtract(1, "year")
|
let low = dayjs.utc().subtract(1, "year")
|
||||||
let high = dayjs.utc().add(1, "day")
|
let high = dayjs.utc().add(1, "day")
|
||||||
|
|
||||||
if (option === "Last 1 day") {
|
if (value === "Last 1 day") {
|
||||||
low = dayjs.utc().subtract(1, "day")
|
low = dayjs.utc().subtract(1, "day")
|
||||||
} else if (option === "Last 7 days") {
|
} else if (value === "Last 7 days") {
|
||||||
low = dayjs.utc().subtract(7, "days")
|
low = dayjs.utc().subtract(7, "days")
|
||||||
} else if (option === "Last 30 days") {
|
} else if (value === "Last 30 days") {
|
||||||
low = dayjs.utc().subtract(30, "days")
|
low = dayjs.utc().subtract(30, "days")
|
||||||
} else if (option === "Last 3 months") {
|
} else if (value === "Last 3 months") {
|
||||||
low = dayjs.utc().subtract(3, "months")
|
low = dayjs.utc().subtract(3, "months")
|
||||||
} else if (option === "Last 6 months") {
|
} else if (value === "Last 6 months") {
|
||||||
low = dayjs.utc().subtract(6, "months")
|
low = dayjs.utc().subtract(6, "months")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update data provider query with the new filter
|
return { low: low.format(), high: high.format() }
|
||||||
setQuery({
|
|
||||||
...query,
|
|
||||||
range: {
|
|
||||||
...query.range,
|
|
||||||
[field]: {
|
|
||||||
high: high.format(),
|
|
||||||
low: low.format(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the range on mount to the initial value
|
onDestroy(() => {
|
||||||
onMount(() => {
|
removeExtension?.($component.id)
|
||||||
updateDateRange(value)
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -71,6 +64,6 @@
|
||||||
placeholder={null}
|
placeholder={null}
|
||||||
{options}
|
{options}
|
||||||
{value}
|
{value}
|
||||||
on:change={e => updateDateRange(e.detail)}
|
on:change={e => (value = e.detail)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,10 +13,11 @@
|
||||||
export let underline
|
export let underline
|
||||||
export let size
|
export let size
|
||||||
|
|
||||||
$: placeholder = $builderStore.inBuilder && !text
|
let node
|
||||||
$: componentText = $builderStore.inBuilder
|
|
||||||
? text || $component.name || "Placeholder text"
|
$: $component.editing && node?.focus()
|
||||||
: text || ""
|
$: placeholder = $builderStore.inBuilder && !text && !$component.editing
|
||||||
|
$: componentText = getComponentText(text, $builderStore, $component)
|
||||||
$: sizeClass = `spectrum-Heading--size${size || "M"}`
|
$: sizeClass = `spectrum-Heading--size${size || "M"}`
|
||||||
$: alignClass = `align--${align || "left"}`
|
$: alignClass = `align--${align || "left"}`
|
||||||
|
|
||||||
|
@ -24,6 +25,13 @@
|
||||||
// overrides the color when it's passed as inline style.
|
// overrides the color when it's passed as inline style.
|
||||||
$: styles = enrichStyles($component.styles, color)
|
$: styles = enrichStyles($component.styles, color)
|
||||||
|
|
||||||
|
const getComponentText = (text, builderState, componentState) => {
|
||||||
|
if (!builderState.inBuilder || componentState.editing) {
|
||||||
|
return text || ""
|
||||||
|
}
|
||||||
|
return text || componentState.name || "Placeholder text"
|
||||||
|
}
|
||||||
|
|
||||||
const enrichStyles = (styles, color) => {
|
const enrichStyles = (styles, color) => {
|
||||||
if (!color) {
|
if (!color) {
|
||||||
return styles
|
return styles
|
||||||
|
@ -36,15 +44,24 @@
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert contenteditable HTML to text and save
|
||||||
|
const updateText = e => {
|
||||||
|
const sanitized = e.target.innerHTML.replace(/<br>/gi, "\n")
|
||||||
|
builderStore.actions.updateProp("text", sanitized)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1
|
<h1
|
||||||
|
bind:this={node}
|
||||||
|
contenteditable={$component.editing}
|
||||||
use:styleable={styles}
|
use:styleable={styles}
|
||||||
class:placeholder
|
class:placeholder
|
||||||
class:bold
|
class:bold
|
||||||
class:italic
|
class:italic
|
||||||
class:underline
|
class:underline
|
||||||
class="spectrum-Heading {sizeClass} {alignClass}"
|
class="spectrum-Heading {sizeClass} {alignClass}"
|
||||||
|
on:blur={$component.editing ? updateText : null}
|
||||||
>
|
>
|
||||||
{componentText}
|
{componentText}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
|
@ -14,19 +14,25 @@
|
||||||
export let underline
|
export let underline
|
||||||
export let size
|
export let size
|
||||||
|
|
||||||
$: external = url && typeof url === "string" && !url.startsWith("/")
|
let node
|
||||||
|
|
||||||
|
$: $component.editing && node?.focus()
|
||||||
|
$: externalLink = url && typeof url === "string" && !url.startsWith("/")
|
||||||
$: target = openInNewTab ? "_blank" : "_self"
|
$: target = openInNewTab ? "_blank" : "_self"
|
||||||
$: placeholder = $builderStore.inBuilder && !text
|
$: placeholder = $builderStore.inBuilder && !text
|
||||||
$: componentText = $builderStore.inBuilder
|
$: componentText = getComponentText(text, $builderStore, $component)
|
||||||
? text || "Placeholder link"
|
|
||||||
: text || ""
|
|
||||||
|
|
||||||
// Add color styles to main styles object, otherwise the styleable helper
|
|
||||||
// overrides the color when it's passed as inline style.
|
|
||||||
// Add color styles to main styles object, otherwise the styleable helper
|
// Add color styles to main styles object, otherwise the styleable helper
|
||||||
// overrides the color when it's passed as inline style.
|
// overrides the color when it's passed as inline style.
|
||||||
$: styles = enrichStyles($component.styles, color)
|
$: styles = enrichStyles($component.styles, color)
|
||||||
|
|
||||||
|
const getComponentText = (text, builderState, componentState) => {
|
||||||
|
if (!builderState.inBuilder || componentState.editing) {
|
||||||
|
return text || ""
|
||||||
|
}
|
||||||
|
return text || componentState.name || "Placeholder text"
|
||||||
|
}
|
||||||
|
|
||||||
const enrichStyles = (styles, color) => {
|
const enrichStyles = (styles, color) => {
|
||||||
if (!color) {
|
if (!color) {
|
||||||
return styles
|
return styles
|
||||||
|
@ -39,10 +45,27 @@
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateText = e => {
|
||||||
|
builderStore.actions.updateProp("text", e.target.textContent)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $builderStore.inBuilder || componentText}
|
{#if $component.editing}
|
||||||
{#if external}
|
<div
|
||||||
|
bind:this={node}
|
||||||
|
contenteditable
|
||||||
|
use:styleable={styles}
|
||||||
|
class:bold
|
||||||
|
class:italic
|
||||||
|
class:underline
|
||||||
|
class="align--{align || 'left'} size--{size || 'M'}"
|
||||||
|
on:blur={$component.editing ? updateText : null}
|
||||||
|
>
|
||||||
|
{componentText}
|
||||||
|
</div>
|
||||||
|
{:else if $builderStore.inBuilder || componentText}
|
||||||
|
{#if externalLink}
|
||||||
<a
|
<a
|
||||||
{target}
|
{target}
|
||||||
href={url || "/"}
|
href={url || "/"}
|
||||||
|
@ -72,12 +95,12 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
a {
|
a,
|
||||||
|
div {
|
||||||
color: var(--spectrum-alias-text-color);
|
color: var(--spectrum-alias-text-color);
|
||||||
white-space: nowrap;
|
|
||||||
transition: color 130ms ease-in-out;
|
transition: color 130ms ease-in-out;
|
||||||
}
|
}
|
||||||
a:hover {
|
a:not(.placeholder):hover {
|
||||||
color: var(--spectrum-link-primary-m-text-color-hover) !important;
|
color: var(--spectrum-link-primary-m-text-color-hover) !important;
|
||||||
}
|
}
|
||||||
.placeholder {
|
.placeholder {
|
||||||
|
|
|
@ -12,10 +12,11 @@
|
||||||
export let underline
|
export let underline
|
||||||
export let size
|
export let size
|
||||||
|
|
||||||
$: placeholder = $builderStore.inBuilder && !text
|
let node
|
||||||
$: componentText = $builderStore.inBuilder
|
|
||||||
? text || $component.name || "Placeholder text"
|
$: $component.editing && node?.focus()
|
||||||
: text || ""
|
$: placeholder = $builderStore.inBuilder && !text && !$component.editing
|
||||||
|
$: componentText = getComponentText(text, $builderStore, $component)
|
||||||
$: sizeClass = `spectrum-Body--size${size || "M"}`
|
$: sizeClass = `spectrum-Body--size${size || "M"}`
|
||||||
$: alignClass = `align--${align || "left"}`
|
$: alignClass = `align--${align || "left"}`
|
||||||
|
|
||||||
|
@ -23,6 +24,13 @@
|
||||||
// overrides the color when it's passed as inline style.
|
// overrides the color when it's passed as inline style.
|
||||||
$: styles = enrichStyles($component.styles, color)
|
$: styles = enrichStyles($component.styles, color)
|
||||||
|
|
||||||
|
const getComponentText = (text, builderState, componentState) => {
|
||||||
|
if (!builderState.inBuilder || componentState.editing) {
|
||||||
|
return text || ""
|
||||||
|
}
|
||||||
|
return text || componentState.name || "Placeholder text"
|
||||||
|
}
|
||||||
|
|
||||||
const enrichStyles = (styles, color) => {
|
const enrichStyles = (styles, color) => {
|
||||||
if (!color) {
|
if (!color) {
|
||||||
return styles
|
return styles
|
||||||
|
@ -35,15 +43,24 @@
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert contenteditable HTML to text and save
|
||||||
|
const updateText = e => {
|
||||||
|
const sanitized = e.target.innerHTML.replace(/<br>/gi, "\n")
|
||||||
|
builderStore.actions.updateProp("text", sanitized)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<p
|
<p
|
||||||
|
bind:this={node}
|
||||||
|
contenteditable={$component.editing}
|
||||||
use:styleable={styles}
|
use:styleable={styles}
|
||||||
class:placeholder
|
class:placeholder
|
||||||
class:bold
|
class:bold
|
||||||
class:italic
|
class:italic
|
||||||
class:underline
|
class:underline
|
||||||
class="spectrum-Body {sizeClass} {alignClass}"
|
class="spectrum-Body {sizeClass} {alignClass}"
|
||||||
|
on:blur={$component.editing ? updateText : null}
|
||||||
>
|
>
|
||||||
{componentText}
|
{componentText}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount, getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import Block from "components/Block.svelte"
|
import Block from "components/Block.svelte"
|
||||||
import BlockComponent from "components/BlockComponent.svelte"
|
import BlockComponent from "components/BlockComponent.svelte"
|
||||||
import { Heading } from "@budibase/bbui"
|
import { Heading } from "@budibase/bbui"
|
||||||
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
|
|
||||||
export let title
|
export let title
|
||||||
export let dataSource
|
export let dataSource
|
||||||
|
@ -45,6 +46,7 @@
|
||||||
let repeaterId
|
let repeaterId
|
||||||
let schema
|
let schema
|
||||||
|
|
||||||
|
$: fetchSchema(dataSource)
|
||||||
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema)
|
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema)
|
||||||
$: enrichedFilter = enrichFilter(filter, enrichedSearchColumns, formId)
|
$: enrichedFilter = enrichFilter(filter, enrichedSearchColumns, formId)
|
||||||
$: cardWidth = cardHorizontal ? 420 : 300
|
$: cardWidth = cardHorizontal ? 420 : 300
|
||||||
|
@ -103,15 +105,15 @@
|
||||||
}
|
}
|
||||||
const col = linkColumn || "_id"
|
const col = linkColumn || "_id"
|
||||||
const split = url.split("/:")
|
const split = url.split("/:")
|
||||||
return `${split[0]}/{{ [${repeaterId}].[${col}] }}`
|
return `${split[0]}/{{ ${safe(repeaterId)}.${safe(col)} }}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the datasource schema on mount so we can determine column types
|
// Load the datasource schema so we can determine column types
|
||||||
onMount(async () => {
|
const fetchSchema = async dataSource => {
|
||||||
if (dataSource) {
|
if (dataSource) {
|
||||||
schema = await API.fetchDatasourceSchema(dataSource)
|
schema = await API.fetchDatasourceSchema(dataSource)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Block>
|
<Block>
|
||||||
|
@ -171,7 +173,7 @@
|
||||||
bind:id={repeaterId}
|
bind:id={repeaterId}
|
||||||
context="repeater"
|
context="repeater"
|
||||||
props={{
|
props={{
|
||||||
dataProvider: `{{ literal [${dataProviderId}] }}`,
|
dataProvider: `{{ literal ${safe(dataProviderId)} }}`,
|
||||||
direction: "row",
|
direction: "row",
|
||||||
hAlign: "stretch",
|
hAlign: "stretch",
|
||||||
vAlign: "top",
|
vAlign: "top",
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
<script>
|
||||||
|
import BlockComponent from "components/BlockComponent.svelte"
|
||||||
|
import Block from "components/Block.svelte"
|
||||||
|
import Placeholder from "components/app/Placeholder.svelte"
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
|
|
||||||
|
export let dataSource
|
||||||
|
export let filter
|
||||||
|
export let sortColumn
|
||||||
|
export let sortOrder
|
||||||
|
export let limit
|
||||||
|
export let paginate
|
||||||
|
export let noRowsMessage
|
||||||
|
export let direction
|
||||||
|
export let hAlign
|
||||||
|
export let vAlign
|
||||||
|
export let gap
|
||||||
|
|
||||||
|
let providerId
|
||||||
|
|
||||||
|
const component = getContext("component")
|
||||||
|
const { styleable } = getContext("sdk")
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Block>
|
||||||
|
<div use:styleable={$component.styles}>
|
||||||
|
<BlockComponent
|
||||||
|
type="dataprovider"
|
||||||
|
context="provider"
|
||||||
|
bind:id={providerId}
|
||||||
|
props={{
|
||||||
|
dataSource,
|
||||||
|
filter,
|
||||||
|
sortColumn,
|
||||||
|
sortOrder,
|
||||||
|
limit,
|
||||||
|
paginate,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{#if $component.empty}
|
||||||
|
<Placeholder text={$component.name} />
|
||||||
|
{:else}
|
||||||
|
<BlockComponent
|
||||||
|
type="repeater"
|
||||||
|
context="repeater"
|
||||||
|
props={{
|
||||||
|
dataProvider: `{{ literal ${safe(providerId)} }}`,
|
||||||
|
noRowsMessage,
|
||||||
|
direction,
|
||||||
|
hAlign,
|
||||||
|
vAlign,
|
||||||
|
gap,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</BlockComponent>
|
||||||
|
{/if}
|
||||||
|
</BlockComponent>
|
||||||
|
</div>
|
||||||
|
</Block>
|
|
@ -1,8 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount, getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import Block from "components/Block.svelte"
|
import Block from "components/Block.svelte"
|
||||||
import BlockComponent from "components/BlockComponent.svelte"
|
import BlockComponent from "components/BlockComponent.svelte"
|
||||||
import { Heading } from "@budibase/bbui"
|
import { Heading } from "@budibase/bbui"
|
||||||
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
|
|
||||||
export let title
|
export let title
|
||||||
export let dataSource
|
export let dataSource
|
||||||
|
@ -40,6 +41,7 @@
|
||||||
let dataProviderId
|
let dataProviderId
|
||||||
let schema
|
let schema
|
||||||
|
|
||||||
|
$: fetchSchema(dataSource)
|
||||||
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema)
|
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema)
|
||||||
$: enrichedFilter = enrichFilter(filter, enrichedSearchColumns, formId)
|
$: enrichedFilter = enrichFilter(filter, enrichedSearchColumns, formId)
|
||||||
$: titleButtonAction = [
|
$: titleButtonAction = [
|
||||||
|
@ -61,7 +63,7 @@
|
||||||
operator: column.type === "string" ? "string" : "equal",
|
operator: column.type === "string" ? "string" : "equal",
|
||||||
type: "string",
|
type: "string",
|
||||||
valueType: "Binding",
|
valueType: "Binding",
|
||||||
value: `{{ [${formId}].[${column.name}] }}`,
|
value: `{{ ${safe(formId)}.${safe(column.name)} }}`,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
return enrichedFilter
|
return enrichedFilter
|
||||||
|
@ -84,12 +86,12 @@
|
||||||
return enrichedColumns.slice(0, 3)
|
return enrichedColumns.slice(0, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the datasource schema on mount so we can determine column types
|
// Load the datasource schema so we can determine column types
|
||||||
onMount(async () => {
|
const fetchSchema = async dataSource => {
|
||||||
if (dataSource) {
|
if (dataSource) {
|
||||||
schema = await API.fetchDatasourceSchema(dataSource)
|
schema = await API.fetchDatasourceSchema(dataSource)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Block>
|
<Block>
|
||||||
|
@ -147,7 +149,7 @@
|
||||||
<BlockComponent
|
<BlockComponent
|
||||||
type="table"
|
type="table"
|
||||||
props={{
|
props={{
|
||||||
dataProvider: `{{ literal [${dataProviderId}] }}`,
|
dataProvider: `{{ literal ${safe(dataProviderId)} }}`,
|
||||||
columns: tableColumns,
|
columns: tableColumns,
|
||||||
showAutoColumns,
|
showAutoColumns,
|
||||||
rowCount,
|
rowCount,
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
export { default as tableblock } from "./TableBlock.svelte"
|
export { default as tableblock } from "./TableBlock.svelte"
|
||||||
export { default as cardsblock } from "./CardsBlock.svelte"
|
export { default as cardsblock } from "./CardsBlock.svelte"
|
||||||
|
export { default as repeaterblock } from "./RepeaterBlock.svelte"
|
||||||
|
|
|
@ -17,54 +17,53 @@
|
||||||
const formContext = getContext("form")
|
const formContext = getContext("form")
|
||||||
const formStepContext = getContext("form-step")
|
const formStepContext = getContext("form-step")
|
||||||
const fieldGroupContext = getContext("field-group")
|
const fieldGroupContext = getContext("field-group")
|
||||||
const { styleable } = getContext("sdk")
|
const { styleable, builderStore } = getContext("sdk")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
|
|
||||||
// Register field with form
|
// Register field with form
|
||||||
const formApi = formContext?.formApi
|
const formApi = formContext?.formApi
|
||||||
const labelPos = fieldGroupContext?.labelPosition || "above"
|
const labelPos = fieldGroupContext?.labelPosition || "above"
|
||||||
const formField = formApi?.registerField(
|
$: formStep = formStepContext ? $formStepContext || 1 : 1
|
||||||
|
$: formField = formApi?.registerField(
|
||||||
field,
|
field,
|
||||||
type,
|
type,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
disabled,
|
disabled,
|
||||||
validation,
|
validation,
|
||||||
formStepContext || 1
|
formStep
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Focus label when editing
|
||||||
|
let labelNode
|
||||||
|
$: $component.editing && labelNode?.focus()
|
||||||
|
|
||||||
// Update form properties in parent component on every store change
|
// Update form properties in parent component on every store change
|
||||||
const unsubscribe = formField?.subscribe(value => {
|
$: unsubscribe = formField?.subscribe(value => {
|
||||||
fieldState = value?.fieldState
|
fieldState = value?.fieldState
|
||||||
fieldApi = value?.fieldApi
|
fieldApi = value?.fieldApi
|
||||||
fieldSchema = value?.fieldSchema
|
fieldSchema = value?.fieldSchema
|
||||||
})
|
})
|
||||||
onDestroy(() => unsubscribe?.())
|
onDestroy(() => unsubscribe?.())
|
||||||
|
|
||||||
// Keep field state up to date with props which might change due to
|
|
||||||
// conditional UI
|
|
||||||
$: updateValidation(validation)
|
|
||||||
$: updateDisabled(disabled)
|
|
||||||
|
|
||||||
// Determine label class from position
|
// Determine label class from position
|
||||||
$: labelClass = labelPos === "above" ? "" : `spectrum-FieldLabel--${labelPos}`
|
$: labelClass = labelPos === "above" ? "" : `spectrum-FieldLabel--${labelPos}`
|
||||||
|
|
||||||
const updateValidation = validation => {
|
const updateLabel = e => {
|
||||||
fieldApi?.updateValidation(validation)
|
builderStore.actions.updateProp("label", e.target.textContent)
|
||||||
}
|
|
||||||
|
|
||||||
const updateDisabled = disabled => {
|
|
||||||
fieldApi?.setDisabled(disabled)
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FieldGroupFallback>
|
<FieldGroupFallback>
|
||||||
<div class="spectrum-Form-item" use:styleable={$component.styles}>
|
<div class="spectrum-Form-item" use:styleable={$component.styles}>
|
||||||
<label
|
<label
|
||||||
|
bind:this={labelNode}
|
||||||
|
contenteditable={$component.editing}
|
||||||
|
on:blur={$component.editing ? updateLabel : null}
|
||||||
class:hidden={!label}
|
class:hidden={!label}
|
||||||
for={fieldState?.fieldId}
|
for={fieldState?.fieldId}
|
||||||
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${labelClass}`}
|
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${labelClass}`}
|
||||||
>
|
>
|
||||||
{label || ""}
|
{label || " "}
|
||||||
</label>
|
</label>
|
||||||
<div class="spectrum-Form-itemField">
|
<div class="spectrum-Form-itemField">
|
||||||
{#if !formContext}
|
{#if !formContext}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, onMount } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import InnerForm from "./InnerForm.svelte"
|
import InnerForm from "./InnerForm.svelte"
|
||||||
|
import { hashString } from "utils/helpers"
|
||||||
|
|
||||||
export let dataSource
|
export let dataSource
|
||||||
export let theme
|
export let theme
|
||||||
|
@ -15,6 +16,8 @@
|
||||||
let schema
|
let schema
|
||||||
let table
|
let table
|
||||||
|
|
||||||
|
$: fetchSchema(dataSource)
|
||||||
|
|
||||||
// Returns the closes data context which isn't a built in context
|
// Returns the closes data context which isn't a built in context
|
||||||
const getInitialValues = (type, dataSource, context) => {
|
const getInitialValues = (type, dataSource, context) => {
|
||||||
// Only inherit values for update forms
|
// Only inherit values for update forms
|
||||||
|
@ -35,7 +38,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetches the form schema from this form's dataSource
|
// Fetches the form schema from this form's dataSource
|
||||||
const fetchSchema = async () => {
|
const fetchSchema = async dataSource => {
|
||||||
if (!dataSource) {
|
if (!dataSource) {
|
||||||
schema = {}
|
schema = {}
|
||||||
}
|
}
|
||||||
|
@ -62,14 +65,15 @@
|
||||||
schema = dataSourceSchema || {}
|
schema = dataSourceSchema || {}
|
||||||
}
|
}
|
||||||
|
|
||||||
loaded = true
|
if (!loaded) {
|
||||||
|
loaded = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: initialValues = getInitialValues(actionType, dataSource, $context)
|
$: initialValues = getInitialValues(actionType, dataSource, $context)
|
||||||
$: resetKey = JSON.stringify(initialValues)
|
$: resetKey = hashString(
|
||||||
|
JSON.stringify(initialValues) + JSON.stringify(schema)
|
||||||
// Load the form schema on mount
|
)
|
||||||
onMount(fetchSchema)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if loaded}
|
{#if loaded}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, setContext } from "svelte"
|
import { getContext, setContext } from "svelte"
|
||||||
|
import { writable } from "svelte/store"
|
||||||
import Placeholder from "../Placeholder.svelte"
|
import Placeholder from "../Placeholder.svelte"
|
||||||
|
|
||||||
export let step = 1
|
export let step = 1
|
||||||
|
@ -9,7 +10,9 @@
|
||||||
const formContext = getContext("form")
|
const formContext = getContext("form")
|
||||||
|
|
||||||
// Set form step context so fields know what step they are within
|
// Set form step context so fields know what step they are within
|
||||||
setContext("form-step", step || 1)
|
const stepStore = writable(step || 1)
|
||||||
|
$: stepStore.set(step || 1)
|
||||||
|
setContext("form-step", stepStore)
|
||||||
|
|
||||||
$: formState = formContext?.formState
|
$: formState = formContext?.formState
|
||||||
$: currentStep = $formState?.currentStep
|
$: currentStep = $formState?.currentStep
|
||||||
|
|
|
@ -96,15 +96,14 @@
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we've already registered this field then wipe any errors and
|
// If we've already registered this field then keep some existing state
|
||||||
// return the existing field
|
let initialValue = initialValues[field] ?? defaultValue
|
||||||
|
let fieldId = `id-${generateID()}`
|
||||||
const existingField = getField(field)
|
const existingField = getField(field)
|
||||||
if (existingField) {
|
if (existingField) {
|
||||||
existingField.update(state => {
|
const { fieldState } = get(existingField)
|
||||||
state.fieldState.error = null
|
initialValue = fieldState.value ?? initialValue
|
||||||
return state
|
fieldId = fieldState.fieldId
|
||||||
})
|
|
||||||
return existingField
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto columns are always disabled
|
// Auto columns are always disabled
|
||||||
|
@ -125,8 +124,8 @@
|
||||||
type,
|
type,
|
||||||
step: step || 1,
|
step: step || 1,
|
||||||
fieldState: {
|
fieldState: {
|
||||||
fieldId: `id-${generateID()}`,
|
fieldId,
|
||||||
value: initialValues[field] ?? defaultValue,
|
value: initialValue,
|
||||||
error: null,
|
error: null,
|
||||||
disabled: disabled || fieldDisabled || isAutoColumn,
|
disabled: disabled || fieldDisabled || isAutoColumn,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
|
@ -137,7 +136,12 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add this field
|
// Add this field
|
||||||
fields = [...fields, fieldInfo]
|
if (existingField) {
|
||||||
|
const otherFields = fields.filter(info => get(info).name !== field)
|
||||||
|
fields = [...otherFields, fieldInfo]
|
||||||
|
} else {
|
||||||
|
fields = [...fields, fieldInfo]
|
||||||
|
}
|
||||||
|
|
||||||
return fieldInfo
|
return fieldInfo
|
||||||
},
|
},
|
||||||
|
@ -287,7 +291,7 @@
|
||||||
|
|
||||||
// Provide form step context so that forms without any step components
|
// Provide form step context so that forms without any step components
|
||||||
// register their fields to step 1
|
// register their fields to step 1
|
||||||
setContext("form-step", 1)
|
setContext("form-step", writable(1))
|
||||||
|
|
||||||
// Action context to pass to children
|
// Action context to pass to children
|
||||||
const actions = [
|
const actions = [
|
||||||
|
|
|
@ -17,10 +17,6 @@
|
||||||
|
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
const { styleable, getAction, ActionTypes, routeStore } = getContext("sdk")
|
const { styleable, getAction, ActionTypes, routeStore } = getContext("sdk")
|
||||||
const setSorting = getAction(
|
|
||||||
dataProvider?.id,
|
|
||||||
ActionTypes.SetDataProviderSorting
|
|
||||||
)
|
|
||||||
const customColumnKey = `custom-${Math.random()}`
|
const customColumnKey = `custom-${Math.random()}`
|
||||||
const customRenderers = [
|
const customRenderers = [
|
||||||
{
|
{
|
||||||
|
@ -29,13 +25,16 @@
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
// Table state
|
|
||||||
$: hasChildren = $component.children
|
$: hasChildren = $component.children
|
||||||
$: loading = dataProvider?.loading ?? false
|
$: loading = dataProvider?.loading ?? false
|
||||||
$: data = dataProvider?.rows || []
|
$: data = dataProvider?.rows || []
|
||||||
$: fullSchema = dataProvider?.schema ?? {}
|
$: fullSchema = dataProvider?.schema ?? {}
|
||||||
$: fields = getFields(fullSchema, columns, showAutoColumns)
|
$: fields = getFields(fullSchema, columns, showAutoColumns)
|
||||||
$: schema = getFilteredSchema(fullSchema, fields, hasChildren)
|
$: schema = getFilteredSchema(fullSchema, fields, hasChildren)
|
||||||
|
$: setSorting = getAction(
|
||||||
|
dataProvider?.id,
|
||||||
|
ActionTypes.SetDataProviderSorting
|
||||||
|
)
|
||||||
|
|
||||||
const getFields = (schema, customColumns, showAutoColumns) => {
|
const getFields = (schema, customColumns, showAutoColumns) => {
|
||||||
// Check for an invalid column selection
|
// Check for an invalid column selection
|
||||||
|
|
|
@ -147,7 +147,7 @@
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const element = e.target.closest(".component")
|
const element = e.target.closest(".component:not(.block)")
|
||||||
if (
|
if (
|
||||||
element &&
|
element &&
|
||||||
element.classList.contains("droppable") &&
|
element.classList.contains("droppable") &&
|
||||||
|
|
|
@ -17,10 +17,9 @@
|
||||||
|
|
||||||
<div
|
<div
|
||||||
in:fade={{
|
in:fade={{
|
||||||
delay: transition ? 50 : 0,
|
delay: transition ? 130 : 0,
|
||||||
duration: transition ? 130 : 0,
|
duration: transition ? 130 : 0,
|
||||||
}}
|
}}
|
||||||
out:fade={{ duration: transition ? 130 : 0 }}
|
|
||||||
class="indicator"
|
class="indicator"
|
||||||
class:flipped
|
class:flipped
|
||||||
class:line
|
class:line
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
<script>
|
||||||
|
import { onMount, onDestroy } from "svelte"
|
||||||
|
import { get } from "svelte/store"
|
||||||
|
import { builderStore } from "stores"
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (get(builderStore).inBuilder) {
|
||||||
|
document.addEventListener("keydown", onKeyDown)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
if (get(builderStore).inBuilder) {
|
||||||
|
document.removeEventListener("keydown", onKeyDown)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const onKeyDown = e => {
|
||||||
|
if (e.key === "Delete" || e.key === "Backspace") {
|
||||||
|
deleteSelectedComponent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteSelectedComponent = () => {
|
||||||
|
const state = get(builderStore)
|
||||||
|
if (!state.inBuilder || !state.selectedComponentId || state.editMode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const activeTag = document.activeElement?.tagName.toLowerCase()
|
||||||
|
if (["input", "textarea"].indexOf(activeTag) !== -1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
builderStore.actions.deleteComponent(state.selectedComponentId)
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,11 +1,15 @@
|
||||||
<script>
|
<script>
|
||||||
import { builderStore } from "stores"
|
import { builderStore } from "stores"
|
||||||
import IndicatorSet from "./IndicatorSet.svelte"
|
import IndicatorSet from "./IndicatorSet.svelte"
|
||||||
|
|
||||||
|
$: color = $builderStore.editMode
|
||||||
|
? "var(--spectrum-global-color-static-green-500)"
|
||||||
|
: "var(--spectrum-global-color-static-blue-600)"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<IndicatorSet
|
<IndicatorSet
|
||||||
componentId={$builderStore.selectedComponentId}
|
componentId={$builderStore.selectedComponentId}
|
||||||
color="var(--spectrum-global-color-static-blue-600)"
|
{color}
|
||||||
zIndex="910"
|
zIndex="910"
|
||||||
transition
|
transition
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -17,7 +17,19 @@
|
||||||
|
|
||||||
$: definition = $builderStore.selectedComponentDefinition
|
$: definition = $builderStore.selectedComponentDefinition
|
||||||
$: showBar = definition?.showSettingsBar && !$builderStore.isDragging
|
$: showBar = definition?.showSettingsBar && !$builderStore.isDragging
|
||||||
$: settings = definition?.settings?.filter(setting => setting.showInBar) ?? []
|
$: settings = getBarSettings(definition)
|
||||||
|
|
||||||
|
const getBarSettings = definition => {
|
||||||
|
let allSettings = []
|
||||||
|
definition?.settings?.forEach(setting => {
|
||||||
|
if (setting.section) {
|
||||||
|
allSettings = allSettings.concat(setting.settings || [])
|
||||||
|
} else {
|
||||||
|
allSettings.push(setting)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return allSettings.filter(setting => setting.showInBar)
|
||||||
|
}
|
||||||
|
|
||||||
const updatePosition = () => {
|
const updatePosition = () => {
|
||||||
if (!showBar) {
|
if (!showBar) {
|
||||||
|
@ -110,7 +122,7 @@
|
||||||
prop={setting.key}
|
prop={setting.key}
|
||||||
value={option.value}
|
value={option.value}
|
||||||
icon={option.barIcon}
|
icon={option.barIcon}
|
||||||
title={option.barTitle}
|
title={option.barTitle || option.label}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
{:else}
|
{:else}
|
||||||
|
@ -124,7 +136,7 @@
|
||||||
<SettingsButton
|
<SettingsButton
|
||||||
prop={setting.key}
|
prop={setting.key}
|
||||||
icon={setting.barIcon}
|
icon={setting.barIcon}
|
||||||
title={setting.barTitle}
|
title={setting.barTitle || setting.label}
|
||||||
bool
|
bool
|
||||||
/>
|
/>
|
||||||
{:else if setting.type === "color"}
|
{:else if setting.type === "color"}
|
||||||
|
|
|
@ -25,7 +25,8 @@ export const UnsortableTypes = [
|
||||||
export const ActionTypes = {
|
export const ActionTypes = {
|
||||||
ValidateForm: "ValidateForm",
|
ValidateForm: "ValidateForm",
|
||||||
RefreshDatasource: "RefreshDatasource",
|
RefreshDatasource: "RefreshDatasource",
|
||||||
SetDataProviderQuery: "SetDataProviderQuery",
|
AddDataProviderQueryExtension: "AddDataProviderQueryExtension",
|
||||||
|
RemoveDataProviderQueryExtension: "RemoveDataProviderQueryExtension",
|
||||||
SetDataProviderSorting: "SetDataProviderSorting",
|
SetDataProviderSorting: "SetDataProviderSorting",
|
||||||
ClearForm: "ClearForm",
|
ClearForm: "ClearForm",
|
||||||
ChangeFormStep: "ChangeFormStep",
|
ChangeFormStep: "ChangeFormStep",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { writable, derived } from "svelte/store"
|
import { writable, derived, get } from "svelte/store"
|
||||||
import Manifest from "manifest.json"
|
import Manifest from "manifest.json"
|
||||||
import { findComponentById, findComponentPathById } from "../utils/components"
|
import { findComponentById, findComponentPathById } from "../utils/components"
|
||||||
import { pingEndUser } from "../api"
|
import { pingEndUser } from "../api"
|
||||||
|
@ -14,6 +14,7 @@ const createBuilderStore = () => {
|
||||||
layout: null,
|
layout: null,
|
||||||
screen: null,
|
screen: null,
|
||||||
selectedComponentId: null,
|
selectedComponentId: null,
|
||||||
|
editMode: false,
|
||||||
previewId: null,
|
previewId: null,
|
||||||
previewType: null,
|
previewType: null,
|
||||||
selectedPath: [],
|
selectedPath: [],
|
||||||
|
@ -50,6 +51,10 @@ const createBuilderStore = () => {
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
selectComponent: id => {
|
selectComponent: id => {
|
||||||
|
if (id === get(writableStore).selectedComponentId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writableStore.update(state => ({ ...state, editMode: false }))
|
||||||
dispatchEvent("select-component", { id })
|
dispatchEvent("select-component", { id })
|
||||||
},
|
},
|
||||||
updateProp: (prop, value) => {
|
updateProp: (prop, value) => {
|
||||||
|
@ -65,10 +70,7 @@ const createBuilderStore = () => {
|
||||||
pingEndUser()
|
pingEndUser()
|
||||||
},
|
},
|
||||||
setSelectedPath: path => {
|
setSelectedPath: path => {
|
||||||
writableStore.update(state => {
|
writableStore.update(state => ({ ...state, selectedPath: path }))
|
||||||
state.selectedPath = path
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
moveComponent: (componentId, destinationComponentId, mode) => {
|
moveComponent: (componentId, destinationComponentId, mode) => {
|
||||||
dispatchEvent("move-component", {
|
dispatchEvent("move-component", {
|
||||||
|
@ -78,10 +80,16 @@ const createBuilderStore = () => {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setDragging: dragging => {
|
setDragging: dragging => {
|
||||||
writableStore.update(state => {
|
if (dragging === get(writableStore).isDragging) {
|
||||||
state.isDragging = dragging
|
return
|
||||||
return state
|
}
|
||||||
})
|
writableStore.update(state => ({ ...state, isDragging: dragging }))
|
||||||
|
},
|
||||||
|
setEditMode: enabled => {
|
||||||
|
if (enabled === get(writableStore).editMode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writableStore.update(state => ({ ...state, editMode: enabled }))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -21,12 +21,7 @@ export const styleable = (node, styles = {}) => {
|
||||||
let applyNormalStyles
|
let applyNormalStyles
|
||||||
let applyHoverStyles
|
let applyHoverStyles
|
||||||
let selectComponent
|
let selectComponent
|
||||||
|
let editComponent
|
||||||
// Allow dragging if required
|
|
||||||
const parent = node.closest(".component")
|
|
||||||
if (parent && parent.classList.contains("draggable")) {
|
|
||||||
node.setAttribute("draggable", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates event listeners and applies initial styles
|
// Creates event listeners and applies initial styles
|
||||||
const setupStyles = (newStyles = {}) => {
|
const setupStyles = (newStyles = {}) => {
|
||||||
|
@ -45,6 +40,9 @@ export const styleable = (node, styles = {}) => {
|
||||||
...(newStyles.hover || {}),
|
...(newStyles.hover || {}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allow dragging if required
|
||||||
|
node.setAttribute("draggable", !!newStyles.draggable)
|
||||||
|
|
||||||
// Applies a style string to a DOM node
|
// Applies a style string to a DOM node
|
||||||
const applyStyles = styleString => {
|
const applyStyles = styleString => {
|
||||||
node.style = styleString
|
node.style = styleString
|
||||||
|
@ -69,6 +67,17 @@ export const styleable = (node, styles = {}) => {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handler to start editing a component (if applicable) when double
|
||||||
|
// clicking in the builder preview
|
||||||
|
editComponent = event => {
|
||||||
|
if (newStyles.interactive && newStyles.editable) {
|
||||||
|
builderStore.actions.setEditMode(true)
|
||||||
|
}
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Add listeners to toggle hover styles
|
// Add listeners to toggle hover styles
|
||||||
node.addEventListener("mouseover", applyHoverStyles)
|
node.addEventListener("mouseover", applyHoverStyles)
|
||||||
node.addEventListener("mouseout", applyNormalStyles)
|
node.addEventListener("mouseout", applyNormalStyles)
|
||||||
|
@ -76,6 +85,7 @@ export const styleable = (node, styles = {}) => {
|
||||||
// Add builder preview click listener
|
// Add builder preview click listener
|
||||||
if (newStyles.interactive) {
|
if (newStyles.interactive) {
|
||||||
node.addEventListener("click", selectComponent, false)
|
node.addEventListener("click", selectComponent, false)
|
||||||
|
node.addEventListener("dblclick", editComponent, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply initial normal styles
|
// Apply initial normal styles
|
||||||
|
@ -87,6 +97,7 @@ export const styleable = (node, styles = {}) => {
|
||||||
node.removeEventListener("mouseover", applyHoverStyles)
|
node.removeEventListener("mouseover", applyHoverStyles)
|
||||||
node.removeEventListener("mouseout", applyNormalStyles)
|
node.removeEventListener("mouseout", applyNormalStyles)
|
||||||
node.removeEventListener("click", selectComponent)
|
node.removeEventListener("click", selectComponent)
|
||||||
|
node.removeEventListener("dblclick", editComponent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply initial styles
|
// Apply initial styles
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,4 @@
|
||||||
FROM node:12-alpine
|
FROM node:14-alpine
|
||||||
|
|
||||||
LABEL com.centurylinklabs.watchtower.lifecycle.pre-check="scripts/watchtower-hooks/pre-check.sh"
|
LABEL com.centurylinklabs.watchtower.lifecycle.pre-check="scripts/watchtower-hooks/pre-check.sh"
|
||||||
LABEL com.centurylinklabs.watchtower.lifecycle.pre-update="scripts/watchtower-hooks/pre-update.sh"
|
LABEL com.centurylinklabs.watchtower.lifecycle.pre-update="scripts/watchtower-hooks/pre-update.sh"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "0.9.185-alpha.0",
|
"version": "0.9.185-alpha.14",
|
||||||
"description": "Budibase Web Server",
|
"description": "Budibase Web Server",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -68,9 +68,11 @@
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/auth": "^0.9.185-alpha.0",
|
"@budibase/auth": "^0.9.185-alpha.14",
|
||||||
"@budibase/client": "^0.9.185-alpha.0",
|
"@budibase/client": "^0.9.185-alpha.14",
|
||||||
"@budibase/string-templates": "^0.9.185-alpha.0",
|
"@budibase/string-templates": "^0.9.185-alpha.14",
|
||||||
|
"@bull-board/api": "^3.7.0",
|
||||||
|
"@bull-board/koa": "^3.7.0",
|
||||||
"@elastic/elasticsearch": "7.10.0",
|
"@elastic/elasticsearch": "7.10.0",
|
||||||
"@koa/router": "8.0.0",
|
"@koa/router": "8.0.0",
|
||||||
"@sendgrid/mail": "7.1.1",
|
"@sendgrid/mail": "7.1.1",
|
||||||
|
@ -80,7 +82,6 @@
|
||||||
"aws-sdk": "^2.767.0",
|
"aws-sdk": "^2.767.0",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"bull": "^3.22.4",
|
"bull": "^3.22.4",
|
||||||
"bull-board": "^2.0.1",
|
|
||||||
"chmodr": "1.2.0",
|
"chmodr": "1.2.0",
|
||||||
"csvtojson": "2.0.10",
|
"csvtojson": "2.0.10",
|
||||||
"dotenv": "8.2.0",
|
"dotenv": "8.2.0",
|
||||||
|
@ -120,6 +121,7 @@
|
||||||
"uuid": "3.3.2",
|
"uuid": "3.3.2",
|
||||||
"validate.js": "0.13.1",
|
"validate.js": "0.13.1",
|
||||||
"vm2": "^3.9.3",
|
"vm2": "^3.9.3",
|
||||||
|
"worker-farm": "^1.7.0",
|
||||||
"yargs": "13.2.4",
|
"yargs": "13.2.4",
|
||||||
"zlib": "1.0.5"
|
"zlib": "1.0.5"
|
||||||
},
|
},
|
||||||
|
@ -138,7 +140,6 @@
|
||||||
"copyfiles": "^2.4.1",
|
"copyfiles": "^2.4.1",
|
||||||
"docker-compose": "^0.23.6",
|
"docker-compose": "^0.23.6",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
"express": "^4.17.1",
|
|
||||||
"jest": "^27.0.5",
|
"jest": "^27.0.5",
|
||||||
"nodemon": "^2.0.4",
|
"nodemon": "^2.0.4",
|
||||||
"prettier": "^2.3.1",
|
"prettier": "^2.3.1",
|
||||||
|
|
|
@ -50,6 +50,7 @@ async function init() {
|
||||||
SELF_HOSTED: 1,
|
SELF_HOSTED: 1,
|
||||||
DISABLE_ACCOUNT_PORTAL: "",
|
DISABLE_ACCOUNT_PORTAL: "",
|
||||||
MULTI_TENANCY: "",
|
MULTI_TENANCY: "",
|
||||||
|
DISABLE_THREADING: 1,
|
||||||
}
|
}
|
||||||
let envFile = ""
|
let envFile = ""
|
||||||
Object.keys(envFileJson).forEach(key => {
|
Object.keys(envFileJson).forEach(key => {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue