Merge remote-tracking branch 'origin/develop' into fix/pc-generic-fixes
This commit is contained in:
commit
1da1331b3a
|
@ -18,7 +18,7 @@ jobs:
|
|||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x]
|
||||
node-version: [14.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
|
|
@ -18,7 +18,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 14.x
|
||||
- run: yarn
|
||||
- run: yarn bootstrap
|
||||
- run: yarn lint
|
||||
|
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 14.x
|
||||
- run: yarn
|
||||
- run: yarn bootstrap
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 14.x
|
||||
- run: yarn
|
||||
- run: yarn bootstrap
|
||||
- run: yarn lint
|
||||
|
|
46
README.md
46
README.md
|
@ -54,17 +54,51 @@
|
|||
<br /><br />
|
||||
## ✨ Features
|
||||
|
||||
- **Build and ship real software.** Unlike other platforms, with Budibase you build and ship single page applications. Budibase applications have performance baked in and can be designed responsively, providing your users with a great experience.
|
||||
### Build and ship real software
|
||||
Unlike other platforms, with Budibase you build and ship single page applications. Budibase applications have performance baked in and can be designed responsively, providing your users with a great experience.
|
||||
<br /><br />
|
||||
|
||||
- **Open source and extensible.** Budibase is open-source - licensed as GPL v3. This should fill you with confidence that Budibase will always be around. You can also code against Budibase or fork it and make changes as you please, providing a developer-friendly experience.
|
||||
### Open source and extensible
|
||||
Budibase is open-source - licensed as GPL v3. This should fill you with confidence that Budibase will always be around. You can also code against Budibase or fork it and make changes as you please, providing a developer-friendly experience.
|
||||
<br /><br />
|
||||
|
||||
- **Load data or start from scratch.** Budibase pulls in data from multiple sources, including MongoDB, CouchDB, PostgreSQL, MySQL, Airtable, S3, DynamoDB, or a REST API. And unlike other platforms, with Budibase you can start from scratch and create business apps with no data sources. [Request new data sources](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
|
||||
### Load data or start from scratch
|
||||
Budibase pulls in data from multiple sources, including MongoDB, CouchDB, PostgreSQL, MySQL, Airtable, S3, DynamoDB, or a REST API. And unlike other platforms, with Budibase you can start from scratch and create business apps with no data sources. [Request new data sources](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
|
||||
|
||||
- **Design and build apps with powerful pre-made components.** Budibase comes out of the box with beautifully designed, powerful components which you can use like building blocks to build your UI. We also expose a lot of your favourite CSS styling options so you can go that extra creative mile. [Request new component](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
|
||||
<p align="center">
|
||||
<img alt="Budibase data" src="https://res.cloudinary.com/daog6scxm/image/upload/v1636970242/Out%20of%20beta%20launch/data_n1tlhf.png">
|
||||
</p>
|
||||
<br /><br />
|
||||
|
||||
- **Automate processes, integrate with other tools, and connect to webhooks.** Save time by automating manual processes and workflows. From connecting to webhooks, to automating emails, simply tell Budibase what to do and let it work for you. You can easily [create new automations for Budibase here](https://github.com/Budibase/automations) or [Request new automation](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
|
||||
### Design and build apps with powerful pre-made components
|
||||
|
||||
- **Admin paradise.** Budibase is made to scale. With Budibase, you can self-host on your own infrastructure and globally manage users, onboarding, SMTP, apps, groups, theming and more. You can also provide users/groups with an app portal and disseminate user-management to the group manager.
|
||||
Budibase comes out of the box with beautifully designed, powerful components which you can use like building blocks to build your UI. We also expose a lot of your favourite CSS styling options so you can go that extra creative mile. [Request new component](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
|
||||
|
||||
<p align="center">
|
||||
<img alt="Budibase design" src="https://res.cloudinary.com/daog6scxm/image/upload/v1636970243/Out%20of%20beta%20launch/design-like-a-pro_qhlfeu.gif">
|
||||
</p>
|
||||
<br /><br />
|
||||
|
||||
### Automate processes, integrate with other tools, and connect to webhooks
|
||||
Save time by automating manual processes and workflows. From connecting to webhooks, to automating emails, simply tell Budibase what to do and let it work for you. You can easily [create new automations for Budibase here](https://github.com/Budibase/automations) or [Request new automation](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
|
||||
|
||||
<p align="center">
|
||||
<img alt="Budibase automations" src="https://res.cloudinary.com/daog6scxm/image/upload/v1636970486/Out%20of%20beta%20launch/automation_riro7u.png">
|
||||
</p>
|
||||
<br /><br />
|
||||
|
||||
### Integrate with your favorite tools
|
||||
Budibase integrates with a number of popular tools allowing you to build apps that perfectly fit your stack.
|
||||
|
||||
<p align="center">
|
||||
<img alt="Budibase integrations" src="https://res.cloudinary.com/daog6scxm/image/upload/v1636970242/Out%20of%20beta%20launch/integrations_kc7dqt.png">
|
||||
</p>
|
||||
<br /><br />
|
||||
|
||||
### Admin paradise
|
||||
Budibase is made to scale. With Budibase, you can self-host on your own infrastructure and globally manage users, onboarding, SMTP, apps, groups, theming and more. You can also provide users/groups with an app portal and disseminate user-management to the group manager.
|
||||
|
||||
- Checkout the promo video: https://youtu.be/xoljVpty_Kw
|
||||
|
||||
<br /><br /><br />
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "0.9.185-alpha.0",
|
||||
"version": "0.9.185-alpha.14",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
|
|
|
@ -59,6 +59,7 @@
|
|||
"mode:self": "yarn env:selfhost:enable && yarn env:multi:disable && yarn env:account:disable",
|
||||
"mode:cloud": "yarn env:selfhost:disable && yarn env:multi:enable && yarn env:account:disable",
|
||||
"mode:account": "yarn mode:cloud && yarn env:account:enable",
|
||||
"security:audit": "node scripts/audit.js",
|
||||
"postinstall": "husky install"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
module.exports = {
|
||||
user: require("./src/cache/user"),
|
||||
app: require("./src/cache/appMetadata"),
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/auth",
|
||||
"version": "0.9.185-alpha.0",
|
||||
"version": "0.9.185-alpha.14",
|
||||
"description": "Authentication middlewares for budibase builder and apps",
|
||||
"main": "src/index.js",
|
||||
"author": "Budibase",
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
const redis = require("../redis/authRedis")
|
||||
const { getCouch } = require("../db")
|
||||
const { DocumentTypes } = require("../db/constants")
|
||||
|
||||
const AppState = {
|
||||
INVALID: "invalid",
|
||||
}
|
||||
const EXPIRY_SECONDS = 3600
|
||||
|
||||
/**
|
||||
* The default populate app metadata function
|
||||
*/
|
||||
const populateFromDB = async (appId, CouchDB = null) => {
|
||||
if (!CouchDB) {
|
||||
CouchDB = getCouch()
|
||||
}
|
||||
const db = new CouchDB(appId, { skip_setup: true })
|
||||
return db.get(DocumentTypes.APP_METADATA)
|
||||
}
|
||||
|
||||
const isInvalid = metadata => {
|
||||
return !metadata || metadata.state === AppState.INVALID
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the requested app metadata by id.
|
||||
* Use redis cache to first read the app metadata.
|
||||
* If not present fallback to loading the app metadata directly and re-caching.
|
||||
* @param {string} appId the id of the app to get metadata from.
|
||||
* @param {object} CouchDB the database being passed
|
||||
* @returns {object} the app metadata.
|
||||
*/
|
||||
exports.getAppMetadata = async (appId, CouchDB = null) => {
|
||||
const client = await redis.getAppClient()
|
||||
// try cache
|
||||
let metadata = await client.get(appId)
|
||||
if (!metadata) {
|
||||
let expiry = EXPIRY_SECONDS
|
||||
try {
|
||||
metadata = await populateFromDB(appId, CouchDB)
|
||||
} catch (err) {
|
||||
// app DB left around, but no metadata, it is invalid
|
||||
if (err && err.status === 404) {
|
||||
metadata = { state: AppState.INVALID }
|
||||
// don't expire the reference to an invalid app, it'll only be
|
||||
// updated if a metadata doc actually gets stored (app is remade/reverted)
|
||||
expiry = undefined
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
// needed for cypress/some scenarios where the caching happens
|
||||
// so quickly the requests can get slightly out of sync
|
||||
// might store its invalid just before it stores its valid
|
||||
if (isInvalid(metadata)) {
|
||||
const temp = await client.get(appId)
|
||||
if (temp) {
|
||||
metadata = temp
|
||||
}
|
||||
}
|
||||
await client.store(appId, metadata, expiry)
|
||||
}
|
||||
// we've stored in the cache an object to tell us that it is currently invalid
|
||||
if (isInvalid(metadata)) {
|
||||
throw { status: 404, message: "No app metadata found" }
|
||||
}
|
||||
return metadata
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate/reset the cached metadata when a change occurs in the db.
|
||||
* @param appId {string} the cache key to bust/update.
|
||||
* @param newMetadata {object|undefined} optional - can simply provide the new metadata to update with.
|
||||
* @return {Promise<void>} will respond with success when cache is updated.
|
||||
*/
|
||||
exports.invalidateAppMetadata = async (appId, newMetadata = null) => {
|
||||
if (!appId) {
|
||||
throw "Cannot invalidate if no app ID provided."
|
||||
}
|
||||
const client = await redis.getAppClient()
|
||||
await client.delete(appId)
|
||||
if (newMetadata) {
|
||||
await client.store(appId, newMetadata, EXPIRY_SECONDS)
|
||||
}
|
||||
}
|
|
@ -1,11 +1,14 @@
|
|||
const { newid } = require("../hashing")
|
||||
const Replication = require("./Replication")
|
||||
const { DEFAULT_TENANT_ID } = require("../constants")
|
||||
const { DEFAULT_TENANT_ID, Configs } = require("../constants")
|
||||
const env = require("../environment")
|
||||
const { StaticDatabases, SEPARATOR, DocumentTypes } = require("./constants")
|
||||
const { getTenantId, getTenantIDFromAppID } = require("../tenancy")
|
||||
const fetch = require("node-fetch")
|
||||
const { getCouch } = require("./index")
|
||||
const { getAppMetadata } = require("../cache/appMetadata")
|
||||
|
||||
const NO_APP_ERROR = "No app provided"
|
||||
|
||||
const UNICODE_MAX = "\ufff0"
|
||||
|
||||
|
@ -45,14 +48,23 @@ function getDocParams(docType, docId = null, otherProps = {}) {
|
|||
}
|
||||
|
||||
exports.isDevAppID = appId => {
|
||||
if (!appId) {
|
||||
throw NO_APP_ERROR
|
||||
}
|
||||
return appId.startsWith(exports.APP_DEV_PREFIX)
|
||||
}
|
||||
|
||||
exports.isProdAppID = appId => {
|
||||
if (!appId) {
|
||||
throw NO_APP_ERROR
|
||||
}
|
||||
return appId.startsWith(exports.APP_PREFIX) && !exports.isDevAppID(appId)
|
||||
}
|
||||
|
||||
function isDevApp(app) {
|
||||
if (!app) {
|
||||
throw NO_APP_ERROR
|
||||
}
|
||||
return exports.isDevAppID(app.appId)
|
||||
}
|
||||
|
||||
|
@ -232,16 +244,16 @@ exports.getAllApps = async (CouchDB, { dev, all, idsOnly } = {}) => {
|
|||
if (idsOnly) {
|
||||
return appDbNames
|
||||
}
|
||||
const appPromises = appDbNames.map(db =>
|
||||
const appPromises = appDbNames.map(app =>
|
||||
// skip setup otherwise databases could be re-created
|
||||
new CouchDB(db, { skip_setup: true }).get(DocumentTypes.APP_METADATA)
|
||||
getAppMetadata(app, CouchDB)
|
||||
)
|
||||
if (appPromises.length === 0) {
|
||||
return []
|
||||
} else {
|
||||
const response = await Promise.allSettled(appPromises)
|
||||
const apps = response
|
||||
.filter(result => result.status === "fulfilled")
|
||||
.filter(result => result.status === "fulfilled" && result.value != null)
|
||||
.map(({ value }) => value)
|
||||
if (!all) {
|
||||
return apps.filter(app => {
|
||||
|
@ -351,13 +363,50 @@ const getScopedFullConfig = async function (db, { type, user, workspace }) {
|
|||
}
|
||||
|
||||
// Find the config with the most granular scope based on context
|
||||
const scopedConfig = response.rows.sort(
|
||||
let scopedConfig = response.rows.sort(
|
||||
(a, b) => determineScore(a) - determineScore(b)
|
||||
)[0]
|
||||
|
||||
// custom logic for settings doc
|
||||
// always provide the platform URL
|
||||
if (type === Configs.SETTINGS) {
|
||||
if (scopedConfig && scopedConfig.doc) {
|
||||
scopedConfig.doc.config.platformUrl = await getPlatformUrl(
|
||||
scopedConfig.doc.config
|
||||
)
|
||||
} else {
|
||||
scopedConfig = {
|
||||
doc: {
|
||||
config: {
|
||||
platformUrl: await getPlatformUrl(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return scopedConfig && scopedConfig.doc
|
||||
}
|
||||
|
||||
const getPlatformUrl = async settings => {
|
||||
let platformUrl = env.PLATFORM_URL
|
||||
|
||||
if (!env.SELF_HOSTED && env.MULTI_TENANCY) {
|
||||
// cloud and multi tenant - add the tenant to the default platform url
|
||||
const tenantId = getTenantId()
|
||||
if (!platformUrl.includes("localhost:")) {
|
||||
platformUrl = platformUrl.replace("://", `://${tenantId}.`)
|
||||
}
|
||||
} else {
|
||||
// self hosted - check for platform url override
|
||||
if (settings && settings.platformUrl) {
|
||||
platformUrl = settings.platformUrl
|
||||
}
|
||||
}
|
||||
|
||||
return platformUrl ? platformUrl : "http://localhost:10000"
|
||||
}
|
||||
|
||||
async function getScopedConfig(db, params) {
|
||||
const configDoc = await getScopedFullConfig(db, params)
|
||||
return configDoc && configDoc.config ? configDoc.config : configDoc
|
||||
|
|
|
@ -25,6 +25,7 @@ module.exports = {
|
|||
DISABLE_ACCOUNT_PORTAL: process.env.DISABLE_ACCOUNT_PORTAL,
|
||||
SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED),
|
||||
COOKIE_DOMAIN: process.env.COOKIE_DOMAIN,
|
||||
PLATFORM_URL: process.env.PLATFORM_URL,
|
||||
isTest,
|
||||
_set(key, value) {
|
||||
process.env[key] = value
|
||||
|
|
|
@ -92,6 +92,10 @@ module.exports = (
|
|||
finalise(ctx, { authenticated, user, internal, version, publicEndpoint })
|
||||
return next()
|
||||
} catch (err) {
|
||||
// invalid token, clear the cookie
|
||||
if (err && err.name === "JsonWebTokenError") {
|
||||
clearCookie(ctx, Cookies.Auth)
|
||||
}
|
||||
// allow configuring for public access
|
||||
if ((opts && opts.publicAllowed) || publicEndpoint) {
|
||||
finalise(ctx, { authenticated: false, version, publicEndpoint })
|
||||
|
|
|
@ -6,6 +6,7 @@ exports.ObjectStoreBuckets = {
|
|||
APPS: "prod-budi-app-assets",
|
||||
TEMPLATES: "templates",
|
||||
GLOBAL: "global",
|
||||
GLOBAL_CLOUD: "prod-budi-tenant-uploads",
|
||||
}
|
||||
|
||||
exports.budibaseTempDir = function () {
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
const Client = require("./index")
|
||||
const utils = require("./utils")
|
||||
|
||||
let userClient, sessionClient
|
||||
let userClient, sessionClient, appClient
|
||||
|
||||
async function init() {
|
||||
userClient = await new Client(utils.Databases.USER_CACHE).init()
|
||||
sessionClient = await new Client(utils.Databases.SESSIONS).init()
|
||||
appClient = await new Client(utils.Databases.APP_METADATA).init()
|
||||
}
|
||||
|
||||
process.on("exit", async () => {
|
||||
if (userClient) await userClient.finish()
|
||||
if (sessionClient) await sessionClient.finish()
|
||||
if (appClient) await appClient.finish()
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
|
@ -26,4 +28,10 @@ module.exports = {
|
|||
}
|
||||
return sessionClient
|
||||
},
|
||||
getAppClient: async () => {
|
||||
if (!appClient) {
|
||||
await init()
|
||||
}
|
||||
return appClient
|
||||
},
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ exports.Databases = {
|
|||
SESSIONS: "session",
|
||||
USER_CACHE: "users",
|
||||
FLAGS: "flags",
|
||||
APP_METADATA: "appMetadata",
|
||||
}
|
||||
|
||||
exports.SEPARATOR = SEPARATOR
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/bbui",
|
||||
"description": "A UI solution used in the different Budibase projects.",
|
||||
"version": "0.9.185-alpha.0",
|
||||
"version": "0.9.185-alpha.14",
|
||||
"license": "AGPL-3.0",
|
||||
"svelte": "src/index.js",
|
||||
"module": "dist/bbui.es.js",
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
export let readonly = false
|
||||
export let updateOnChange = true
|
||||
export let quiet = false
|
||||
export let dataCy
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let focus = false
|
||||
|
@ -78,6 +79,7 @@
|
|||
{disabled}
|
||||
{readonly}
|
||||
{id}
|
||||
data-cy={dataCy}
|
||||
value={value || ""}
|
||||
placeholder={placeholder || ""}
|
||||
on:click
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
export let error = null
|
||||
export let updateOnChange = true
|
||||
export let quiet = false
|
||||
export let dataCy
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const onChange = e => {
|
||||
|
@ -23,6 +24,7 @@
|
|||
|
||||
<Field {label} {labelPosition} {error}>
|
||||
<TextField
|
||||
{dataCy}
|
||||
{updateOnChange}
|
||||
{error}
|
||||
{disabled}
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
export let onConfirm = undefined
|
||||
|
||||
$: icon = selectIcon(type)
|
||||
// if newlines used, convert them to different elements
|
||||
$: split = message.split("\n")
|
||||
|
||||
function selectIcon(alertType) {
|
||||
switch (alertType) {
|
||||
|
@ -33,7 +35,9 @@
|
|||
<use xlink:href="#spectrum-icon-18-{icon}" />
|
||||
</svg>
|
||||
<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}
|
||||
<div class="spectrum-InLineAlert-footer">
|
||||
<Button secondary on:click={onConfirm}>OK</Button>
|
||||
|
@ -47,5 +51,6 @@
|
|||
--spectrum-semantic-positive-border-color: #2d9d78;
|
||||
--spectrum-semantic-positive-icon-color: #2d9d78;
|
||||
--spectrum-semantic-negative-icon-color: #e34850;
|
||||
min-width: 100px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,16 +1,67 @@
|
|||
<script>
|
||||
import "@spectrum-css/fieldlabel/dist/index-vars.css"
|
||||
import Tooltip from "../Tooltip/Tooltip.svelte"
|
||||
import Icon from "../Icon/Icon.svelte"
|
||||
|
||||
export let size = "M"
|
||||
export let tooltip = ""
|
||||
export let showTooltip = false
|
||||
</script>
|
||||
|
||||
<label for="" class={`spectrum-FieldLabel spectrum-FieldLabel--size${size}`}>
|
||||
<slot />
|
||||
</label>
|
||||
{#if tooltip}
|
||||
<div class="container">
|
||||
<label
|
||||
for=""
|
||||
class={`spectrum-FieldLabel spectrum-FieldLabel--size${size}`}
|
||||
>
|
||||
<slot />
|
||||
</label>
|
||||
<div class="icon-container">
|
||||
<div
|
||||
class="icon"
|
||||
on:mouseover={() => (showTooltip = true)}
|
||||
on:mouseleave={() => (showTooltip = false)}
|
||||
>
|
||||
<Icon name="InfoOutline" size="S" disabled={true} />
|
||||
</div>
|
||||
{#if showTooltip}
|
||||
<div class="tooltip">
|
||||
<Tooltip textWrapping={true} direction={"bottom"} text={tooltip} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<label for="" class={`spectrum-FieldLabel spectrum-FieldLabel--size${size}`}>
|
||||
<slot />
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
label {
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
}
|
||||
.icon-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 1px;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.tooltip {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
top: 15px;
|
||||
z-index: 1;
|
||||
width: 160px;
|
||||
}
|
||||
.icon {
|
||||
transform: scale(0.75);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -3,12 +3,22 @@
|
|||
|
||||
export let direction = "top"
|
||||
export let text = ""
|
||||
export let textWrapping = false
|
||||
</script>
|
||||
|
||||
<span class="u-tooltip-showOnHover tooltip">
|
||||
<slot />
|
||||
<div class={`spectrum-Tooltip spectrum-Tooltip--${direction}`}>
|
||||
<!-- Showing / Hiding a text wrapped tooltip should be handled outside the component -->
|
||||
{#if textWrapping}
|
||||
<span class="spectrum-Tooltip spectrum-Tooltip--{direction} is-open">
|
||||
<span class="spectrum-Tooltip-label">{text}</span>
|
||||
<span class="spectrum-Tooltip-tip" />
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
{:else}
|
||||
<!-- The default show on hover tooltip does not support text wrapping -->
|
||||
<span class="u-tooltip-showOnHover tooltip">
|
||||
<slot />
|
||||
<div class={`spectrum-Tooltip spectrum-Tooltip--${direction}`}>
|
||||
<span class="spectrum-Tooltip-label">{text}</span>
|
||||
<span class="spectrum-Tooltip-tip" />
|
||||
</div>
|
||||
</span>
|
||||
{/if}
|
||||
|
|
|
@ -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.contains("Delete").click()
|
||||
cy.wait(50)
|
||||
cy.get(`[data-cy="delete-column-confirm"]`).type("nameupdated")
|
||||
cy.contains("Delete Column").click()
|
||||
cy.contains("nameupdated").should("not.exist")
|
||||
})
|
||||
|
@ -66,6 +67,7 @@ context("Create a Table", () => {
|
|||
cy.get(".actions .spectrum-Icon").click({ force: true })
|
||||
})
|
||||
cy.get(".spectrum-Menu > :nth-child(2)").click()
|
||||
cy.get(`[data-cy="delete-table-confirm"]`).type("dog")
|
||||
cy.contains("Delete Table").click()
|
||||
cy.contains("dog").should("not.exist")
|
||||
})
|
||||
|
|
|
@ -17,6 +17,7 @@ process.env.JWT_SECRET = cypressConfig.env.JWT_SECRET
|
|||
process.env.COUCH_URL = `leveldb://${tmpdir}/.data/`
|
||||
process.env.SELF_HOSTED = 1
|
||||
process.env.WORKER_URL = "http://localhost:10002/"
|
||||
process.env.APPS_URL = `http://localhost:${MAIN_PORT}/`
|
||||
process.env.MINIO_URL = `http://localhost:${MAIN_PORT}/`
|
||||
process.env.MINIO_ACCESS_KEY = "budibase"
|
||||
process.env.MINIO_SECRET_KEY = "budibase"
|
||||
|
|
|
@ -188,8 +188,16 @@ Cypress.Commands.add("navigateToFrontend", () => {
|
|||
Cypress.Commands.add("createScreen", (screenName, route) => {
|
||||
cy.get("[aria-label=AddCircle]").click()
|
||||
cy.get(".spectrum-Modal").within(() => {
|
||||
cy.get("input").first().type(screenName)
|
||||
cy.get("input").eq(1).type(route)
|
||||
cy.get(".item").first().click()
|
||||
cy.get(".spectrum-Button--cta").click()
|
||||
})
|
||||
cy.get(".spectrum-Modal").within(() => {
|
||||
cy.get("input").first().clear().type(screenName)
|
||||
cy.get("input").eq(1).clear().type(route)
|
||||
cy.get(".spectrum-Button--cta").click()
|
||||
})
|
||||
cy.get(".spectrum-Modal").within(() => {
|
||||
cy.get(`[data-cy="left-nav"]`).click()
|
||||
cy.get(".spectrum-Button--cta").click()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/builder",
|
||||
"version": "0.9.185-alpha.0",
|
||||
"version": "0.9.185-alpha.14",
|
||||
"license": "AGPL-3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
@ -9,7 +9,7 @@
|
|||
"test": "jest",
|
||||
"test:watch": "jest --watchAll",
|
||||
"dev:builder": "routify -c dev:vite",
|
||||
"dev:vite": "vite",
|
||||
"dev:vite": "vite --host 0.0.0.0",
|
||||
"rollup": "rollup -c -w",
|
||||
"cy:setup": "node ./cypress/setup.js",
|
||||
"cy:run": "cypress run",
|
||||
|
@ -65,10 +65,10 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "^0.9.185-alpha.0",
|
||||
"@budibase/client": "^0.9.185-alpha.0",
|
||||
"@budibase/bbui": "^0.9.185-alpha.14",
|
||||
"@budibase/client": "^0.9.185-alpha.14",
|
||||
"@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",
|
||||
"@spectrum-css/page": "^3.0.1",
|
||||
"@spectrum-css/vars": "^3.0.1",
|
||||
|
@ -91,7 +91,7 @@
|
|||
"@babel/runtime": "^7.13.10",
|
||||
"@rollup/plugin-replace": "^2.4.2",
|
||||
"@roxi/routify": "2.18.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.5",
|
||||
"@sveltejs/vite-plugin-svelte": "1.0.0-next.19",
|
||||
"@testing-library/jest-dom": "^5.11.10",
|
||||
"@testing-library/svelte": "^3.0.0",
|
||||
"babel-jest": "^26.6.3",
|
||||
|
|
|
@ -207,11 +207,11 @@ const getProviderContextBindings = (asset, dataProviders) => {
|
|||
const keys = Object.keys(schema).sort()
|
||||
|
||||
// Generate safe unique runtime prefix
|
||||
let runtimeId = component._id
|
||||
let providerId = component._id
|
||||
if (runtimeSuffix) {
|
||||
runtimeId += `-${runtimeSuffix}`
|
||||
providerId += `-${runtimeSuffix}`
|
||||
}
|
||||
const safeComponentId = makePropSafe(runtimeId)
|
||||
const safeComponentId = makePropSafe(providerId)
|
||||
|
||||
// Create bindable properties for each schema field
|
||||
keys.forEach(key => {
|
||||
|
@ -235,7 +235,7 @@ const getProviderContextBindings = (asset, dataProviders) => {
|
|||
// Field schema and provider are required to construct relationship
|
||||
// datasource options, based on bindable properties
|
||||
fieldSchema,
|
||||
providerId: component._id,
|
||||
providerId,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -333,8 +333,11 @@ const getUrlBindings = asset => {
|
|||
*/
|
||||
export const getSchemaForDatasource = (asset, datasource, isForm = false) => {
|
||||
let schema, table
|
||||
|
||||
if (datasource) {
|
||||
const { type } = datasource
|
||||
|
||||
// Determine the source table from the datasource type
|
||||
if (type === "provider") {
|
||||
const component = findComponent(asset.props, datasource.providerId)
|
||||
const source = getDatasourceForProvider(asset, component)
|
||||
|
@ -342,11 +345,32 @@ export const getSchemaForDatasource = (asset, datasource, isForm = false) => {
|
|||
} else if (type === "query") {
|
||||
const queries = get(queriesStores).list
|
||||
table = queries.find(query => query._id === datasource._id)
|
||||
} else if (type === "field") {
|
||||
table = { name: datasource.fieldName }
|
||||
const { fieldType } = datasource
|
||||
if (fieldType === "attachment") {
|
||||
schema = {
|
||||
url: {
|
||||
type: "string",
|
||||
},
|
||||
name: {
|
||||
type: "string",
|
||||
},
|
||||
}
|
||||
} else if (fieldType === "array") {
|
||||
schema = {
|
||||
value: {
|
||||
type: "string",
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const tables = get(tablesStore).list
|
||||
table = tables.find(table => table._id === datasource.tableId)
|
||||
}
|
||||
if (table) {
|
||||
|
||||
// Determine the schema from the table if not already determined
|
||||
if (table && !schema) {
|
||||
if (type === "view") {
|
||||
schema = cloneDeep(table.views?.[datasource.name]?.schema)
|
||||
} else if (type === "query" && isForm) {
|
||||
|
@ -525,7 +549,7 @@ function bindingReplacement(bindableProperties, textWithBindings, convertTo) {
|
|||
* {{ literal [componentId] }}
|
||||
*/
|
||||
function extractLiteralHandlebarsID(value) {
|
||||
return value?.match(/{{\s*literal[\s[]+([a-fA-F0-9]+)[\s\]]*}}/)?.[1]
|
||||
return value?.match(/{{\s*literal\s*\[+([^\]]+)].*}}/)?.[1]
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -620,6 +620,9 @@ export const getFrontendStore = () => {
|
|||
if (!name || !component) {
|
||||
return
|
||||
}
|
||||
if (component[name] === value) {
|
||||
return
|
||||
}
|
||||
component[name] = value
|
||||
store.update(state => {
|
||||
state.selectedComponentId = component._id
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Screen } from "./utils/Screen"
|
|||
|
||||
export default {
|
||||
name: `Create from scratch`,
|
||||
id: `createFromScratch`,
|
||||
create: () => createScreen(),
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import CreateViewButton from "./buttons/CreateViewButton.svelte"
|
||||
import ExistingRelationshipButton from "./buttons/ExistingRelationshipButton.svelte"
|
||||
import ExportButton from "./buttons/ExportButton.svelte"
|
||||
import ImportButton from "./buttons/ImportButton.svelte"
|
||||
import EditRolesButton from "./buttons/EditRolesButton.svelte"
|
||||
import ManageAccessButton from "./buttons/ManageAccessButton.svelte"
|
||||
import HideAutocolumnButton from "./buttons/HideAutocolumnButton.svelte"
|
||||
|
@ -124,6 +125,10 @@
|
|||
<HideAutocolumnButton bind:hideAutocolumns />
|
||||
<!-- always have the export last -->
|
||||
<ExportButton view={$tables.selected?._id} />
|
||||
<ImportButton
|
||||
tableId={$tables.selected?._id}
|
||||
on:updaterows={onUpdateRows}
|
||||
/>
|
||||
{#key id}
|
||||
<TableFilterButton {schema} on:change={onFilter} />
|
||||
{/key}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
let modal
|
||||
</script>
|
||||
|
||||
<ActionButton icon="Download" size="S" quiet on:click={modal.show}>
|
||||
<ActionButton icon="DataDownload" size="S" quiet on:click={modal.show}>
|
||||
Export
|
||||
</ActionButton>
|
||||
<Modal bind:this={modal}>
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<script>
|
||||
import { ActionButton, Modal } from "@budibase/bbui"
|
||||
import ImportModal from "../modals/ImportModal.svelte"
|
||||
|
||||
export let tableId
|
||||
|
||||
let modal
|
||||
</script>
|
||||
|
||||
<ActionButton icon="DataUpload" size="S" quiet on:click={modal.show}>
|
||||
Import
|
||||
</ActionButton>
|
||||
<Modal bind:this={modal}>
|
||||
<ImportModal {tableId} on:updaterows />
|
||||
</Modal>
|
|
@ -62,6 +62,7 @@
|
|||
let indexes = [...($tables.selected.indexes || [])]
|
||||
let confirmDeleteDialog
|
||||
let deletion
|
||||
let deleteColName
|
||||
|
||||
$: checkConstraints(field)
|
||||
$: required = !!field?.constraints?.presence || primaryDisplay
|
||||
|
@ -179,6 +180,7 @@
|
|||
|
||||
function hideDeleteDialog() {
|
||||
confirmDeleteDialog.hide()
|
||||
deleteColName = ""
|
||||
deletion = false
|
||||
}
|
||||
|
||||
|
@ -408,9 +410,20 @@
|
|||
</ModalContent>
|
||||
<ConfirmDialog
|
||||
bind:this={confirmDeleteDialog}
|
||||
body={`Are you sure you wish to delete this column? Your data will be deleted and this action cannot be undone.`}
|
||||
okText="Delete Column"
|
||||
onOk={deleteColumn}
|
||||
onCancel={hideDeleteDialog}
|
||||
title="Confirm Deletion"
|
||||
/>
|
||||
disabled={deleteColName !== field.name}
|
||||
>
|
||||
<p>
|
||||
Are you sure you wish to delete the column <b>{field.name}?</b>
|
||||
Your data will be deleted and this action cannot be undone - enter the column
|
||||
name to confirm.
|
||||
</p>
|
||||
<Input
|
||||
dataCy="delete-column-confirm"
|
||||
bind:value={deleteColName}
|
||||
placeholder={field.name}
|
||||
/>
|
||||
</ConfirmDialog>
|
||||
|
|
|
@ -69,6 +69,7 @@
|
|||
({ _id }) => _id === $views.selected?.tableId
|
||||
)
|
||||
$: fields = viewTable && Object.keys(viewTable.schema)
|
||||
$: schema = viewTable && viewTable.schema ? viewTable.schema : {}
|
||||
|
||||
function saveView() {
|
||||
views.save(view)
|
||||
|
@ -90,29 +91,29 @@
|
|||
|
||||
function isMultipleChoice(field) {
|
||||
return (
|
||||
viewTable.schema[field]?.constraints?.inclusion?.length ||
|
||||
viewTable.schema[field]?.type === "boolean"
|
||||
schema[field]?.constraints?.inclusion?.length ||
|
||||
schema[field]?.type === "boolean"
|
||||
)
|
||||
}
|
||||
|
||||
function fieldOptions(field) {
|
||||
return viewTable.schema[field]?.type === "options"
|
||||
? viewTable.schema[field]?.constraints.inclusion
|
||||
return schema[field]?.type === "options"
|
||||
? schema[field]?.constraints.inclusion
|
||||
: [true, false]
|
||||
}
|
||||
|
||||
function isDate(field) {
|
||||
return viewTable.schema[field]?.type === "datetime"
|
||||
return schema[field]?.type === "datetime"
|
||||
}
|
||||
|
||||
function isNumber(field) {
|
||||
return viewTable.schema[field]?.type === "number"
|
||||
return schema[field]?.type === "number"
|
||||
}
|
||||
|
||||
const fieldChanged = filter => ev => {
|
||||
// Reset if type changed
|
||||
const oldType = viewTable.schema[filter.key]?.type
|
||||
const newType = viewTable.schema[ev.detail]?.type
|
||||
const oldType = schema[filter.key]?.type
|
||||
const newType = schema[ev.detail]?.type
|
||||
if (filter.key && ev.detail && oldType !== newType) {
|
||||
filter.value = ""
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<script>
|
||||
import { ModalContent, Label, notifications, Body } from "@budibase/bbui"
|
||||
import TableDataImport from "../../TableNavigator/TableDataImport.svelte"
|
||||
import api from "builderStore/api"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let tableId
|
||||
let dataImport
|
||||
|
||||
$: valid = dataImport?.csvString != null && dataImport?.valid
|
||||
|
||||
async function importData() {
|
||||
const response = await api.post(`/api/tables/${tableId}/import`, {
|
||||
dataImport,
|
||||
})
|
||||
if (response.status !== 200) {
|
||||
const error = await response.text()
|
||||
notifications.error(`Unable to import data - ${error}`)
|
||||
} else {
|
||||
notifications.success("Rows successfully imported.")
|
||||
}
|
||||
dispatch("updaterows")
|
||||
}
|
||||
</script>
|
||||
|
||||
<ModalContent
|
||||
title="Import Data"
|
||||
confirmText="Import"
|
||||
onConfirm={importData}
|
||||
disabled={!valid}
|
||||
>
|
||||
<Body
|
||||
>Import rows to an existing table from a CSV. Only columns from the CSV
|
||||
which exist in the table will be imported.</Body
|
||||
>
|
||||
<Label grey extraSmall>CSV to import</Label>
|
||||
<TableDataImport bind:dataImport bind:existingTableId={tableId} />
|
||||
</ModalContent>
|
||||
|
||||
<style>
|
||||
</style>
|
|
@ -23,8 +23,6 @@
|
|||
// Show updated permissions in UI: REMOVE
|
||||
permissions = await permissionsStore.forResource(resourceId)
|
||||
notifications.success("Updated permissions.")
|
||||
// TODO: update permissions
|
||||
// permissions[]
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<script>
|
||||
import { Select } from "@budibase/bbui"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import { Select, InlineAlert, notifications } from "@budibase/bbui"
|
||||
import { FIELDS } from "constants/backend"
|
||||
import api from "builderStore/api"
|
||||
|
||||
|
@ -12,11 +11,13 @@
|
|||
valid: true,
|
||||
schema: {},
|
||||
}
|
||||
export let existingTableId
|
||||
|
||||
let csvString
|
||||
let primaryDisplay
|
||||
let csvString = undefined
|
||||
let primaryDisplay = undefined
|
||||
let schema = {}
|
||||
let fields = []
|
||||
let hasValidated = false
|
||||
|
||||
$: valid = !schema || fields.every(column => schema[column].success)
|
||||
$: dataImport = {
|
||||
|
@ -25,6 +26,9 @@
|
|||
csvString,
|
||||
primaryDisplay,
|
||||
}
|
||||
$: noFieldsError = existingTableId
|
||||
? "No columns in CSV match existing table schema"
|
||||
: "Could not find any columns to import"
|
||||
|
||||
function buildTableSchema(schema) {
|
||||
const tableSchema = {}
|
||||
|
@ -46,6 +50,7 @@
|
|||
const response = await api.post("/api/tables/csv/validate", {
|
||||
csvString,
|
||||
schema: schema || {},
|
||||
tableId: existingTableId,
|
||||
})
|
||||
|
||||
const parseResult = await response.json()
|
||||
|
@ -63,6 +68,7 @@
|
|||
notifications.error("CSV Invalid, please try another CSV file")
|
||||
return []
|
||||
}
|
||||
hasValidated = true
|
||||
}
|
||||
|
||||
async function handleFile(evt) {
|
||||
|
@ -138,6 +144,7 @@
|
|||
placeholder={null}
|
||||
getOptionLabel={option => option.label}
|
||||
getOptionValue={option => option.value}
|
||||
disabled={!!existingTableId}
|
||||
/>
|
||||
<span class="field-status" class:error={!schema[columnName].success}>
|
||||
{schema[columnName].success ? "Success" : "Failure"}
|
||||
|
@ -149,15 +156,22 @@
|
|||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if fields.length}
|
||||
<div class="display-column">
|
||||
<Select
|
||||
label="Display Column"
|
||||
bind:value={primaryDisplay}
|
||||
options={fields}
|
||||
sort
|
||||
{#if !existingTableId}
|
||||
<div class="display-column">
|
||||
<Select
|
||||
label="Display Column"
|
||||
bind:value={primaryDisplay}
|
||||
options={fields}
|
||||
sort
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{:else if hasValidated}
|
||||
<div>
|
||||
<InlineAlert
|
||||
header="Invalid CSV"
|
||||
bind:message={noFieldsError}
|
||||
type="error"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -21,8 +21,10 @@
|
|||
let originalName = table.name
|
||||
let templateScreens
|
||||
let willBeDeleted
|
||||
let deleteTableName
|
||||
|
||||
$: internal = table?.type === "internal"
|
||||
$: external = table?.type === "external"
|
||||
$: allowDeletion = !external || table?.created
|
||||
|
||||
function showDeleteModal() {
|
||||
templateScreens = $allScreens.filter(
|
||||
|
@ -36,18 +38,26 @@
|
|||
|
||||
async function deleteTable() {
|
||||
const wasSelectedTable = $tables.selected
|
||||
await tables.delete(table)
|
||||
store.actions.screens.delete(templateScreens)
|
||||
await tables.fetch()
|
||||
notifications.success("Table deleted")
|
||||
if (table.type === "external") {
|
||||
await datasources.fetch()
|
||||
}
|
||||
if (wasSelectedTable && wasSelectedTable._id === table._id) {
|
||||
$goto("./table")
|
||||
try {
|
||||
await tables.delete(table)
|
||||
await store.actions.screens.delete(templateScreens)
|
||||
await tables.fetch()
|
||||
if (table.type === "external") {
|
||||
await datasources.fetch()
|
||||
}
|
||||
notifications.success("Table deleted")
|
||||
if (wasSelectedTable && wasSelectedTable._id === table._id) {
|
||||
$goto("./table")
|
||||
}
|
||||
} catch (err) {
|
||||
notifications.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
function hideDeleteDialog() {
|
||||
deleteTableName = ""
|
||||
}
|
||||
|
||||
async function save() {
|
||||
await tables.save(table)
|
||||
notifications.success("Table renamed successfully")
|
||||
|
@ -66,10 +76,12 @@
|
|||
<div slot="control" class="icon">
|
||||
<Icon s hoverable name="MoreSmallList" />
|
||||
</div>
|
||||
{#if internal}
|
||||
{#if external}
|
||||
<MenuItem icon="Edit" on:click={editorModal.show}>Edit</MenuItem>
|
||||
{/if}
|
||||
<MenuItem icon="Delete" on:click={showDeleteModal}>Delete</MenuItem>
|
||||
{#if allowDeletion}
|
||||
<MenuItem icon="Delete" on:click={showDeleteModal}>Delete</MenuItem>
|
||||
{/if}
|
||||
</ActionMenu>
|
||||
|
||||
<Modal bind:this={editorModal}>
|
||||
|
@ -92,11 +104,15 @@
|
|||
bind:this={confirmDeleteDialog}
|
||||
okText="Delete Table"
|
||||
onOk={deleteTable}
|
||||
onCancel={hideDeleteDialog}
|
||||
title="Confirm Deletion"
|
||||
disabled={deleteTableName !== table.name}
|
||||
>
|
||||
Are you sure you wish to delete the table
|
||||
<i>{table.name}?</i>
|
||||
The following will also be deleted:
|
||||
<p>
|
||||
Are you sure you wish to delete the table
|
||||
<b>{table.name}?</b>
|
||||
The following will also be deleted:
|
||||
</p>
|
||||
<b>
|
||||
<div class="delete-items">
|
||||
{#each willBeDeleted as item}
|
||||
|
@ -104,7 +120,15 @@
|
|||
{/each}
|
||||
</div>
|
||||
</b>
|
||||
This action cannot be undone.
|
||||
<p>
|
||||
This action cannot be undone - to continue please enter the table name below
|
||||
to confirm.
|
||||
</p>
|
||||
<Input
|
||||
bind:value={deleteTableName}
|
||||
placeholder={table.name}
|
||||
dataCy="delete-table-confirm"
|
||||
/>
|
||||
</ConfirmDialog>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -36,11 +36,9 @@
|
|||
// Messages that can be sent from the iframe preview to the builder
|
||||
// Budibase events are and initalisation events
|
||||
const MessageTypes = {
|
||||
IFRAME_LOADED: "iframe-loaded",
|
||||
READY: "ready",
|
||||
ERROR: "error",
|
||||
BUDIBASE: "type",
|
||||
KEYDOWN: "keydown"
|
||||
}
|
||||
|
||||
// Construct iframe template
|
||||
|
@ -69,7 +67,7 @@
|
|||
theme: $store.theme,
|
||||
customTheme: $store.customTheme,
|
||||
previewDevice: $store.previewDevice,
|
||||
messagePassing: $store.clientFeatures.messagePassing
|
||||
messagePassing: $store.clientFeatures.messagePassing,
|
||||
}
|
||||
|
||||
// Saving pages and screens to the DB causes them to have _revs.
|
||||
|
@ -111,7 +109,6 @@
|
|||
loading = false
|
||||
error = event.error || "An unknown error occurred"
|
||||
},
|
||||
[MessageTypes.KEYDOWN]: handleKeydownEvent
|
||||
}
|
||||
|
||||
const messageHandler = handlers[message.data.type] || handleBudibaseEvent
|
||||
|
@ -122,15 +119,24 @@
|
|||
window.addEventListener("message", receiveMessage)
|
||||
if (!$store.clientFeatures.messagePassing) {
|
||||
// Legacy - remove in later versions of BB
|
||||
iframe.contentWindow.addEventListener("ready", () => {
|
||||
receiveMessage({ data: { type: MessageTypes.READY }})
|
||||
}, { once: true })
|
||||
iframe.contentWindow.addEventListener("error", event => {
|
||||
receiveMessage({ data: { type: MessageTypes.ERROR, error: event.detail }})
|
||||
}, { once: true })
|
||||
iframe.contentWindow.addEventListener(
|
||||
"ready",
|
||||
() => {
|
||||
receiveMessage({ data: { type: MessageTypes.READY } })
|
||||
},
|
||||
{ once: true }
|
||||
)
|
||||
iframe.contentWindow.addEventListener(
|
||||
"error",
|
||||
event => {
|
||||
receiveMessage({
|
||||
data: { type: MessageTypes.ERROR, error: event.detail },
|
||||
})
|
||||
},
|
||||
{ once: true }
|
||||
)
|
||||
// Add listener for events sent by client library in preview
|
||||
iframe.contentWindow.addEventListener("bb-event", handleBudibaseEvent)
|
||||
iframe.contentWindow.addEventListener("keydown", handleKeydownEvent)
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -140,14 +146,20 @@
|
|||
window.removeEventListener("message", receiveMessage)
|
||||
if (!$store.clientFeatures.messagePassing) {
|
||||
// Legacy - remove in later versions of BB
|
||||
iframe.contentWindow.removeEventListener("bb-event", handleBudibaseEvent)
|
||||
iframe.contentWindow.removeEventListener("keydown", handleKeydownEvent)
|
||||
iframe.contentWindow.removeEventListener(
|
||||
"bb-event",
|
||||
handleBudibaseEvent
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const handleBudibaseEvent = event => {
|
||||
const { type, data } = event.data || event.detail
|
||||
if (!type) {
|
||||
return
|
||||
}
|
||||
|
||||
if (type === "select-component" && data.id) {
|
||||
store.actions.components.select({ _id: data.id })
|
||||
} else if (type === "update-prop") {
|
||||
|
@ -183,19 +195,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
const handleKeydownEvent = event => {
|
||||
const { key } = event.data || event
|
||||
if (
|
||||
(key === "Delete" || key === "Backspace") &&
|
||||
selectedComponentId &&
|
||||
["input", "textarea"].indexOf(
|
||||
iframe.contentWindow.document.activeElement?.tagName.toLowerCase()
|
||||
) === -1
|
||||
) {
|
||||
confirmDeleteComponent(selectedComponentId)
|
||||
}
|
||||
}
|
||||
|
||||
const confirmDeleteComponent = componentId => {
|
||||
idToDelete = componentId
|
||||
confirmDeleteDialog.show()
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
"icon": "Article",
|
||||
"children": [
|
||||
"tableblock",
|
||||
"cardsblock"
|
||||
"cardsblock",
|
||||
"repeaterblock"
|
||||
]
|
||||
},
|
||||
"section",
|
||||
|
|
|
@ -84,7 +84,6 @@ export default `
|
|||
if (window.loadBudibase) {
|
||||
window.loadBudibase()
|
||||
document.documentElement.classList.add("loaded")
|
||||
window.parent.postMessage({ type: "iframe-loaded" })
|
||||
} else {
|
||||
throw "The client library couldn't be loaded"
|
||||
}
|
||||
|
@ -94,10 +93,6 @@ export default `
|
|||
}
|
||||
|
||||
window.addEventListener("message", receiveMessage)
|
||||
window.addEventListener("keydown", evt => {
|
||||
window.parent.postMessage({ type: "keydown", key: event.key })
|
||||
})
|
||||
|
||||
window.parent.postMessage({ type: "ready" })
|
||||
</script>
|
||||
</head>
|
||||
|
|
|
@ -13,6 +13,12 @@
|
|||
$: noChildrenAllowed = !component || !definition?.hasChildren
|
||||
$: noPaste = !$store.componentToPaste
|
||||
|
||||
// "editable" has been repurposed for inline text editing.
|
||||
// It remains here for legacy compatibility.
|
||||
// Future components should define "static": true for indicate they should
|
||||
// not show a context menu.
|
||||
$: showMenu = definition?.editable !== false && definition?.static !== true
|
||||
|
||||
const moveUpComponent = () => {
|
||||
const asset = get(currentAsset)
|
||||
const parent = findComponentParent(asset.props, component._id)
|
||||
|
@ -69,7 +75,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
{#if definition?.editable !== false}
|
||||
{#if showMenu}
|
||||
<ActionMenu>
|
||||
<div slot="control" class="icon">
|
||||
<Icon size="S" hoverable name="MoreSmallList" />
|
||||
|
|
|
@ -10,10 +10,11 @@
|
|||
import { roles } from "stores/backend"
|
||||
import ComponentNavigationTree from "components/design/NavigationPanel/ComponentNavigationTree/index.svelte"
|
||||
import Layout from "components/design/NavigationPanel/Layout.svelte"
|
||||
import NewScreenModal from "components/design/NavigationPanel/NewScreenModal.svelte"
|
||||
import NewLayoutModal from "components/design/NavigationPanel/NewLayoutModal.svelte"
|
||||
import { Icon, Modal, Select, Search, Tabs, Tab } from "@budibase/bbui"
|
||||
|
||||
export let showModal
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
title: "Screens",
|
||||
|
@ -85,9 +86,6 @@
|
|||
<div class="nav-items-container">
|
||||
<ComponentNavigationTree />
|
||||
</div>
|
||||
<Modal bind:this={modal}>
|
||||
<NewScreenModal />
|
||||
</Modal>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab title="Layouts">
|
||||
|
@ -102,7 +100,7 @@
|
|||
</Tab>
|
||||
</Tabs>
|
||||
<div class="add-button">
|
||||
<Icon hoverable name="AddCircle" on:click={modal.show} />
|
||||
<Icon hoverable name="AddCircle" on:click={showModal()} />
|
||||
</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>
|
||||
import { store, allScreens, selectedAccessRole } from "builderStore"
|
||||
import { store } from "builderStore"
|
||||
import { tables } from "stores/backend"
|
||||
import { roles } from "stores/backend"
|
||||
import { Input, Select, ModalContent, Toggle } from "@budibase/bbui"
|
||||
import { ModalContent, Body, Detail, Layout, Icon } from "@budibase/bbui"
|
||||
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 = ""
|
||||
let routeError
|
||||
let baseComponent = CONTAINER
|
||||
let templateIndex
|
||||
let draftScreen
|
||||
let createLink = true
|
||||
let roleId = $selectedAccessRole || "BASIC"
|
||||
const blankScreen = "createFromScratch"
|
||||
|
||||
$: blankSelected = selectedScreens?.length === 1
|
||||
$: autoSelected = selectedScreens?.length > 0 && !blankSelected
|
||||
|
||||
$: templates = getTemplates($store, $tables.list)
|
||||
$: route = !route && $allScreens.length === 0 ? "*" : route
|
||||
$: {
|
||||
if (templates && templateIndex === undefined) {
|
||||
templateIndex = 0
|
||||
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"
|
||||
const toggleScreenSelection = table => {
|
||||
if (selectedScreens.find(s => s.name.includes(table.name))) {
|
||||
selectedScreens = selectedScreens.filter(
|
||||
screen => !screen.name.includes(table.name)
|
||||
)
|
||||
} else {
|
||||
if (routeExists(route, roleId)) {
|
||||
routeError = "This URL is already taken for this access role"
|
||||
} else {
|
||||
routeError = ""
|
||||
}
|
||||
templates = templates.filter(template =>
|
||||
template.name.includes(table.name)
|
||||
)
|
||||
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>
|
||||
|
||||
<ModalContent title="New Screen" confirmText="Create Screen" onConfirm={save}>
|
||||
<Select
|
||||
label="Choose a Template"
|
||||
bind:value={templateIndex}
|
||||
on:change={ev => templateChanged(ev.detail)}
|
||||
options={templates}
|
||||
placeholder={null}
|
||||
getOptionLabel={x => x.name}
|
||||
getOptionValue={(x, idx) => idx}
|
||||
/>
|
||||
<Input label="Name" bind:value={name} />
|
||||
<Input
|
||||
label="Url"
|
||||
error={routeError}
|
||||
bind:value={route}
|
||||
on:change={routeChanged}
|
||||
/>
|
||||
<Select
|
||||
label="Access"
|
||||
bind:value={roleId}
|
||||
options={$roles}
|
||||
getOptionLabel={x => x.name}
|
||||
getOptionValue={x => x._id}
|
||||
/>
|
||||
<Toggle text="Create link in navigation bar" bind:value={createLink} />
|
||||
<ModalContent
|
||||
title="Add screens"
|
||||
confirmText="Add Screens"
|
||||
cancelText="Cancel"
|
||||
onConfirm={() => (autoSelected ? chooseModal(2) : chooseModal(1))}
|
||||
disabled={!selectedScreens.length}
|
||||
size="L"
|
||||
>
|
||||
<Body size="XS"
|
||||
>Please select the screens you would like to add to your application.
|
||||
Autogenerated screens come with CRUD functionality.</Body
|
||||
>
|
||||
|
||||
<Layout noPadding gap="S">
|
||||
<Detail size="S">Blank screen</Detail>
|
||||
<div
|
||||
class="item"
|
||||
class:selected={selectedScreens.find(x => x.id.includes(blankScreen))}
|
||||
on:click={() =>
|
||||
toggleScreenSelection(templates.find(t => t.id === blankScreen))}
|
||||
class:disabled={autoSelected}
|
||||
>
|
||||
<div data-cy="blank-screen" class="content">
|
||||
<Body size="S">Blank</Body>
|
||||
</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>
|
||||
|
||||
<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)} }}`
|
||||
|
||||
$: path = findComponentPath($currentAsset.props, $store.selectedComponentId)
|
||||
$: providers = path.filter(
|
||||
component =>
|
||||
component._component === "@budibase/standard-components/dataprovider"
|
||||
)
|
||||
$: providers = path.filter(c => c._component?.endsWith("/dataprovider"))
|
||||
|
||||
// Set initial value to closest data provider
|
||||
onMount(() => {
|
||||
|
|
|
@ -20,16 +20,18 @@
|
|||
import { notifications } from "@budibase/bbui"
|
||||
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
|
||||
import IntegrationQueryEditor from "components/integration/index.svelte"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let anchorRight, dropdownRight
|
||||
let drawer
|
||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||
|
||||
export let value = {}
|
||||
export let otherSources
|
||||
export let showAllQueries
|
||||
export let bindings = []
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const arrayTypes = ["attachment", "array"]
|
||||
let anchorRight, dropdownRight
|
||||
let drawer
|
||||
|
||||
$: text = value?.label ?? "Choose an option"
|
||||
$: tables = $tablesStore.list.map(m => ({
|
||||
label: m.name,
|
||||
|
@ -54,8 +56,6 @@
|
|||
name: query.name,
|
||||
tableId: query._id,
|
||||
...query,
|
||||
schema: query.schema,
|
||||
parameters: query.parameters,
|
||||
type: "query",
|
||||
}))
|
||||
$: dataProviders = getDataProviderComponents(
|
||||
|
@ -65,29 +65,40 @@
|
|||
label: provider._instanceName,
|
||||
name: provider._instanceName,
|
||||
providerId: provider._id,
|
||||
value: `{{ literal [${provider._id}] }}`,
|
||||
value: `{{ literal ${safe(provider._id)} }}`,
|
||||
type: "provider",
|
||||
schema: provider.schema,
|
||||
}))
|
||||
$: queryBindableProperties = bindings.map(property => ({
|
||||
...property,
|
||||
category: property.type === "instance" ? "Component" : "Table",
|
||||
label: property.readableBinding,
|
||||
path: property.readableBinding,
|
||||
}))
|
||||
$: links = bindings
|
||||
.filter(x => x.fieldSchema?.type === "link")
|
||||
.map(property => {
|
||||
.map(binding => {
|
||||
const { providerId, readableBinding, fieldSchema } = binding || {}
|
||||
const { name, tableId } = fieldSchema || {}
|
||||
const safeProviderId = safe(providerId)
|
||||
return {
|
||||
providerId: property.providerId,
|
||||
label: property.readableBinding,
|
||||
fieldName: property.fieldSchema.name,
|
||||
tableId: property.fieldSchema.tableId,
|
||||
providerId,
|
||||
label: readableBinding,
|
||||
fieldName: name,
|
||||
tableId,
|
||||
type: "link",
|
||||
// These properties will be enriched by the client library and provide
|
||||
// details of the parent row of the relationship field, from context
|
||||
rowId: `{{ ${property.providerId}._id }}`,
|
||||
rowTableId: `{{ ${property.providerId}.tableId }}`,
|
||||
rowId: `{{ ${safeProviderId}.${safe("_id")} }}`,
|
||||
rowTableId: `{{ ${safeProviderId}.${safe("tableId")} }}`,
|
||||
}
|
||||
})
|
||||
$: fields = bindings
|
||||
.filter(x => arrayTypes.includes(x.fieldSchema?.type))
|
||||
.map(binding => {
|
||||
const { providerId, readableBinding, runtimeBinding } = binding
|
||||
const { name, type, tableId } = binding.fieldSchema
|
||||
return {
|
||||
providerId,
|
||||
label: readableBinding,
|
||||
fieldName: name,
|
||||
fieldType: type,
|
||||
tableId,
|
||||
type: "field",
|
||||
value: `{{ literal ${runtimeBinding} }}`,
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -102,6 +113,14 @@
|
|||
).source
|
||||
return $integrations[source].query[query.queryVerb]
|
||||
}
|
||||
|
||||
const getQueryParams = query => {
|
||||
return $queriesStore.list.find(q => q._id === query?._id)?.parameters || []
|
||||
}
|
||||
|
||||
const getQueryDatasource = query => {
|
||||
return $datasources.list.find(ds => ds._id === query?.datasourceId)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container" bind:this={anchorRight}>
|
||||
|
@ -127,11 +146,10 @@
|
|||
</Button>
|
||||
<DrawerContent slot="body">
|
||||
<Layout noPadding>
|
||||
{#if value.parameters.length > 0}
|
||||
{#if getQueryParams(value._id).length > 0}
|
||||
<ParameterBuilder
|
||||
bind:customParams={value.queryParams}
|
||||
parameters={queries.find(query => query._id === value._id)
|
||||
.parameters}
|
||||
parameters={getQueryParams(value)}
|
||||
{bindings}
|
||||
/>
|
||||
{/if}
|
||||
|
@ -139,9 +157,7 @@
|
|||
height={200}
|
||||
query={value}
|
||||
schema={fetchQueryDefinition(value)}
|
||||
datasource={$datasources.list.find(
|
||||
ds => ds._id === value.datasourceId
|
||||
)}
|
||||
datasource={getQueryDatasource(value)}
|
||||
editable={false}
|
||||
/>
|
||||
</Layout>
|
||||
|
@ -159,52 +175,71 @@
|
|||
<li on:click={() => handleSelected(table)}>{table.label}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<Divider size="S" />
|
||||
<div class="title">
|
||||
<Heading size="XS">Views</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each views as view}
|
||||
<li on:click={() => handleSelected(view)}>{view.label}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<Divider size="S" />
|
||||
<div class="title">
|
||||
<Heading size="XS">Relationships</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each links as link}
|
||||
<li on:click={() => handleSelected(link)}>{link.label}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<Divider size="S" />
|
||||
<div class="title">
|
||||
<Heading size="XS">Queries</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each queries as query}
|
||||
<li
|
||||
class:selected={value === query}
|
||||
on:click={() => handleSelected(query)}
|
||||
>
|
||||
{query.label}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<Divider size="S" />
|
||||
<div class="title">
|
||||
<Heading size="XS">Data Providers</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each dataProviders as provider}
|
||||
<li
|
||||
class:selected={value === provider}
|
||||
on:click={() => handleSelected(provider)}
|
||||
>
|
||||
{provider.label}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{#if views?.length}
|
||||
<Divider size="S" />
|
||||
<div class="title">
|
||||
<Heading size="XS">Views</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each views as view}
|
||||
<li on:click={() => handleSelected(view)}>{view.label}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
{#if queries?.length}
|
||||
<Divider size="S" />
|
||||
<div class="title">
|
||||
<Heading size="XS">Queries</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each queries as query}
|
||||
<li
|
||||
class:selected={value === query}
|
||||
on:click={() => handleSelected(query)}
|
||||
>
|
||||
{query.label}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
{#if links?.length}
|
||||
<Divider size="S" />
|
||||
<div class="title">
|
||||
<Heading size="XS">Relationships</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each links as link}
|
||||
<li on:click={() => handleSelected(link)}>{link.label}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
{#if fields?.length}
|
||||
<Divider size="S" />
|
||||
<div class="title">
|
||||
<Heading size="XS">Fields</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each fields as field}
|
||||
<li on:click={() => handleSelected(field)}>{field.label}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
{#if dataProviders?.length}
|
||||
<Divider size="S" />
|
||||
<div class="title">
|
||||
<Heading size="XS">Data Providers</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each dataProviders as provider}
|
||||
<li
|
||||
class:selected={value === provider}
|
||||
on:click={() => handleSelected(provider)}
|
||||
>
|
||||
{provider.label}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
{#if otherSources?.length}
|
||||
<Divider size="S" />
|
||||
<div class="title">
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
} from "builderStore/dataBinding"
|
||||
|
||||
export let label = ""
|
||||
export let bindable = true
|
||||
export let componentInstance = {}
|
||||
export let control = null
|
||||
export let key = ""
|
||||
|
|
|
@ -19,15 +19,24 @@
|
|||
import IntegrationQueryEditor from "components/integration/index.svelte"
|
||||
import ExternalDataSourceTable from "components/backend/DataTable/ExternalDataSourceTable.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 CodeMirrorEditor from "components/common/CodeMirrorEditor.svelte"
|
||||
import { Roles } from "constants/backend"
|
||||
import { onMount } from "svelte"
|
||||
|
||||
export let query
|
||||
export let fields = []
|
||||
|
||||
let parameters
|
||||
let data = []
|
||||
let roleId
|
||||
const transformerDocs =
|
||||
"https://docs.budibase.com/building-apps/data/transformers"
|
||||
const typeOptions = [
|
||||
|
@ -70,7 +79,22 @@
|
|||
}
|
||||
|
||||
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) {
|
||||
|
@ -122,6 +146,7 @@
|
|||
async function saveQuery() {
|
||||
try {
|
||||
const { _id } = await queries.save(query.datasourceId, query)
|
||||
await updateRole(roleId, _id)
|
||||
notifications.success(`Query saved successfully.`)
|
||||
$goto(`../${_id}`)
|
||||
} catch (err) {
|
||||
|
@ -129,6 +154,18 @@
|
|||
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>
|
||||
|
||||
<Layout gap="S" noPadding>
|
||||
|
@ -151,6 +188,16 @@
|
|||
queryConfig[verb]?.displayName || capitalise(verb)}
|
||||
/>
|
||||
</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}
|
||||
<ExtraQueryConfig
|
||||
{query}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
let modal
|
||||
$: 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
|
||||
|
||||
onMount(() => {
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
selectedComponent,
|
||||
allScreens,
|
||||
} from "builderStore"
|
||||
import { Detail, Layout, Button, Icon } from "@budibase/bbui"
|
||||
|
||||
import CurrentItemPreview from "components/design/AppPreview"
|
||||
import PropertiesPanel from "components/design/PropertiesPanel/PropertiesPanel.svelte"
|
||||
import ComponentSelectionList from "components/design/AppPreview/ComponentSelectionList.svelte"
|
||||
|
@ -16,6 +18,8 @@
|
|||
import AppThemeSelect from "components/design/AppPreview/AppThemeSelect.svelte"
|
||||
import ThemeEditor from "components/design/AppPreview/ThemeEditor.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
|
||||
let previousType
|
||||
|
@ -23,6 +27,9 @@
|
|||
let previousComponentId
|
||||
let hydrationComplete = false
|
||||
|
||||
// Manage the layout modal flow from here
|
||||
let showModal
|
||||
|
||||
// Hydrate state from URL params
|
||||
$: hydrateStateFromURL($params, $leftover)
|
||||
|
||||
|
@ -145,7 +152,7 @@
|
|||
<!-- routify:options index=1 -->
|
||||
<div class="root">
|
||||
<div class="ui-nav">
|
||||
<FrontendNavigatePane />
|
||||
<FrontendNavigatePane {showModal} />
|
||||
</div>
|
||||
|
||||
<div class="preview-pane">
|
||||
|
@ -166,6 +173,25 @@
|
|||
<CurrentItemPreview />
|
||||
{/key}
|
||||
</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}
|
||||
</div>
|
||||
|
||||
|
@ -178,6 +204,8 @@
|
|||
|
||||
<slot />
|
||||
|
||||
<ScreenWizard bind:showModal />
|
||||
|
||||
<style>
|
||||
.root {
|
||||
display: grid;
|
||||
|
@ -186,7 +214,30 @@
|
|||
flex: 1 1 auto;
|
||||
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 {
|
||||
grid-column: 1;
|
||||
background-color: var(--background);
|
||||
|
@ -237,4 +288,21 @@
|
|||
border-left: var(--border-light);
|
||||
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>
|
||||
|
|
|
@ -165,6 +165,7 @@
|
|||
notifications.error(`Error deleting app: ${err}`)
|
||||
}
|
||||
selectedApp = null
|
||||
appName = null
|
||||
}
|
||||
|
||||
const updateApp = async app => {
|
||||
|
@ -298,12 +299,17 @@
|
|||
title="Confirm deletion"
|
||||
okText="Delete app"
|
||||
onOk={confirmDeleteApp}
|
||||
onCancel={() => (appName = null)}
|
||||
disabled={appName !== selectedApp?.name}
|
||||
>
|
||||
Are you sure you want to delete the app <b>{selectedApp?.name}</b>?
|
||||
|
||||
<p>Please enter the app name below to confirm.</p>
|
||||
<Input bind:value={appName} data-cy="delete-app-confirmation" />
|
||||
<Input
|
||||
bind:value={appName}
|
||||
data-cy="delete-app-confirmation"
|
||||
placeholder={selectedApp?.name}
|
||||
/>
|
||||
</ConfirmDialog>
|
||||
<ConfirmDialog
|
||||
bind:this={unpublishModal}
|
||||
|
|
|
@ -21,26 +21,25 @@
|
|||
} from "@budibase/bbui"
|
||||
import { onMount } from "svelte"
|
||||
import api from "builderStore/api"
|
||||
import { organisation, auth, admin } from "stores/portal"
|
||||
import { organisation, admin } from "stores/portal"
|
||||
import { uuid } from "builderStore/uuid"
|
||||
import analytics, { Events } from "analytics"
|
||||
|
||||
$: tenantId = $auth.tenantId
|
||||
$: multiTenancyEnabled = $admin.multiTenancy
|
||||
|
||||
const ConfigTypes = {
|
||||
Google: "google",
|
||||
OIDC: "oidc",
|
||||
}
|
||||
|
||||
function callbackUrl(tenantId, end) {
|
||||
let url = `/api/global/auth`
|
||||
if (multiTenancyEnabled && tenantId) {
|
||||
url += `/${tenantId}`
|
||||
}
|
||||
url += end
|
||||
return url
|
||||
}
|
||||
// Some older google configs contain a manually specified value - retain the functionality to edit the field
|
||||
// When there is no value or we are in the cloud - prohibit editing the field, must use platform url to change
|
||||
$: googleCallbackUrl = undefined
|
||||
$: googleCallbackReadonly = $admin.cloud || !googleCallbackUrl
|
||||
|
||||
// Indicate to user that callback is based on platform 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 = {
|
||||
Google: [
|
||||
|
@ -49,8 +48,9 @@
|
|||
{
|
||||
name: "callbackURL",
|
||||
label: "Callback URL",
|
||||
readonly: true,
|
||||
placeholder: callbackUrl(tenantId, "/google/callback"),
|
||||
readonly: googleCallbackReadonly,
|
||||
tooltip: googleCallbackTooltip,
|
||||
placeholder: $organisation.googleCallbackUrl,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
@ -62,9 +62,10 @@
|
|||
{ name: "clientSecret", label: "Client Secret" },
|
||||
{
|
||||
name: "callbackURL",
|
||||
label: "Callback URL",
|
||||
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
|
||||
}
|
||||
|
||||
googleCallbackUrl = providers?.google?.config?.callbackURL
|
||||
|
||||
//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
|
||||
const res = await api.get(`/api/global/configs/logos_oidc`)
|
||||
|
@ -308,7 +311,7 @@
|
|||
<Layout gap="XS" noPadding>
|
||||
{#each GoogleConfigFields.Google as field}
|
||||
<div class="form-row">
|
||||
<Label size="L">{field.label}</Label>
|
||||
<Label size="L" tooltip={field.tooltip}>{field.label}</Label>
|
||||
<Input
|
||||
bind:value={providers.google.config[field.name]}
|
||||
readonly={field.readonly}
|
||||
|
@ -346,7 +349,7 @@
|
|||
<Layout gap="XS" noPadding>
|
||||
{#each OIDCConfigFields.Oidc as field}
|
||||
<div class="form-row">
|
||||
<Label size="L">{field.label}</Label>
|
||||
<Label size="L" tooltip={field.tooltip}>{field.label}</Label>
|
||||
<Input
|
||||
bind:value={providers.oidc.config.configs[0][field.name]}
|
||||
readonly={field.readonly}
|
||||
|
|
|
@ -116,7 +116,11 @@
|
|||
</Layout>
|
||||
<div class="fields">
|
||||
<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} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -135,6 +139,7 @@
|
|||
.field {
|
||||
display: grid;
|
||||
grid-template-columns: 100px 1fr;
|
||||
grid-gap: var(--spacing-l);
|
||||
align-items: center;
|
||||
}
|
||||
.file {
|
||||
|
|
|
@ -95,6 +95,7 @@ export function createDatasourcesStore() {
|
|||
return { list: sources, selected: null }
|
||||
})
|
||||
|
||||
await queries.fetch()
|
||||
return response
|
||||
},
|
||||
removeSchemaError: () => {
|
||||
|
|
|
@ -10,13 +10,11 @@ export function createPermissionStore() {
|
|||
const response = await api.post(
|
||||
`/api/permission/${role}/${resource}/${level}`
|
||||
)
|
||||
const json = await response.json()
|
||||
return json
|
||||
return await response.json()
|
||||
},
|
||||
forResource: async resourceId => {
|
||||
const response = await api.get(`/api/permission/${resourceId}`)
|
||||
const json = await response.json()
|
||||
return json
|
||||
return await response.json()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { writable, get } from "svelte/store"
|
||||
import { views, queries, datasources } from "./"
|
||||
import { get, writable } from "svelte/store"
|
||||
import { datasources, queries, views } from "./"
|
||||
import { cloneDeep } from "lodash/fp"
|
||||
import api from "builderStore/api"
|
||||
import { SWITCHABLE_TYPES } from "../../constants/backend"
|
||||
|
@ -97,7 +97,12 @@ export function createTablesStore() {
|
|||
})
|
||||
},
|
||||
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 => ({
|
||||
...state,
|
||||
list: state.list.filter(existing => existing._id !== table._id),
|
||||
|
|
|
@ -3,12 +3,14 @@ import api from "builderStore/api"
|
|||
import { auth } from "stores/portal"
|
||||
|
||||
const DEFAULT_CONFIG = {
|
||||
platformUrl: "http://localhost:10000",
|
||||
platformUrl: "",
|
||||
logoUrl: undefined,
|
||||
docsUrl: undefined,
|
||||
company: "Budibase",
|
||||
oidc: undefined,
|
||||
google: undefined,
|
||||
oidcCallbackUrl: "",
|
||||
googleCallbackUrl: "",
|
||||
}
|
||||
|
||||
export function createOrganisationStore() {
|
||||
|
@ -28,6 +30,13 @@ export function createOrganisationStore() {
|
|||
}
|
||||
|
||||
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", {
|
||||
type: "settings",
|
||||
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 path from "path"
|
||||
|
@ -6,6 +6,11 @@ import path from "path"
|
|||
export default ({ mode }) => {
|
||||
const isProduction = mode === "production"
|
||||
return {
|
||||
server: {
|
||||
fs: {
|
||||
strict: false,
|
||||
},
|
||||
},
|
||||
base: "/builder/",
|
||||
build: {
|
||||
minify: isProduction,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/cli",
|
||||
"version": "0.9.185-alpha.0",
|
||||
"version": "0.9.185-alpha.14",
|
||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||
"main": "src/index.js",
|
||||
"bin": {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -240,13 +240,15 @@
|
|||
"name": "Screenslot",
|
||||
"icon": "WebPage",
|
||||
"description": "Contains your app screens",
|
||||
"editable": false
|
||||
"static": true
|
||||
},
|
||||
"button": {
|
||||
"name": "Button",
|
||||
"description": "A basic html button that is ready for styling",
|
||||
"icon": "Button",
|
||||
"editable": true,
|
||||
"illegalChildren": ["section"],
|
||||
"showSettingsBar": true,
|
||||
"settings": [
|
||||
{
|
||||
"type": "text",
|
||||
|
@ -255,6 +257,7 @@
|
|||
},
|
||||
{
|
||||
"type": "select",
|
||||
"showInBar": true,
|
||||
"label": "Variant",
|
||||
"key": "type",
|
||||
"options": [
|
||||
|
@ -283,6 +286,7 @@
|
|||
{
|
||||
"type": "select",
|
||||
"label": "Size",
|
||||
"showInBar": true,
|
||||
"key": "size",
|
||||
"options": [
|
||||
{
|
||||
|
@ -307,11 +311,18 @@
|
|||
{
|
||||
"type": "boolean",
|
||||
"label": "Quiet",
|
||||
"key": "quiet"
|
||||
"key": "quiet",
|
||||
"showInBar": true,
|
||||
"barIcon": "VisibilityOff",
|
||||
"barTitle": "Quiet variant",
|
||||
"barSeparator": false
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Disabled",
|
||||
"showInBar": true,
|
||||
"barIcon": "NoEdit",
|
||||
"barTitle": "Disable button",
|
||||
"key": "disabled"
|
||||
},
|
||||
{
|
||||
|
@ -590,6 +601,7 @@
|
|||
"icon": "TextParagraph",
|
||||
"illegalChildren": ["section"],
|
||||
"showSettingsBar": true,
|
||||
"editable": true,
|
||||
"settings": [
|
||||
{
|
||||
"type": "text",
|
||||
|
@ -696,6 +708,7 @@
|
|||
"description": "A component for displaying heading text",
|
||||
"illegalChildren": ["section"],
|
||||
"showSettingsBar": true,
|
||||
"editable": true,
|
||||
"settings": [
|
||||
{
|
||||
"type": "text",
|
||||
|
@ -940,6 +953,7 @@
|
|||
"description": "A basic link component for internal and external links",
|
||||
"icon": "Link",
|
||||
"showSettingsBar": true,
|
||||
"editable": true,
|
||||
"illegalChildren": ["section"],
|
||||
"settings": [
|
||||
{
|
||||
|
@ -1831,6 +1845,7 @@
|
|||
"icon": "Text",
|
||||
"illegalChildren": ["section"],
|
||||
"styles": ["size"],
|
||||
"editable": true,
|
||||
"settings": [
|
||||
{
|
||||
"type": "field/string",
|
||||
|
@ -1869,6 +1884,7 @@
|
|||
"name": "Number Field",
|
||||
"icon": "123",
|
||||
"styles": ["size"],
|
||||
"editable": true,
|
||||
"illegalChildren": ["section"],
|
||||
"settings": [
|
||||
{
|
||||
|
@ -1908,6 +1924,7 @@
|
|||
"name": "Password Field",
|
||||
"icon": "LockClosed",
|
||||
"styles": ["size"],
|
||||
"editable": true,
|
||||
"illegalChildren": ["section"],
|
||||
"settings": [
|
||||
{
|
||||
|
@ -1947,6 +1964,7 @@
|
|||
"name": "Options Picker",
|
||||
"icon": "ViewList",
|
||||
"styles": ["size"],
|
||||
"editable": true,
|
||||
"illegalChildren": ["section"],
|
||||
"settings": [
|
||||
{
|
||||
|
@ -2070,6 +2088,7 @@
|
|||
"name": "Multi-select Picker",
|
||||
"icon": "ViewList",
|
||||
"styles": ["size"],
|
||||
"editable": true,
|
||||
"illegalChildren": ["section"],
|
||||
"settings": [
|
||||
{
|
||||
|
@ -2171,6 +2190,7 @@
|
|||
"booleanfield": {
|
||||
"name": "Checkbox",
|
||||
"icon": "Checkmark",
|
||||
"editable": true,
|
||||
"illegalChildren": ["section"],
|
||||
"settings": [
|
||||
{
|
||||
|
@ -2234,6 +2254,7 @@
|
|||
"name": "Rich Text",
|
||||
"icon": "TextParagraph",
|
||||
"styles": ["size"],
|
||||
"editable": true,
|
||||
"illegalChildren": ["section"],
|
||||
"settings": [
|
||||
{
|
||||
|
@ -2274,6 +2295,7 @@
|
|||
"name": "Date Picker",
|
||||
"icon": "DateInput",
|
||||
"styles": ["size"],
|
||||
"editable": true,
|
||||
"illegalChildren": ["section"],
|
||||
"settings": [
|
||||
{
|
||||
|
@ -2319,6 +2341,7 @@
|
|||
"name": "Attachment",
|
||||
"icon": "Attach",
|
||||
"styles": ["size"],
|
||||
"editable": true,
|
||||
"illegalChildren": ["section"],
|
||||
"settings": [
|
||||
{
|
||||
|
@ -2348,6 +2371,7 @@
|
|||
"name": "Relationship Picker",
|
||||
"icon": "TaskList",
|
||||
"styles": ["size"],
|
||||
"editable": true,
|
||||
"illegalChildren": ["section"],
|
||||
"settings": [
|
||||
{
|
||||
|
@ -2933,5 +2957,203 @@
|
|||
"type": "schema",
|
||||
"suffix": "repeater"
|
||||
}
|
||||
},
|
||||
"repeaterblock": {
|
||||
"name": "Repeater block",
|
||||
"icon": "ViewList",
|
||||
"illegalChildren": ["section"],
|
||||
"hasChildren": true,
|
||||
"showSettingsBar": true,
|
||||
"settings": [
|
||||
{
|
||||
"type": "dataSource",
|
||||
"label": "Data",
|
||||
"key": "dataSource"
|
||||
},
|
||||
{
|
||||
"type": "filter",
|
||||
"label": "Filtering",
|
||||
"key": "filter"
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"label": "Sort Column",
|
||||
"key": "sortColumn"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Sort Order",
|
||||
"key": "sortOrder",
|
||||
"options": ["Ascending", "Descending"],
|
||||
"defaultValue": "Descending"
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"label": "Limit",
|
||||
"key": "limit",
|
||||
"defaultValue": 10
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Paginate",
|
||||
"key": "paginate"
|
||||
},
|
||||
{
|
||||
"section": true,
|
||||
"name": "Layout settings",
|
||||
"settings": [
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Empty Text",
|
||||
"key": "noRowsMessage",
|
||||
"defaultValue": "No rows found"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Direction",
|
||||
"key": "direction",
|
||||
"showInBar": true,
|
||||
"barStyle": "buttons",
|
||||
"options": [
|
||||
{
|
||||
"label": "Column",
|
||||
"value": "column",
|
||||
"barIcon": "ViewRow",
|
||||
"barTitle": "Column layout"
|
||||
},
|
||||
{
|
||||
"label": "Row",
|
||||
"value": "row",
|
||||
"barIcon": "ViewColumn",
|
||||
"barTitle": "Row layout"
|
||||
}
|
||||
],
|
||||
"defaultValue": "column"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Horiz. Align",
|
||||
"key": "hAlign",
|
||||
"showInBar": true,
|
||||
"barStyle": "buttons",
|
||||
"options": [
|
||||
{
|
||||
"label": "Left",
|
||||
"value": "left",
|
||||
"barIcon": "AlignLeft",
|
||||
"barTitle": "Align left"
|
||||
},
|
||||
{
|
||||
"label": "Center",
|
||||
"value": "center",
|
||||
"barIcon": "AlignCenter",
|
||||
"barTitle": "Align center"
|
||||
},
|
||||
{
|
||||
"label": "Right",
|
||||
"value": "right",
|
||||
"barIcon": "AlignRight",
|
||||
"barTitle": "Align right"
|
||||
},
|
||||
{
|
||||
"label": "Stretch",
|
||||
"value": "stretch",
|
||||
"barIcon": "MoveLeftRight",
|
||||
"barTitle": "Align stretched horizontally"
|
||||
}
|
||||
],
|
||||
"defaultValue": "stretch"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Vert. Align",
|
||||
"key": "vAlign",
|
||||
"showInBar": true,
|
||||
"barStyle": "buttons",
|
||||
"options": [
|
||||
{
|
||||
"label": "Top",
|
||||
"value": "top",
|
||||
"barIcon": "AlignTop",
|
||||
"barTitle": "Align top"
|
||||
},
|
||||
{
|
||||
"label": "Middle",
|
||||
"value": "middle",
|
||||
"barIcon": "AlignMiddle",
|
||||
"barTitle": "Align middle"
|
||||
},
|
||||
{
|
||||
"label": "Bottom",
|
||||
"value": "bottom",
|
||||
"barIcon": "AlignBottom",
|
||||
"barTitle": "Align bottom"
|
||||
},
|
||||
{
|
||||
"label": "Stretch",
|
||||
"value": "stretch",
|
||||
"barIcon": "MoveUpDown",
|
||||
"barTitle": "Align stretched vertically"
|
||||
}
|
||||
],
|
||||
"defaultValue": "top"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Gap",
|
||||
"key": "gap",
|
||||
"showInBar": true,
|
||||
"barStyle": "picker",
|
||||
"options": [
|
||||
{
|
||||
"label": "None",
|
||||
"value": "N"
|
||||
},
|
||||
{
|
||||
"label": "Small",
|
||||
"value": "S"
|
||||
},
|
||||
{
|
||||
"label": "Medium",
|
||||
"value": "M"
|
||||
},
|
||||
{
|
||||
"label": "Large",
|
||||
"value": "L"
|
||||
}
|
||||
],
|
||||
"defaultValue": "M"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"context": [
|
||||
{
|
||||
"type": "static",
|
||||
"suffix": "provider",
|
||||
"values": [
|
||||
{
|
||||
"label": "Rows",
|
||||
"key": "rows"
|
||||
},
|
||||
{
|
||||
"label": "Rows Length",
|
||||
"key": "rowsLength"
|
||||
},
|
||||
{
|
||||
"label": "Schema",
|
||||
"key": "schema"
|
||||
},
|
||||
{
|
||||
"label": "Page Number",
|
||||
"key": "pageNumber"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "schema",
|
||||
"suffix": "repeater"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/client",
|
||||
"version": "0.9.185-alpha.0",
|
||||
"version": "0.9.185-alpha.14",
|
||||
"license": "MPL-2.0",
|
||||
"module": "dist/budibase-client.js",
|
||||
"main": "dist/budibase-client.js",
|
||||
|
@ -19,9 +19,9 @@
|
|||
"dev:builder": "rollup -cw"
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "^0.9.185-alpha.0",
|
||||
"@budibase/bbui": "^0.9.185-alpha.14",
|
||||
"@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",
|
||||
"shortid": "^2.2.15",
|
||||
"svelte-spa-router": "^3.0.5"
|
||||
|
|
|
@ -55,6 +55,26 @@ export const fetchDatasourceSchema = async dataSource => {
|
|||
return dataSource.value?.schema
|
||||
}
|
||||
|
||||
// Field sources have their schema statically defined
|
||||
if (type === "field") {
|
||||
if (dataSource.fieldType === "attachment") {
|
||||
return {
|
||||
url: {
|
||||
type: "string",
|
||||
},
|
||||
name: {
|
||||
type: "string",
|
||||
},
|
||||
}
|
||||
} else if (dataSource.fieldType === "array") {
|
||||
return {
|
||||
value: {
|
||||
type: "string",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tables, views and links can be fetched by table ID
|
||||
if (
|
||||
(type === "table" || type === "view" || type === "link") &&
|
||||
|
|
|
@ -30,6 +30,6 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<Component {instance}>
|
||||
<Component {instance} isBlock>
|
||||
<slot />
|
||||
</Component>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { writable } from "svelte/store"
|
||||
import { writable, get } from "svelte/store"
|
||||
import { setContext, onMount } from "svelte"
|
||||
import { Layout, Heading, Body } from "@budibase/bbui"
|
||||
import Component from "./Component.svelte"
|
||||
|
@ -25,6 +25,7 @@
|
|||
import CustomThemeWrapper from "./CustomThemeWrapper.svelte"
|
||||
import DNDHandler from "components/preview/DNDHandler.svelte"
|
||||
import ErrorSVG from "builder/assets/error.svg"
|
||||
import KeyboardManager from "components/preview/KeyboardManager.svelte"
|
||||
|
||||
// Provide contexts
|
||||
setContext("sdk", SDK)
|
||||
|
@ -39,7 +40,7 @@
|
|||
await initialise()
|
||||
await authStore.actions.fetchUser()
|
||||
dataLoaded = true
|
||||
if ($builderStore.inBuilder) {
|
||||
if (get(builderStore).inBuilder) {
|
||||
builderStore.actions.notifyLoaded()
|
||||
} else {
|
||||
builderStore.actions.pingEndUser()
|
||||
|
@ -143,6 +144,7 @@
|
|||
</UserBindingsProvider>
|
||||
{/if}
|
||||
</div>
|
||||
<KeyboardManager />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
<script>
|
||||
import { getContext, setContext } from "svelte"
|
||||
import { writable, get } from "svelte/store"
|
||||
import { writable } from "svelte/store"
|
||||
import * as AppComponents from "components/app"
|
||||
import Router from "./Router.svelte"
|
||||
import { enrichProps, propsAreSame } from "utils/componentProps"
|
||||
|
@ -17,16 +17,24 @@
|
|||
export let instance = {}
|
||||
export let isLayout = 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
|
||||
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
|
||||
|
||||
// Settings are hashed when inside the builder preview and used as a key,
|
||||
// so that components fully remount whenever any settings change
|
||||
let hash = 0
|
||||
// Resultant cached settings which will be passed to the component instance.
|
||||
// These are a combination of the enriched, nested and conditional settings.
|
||||
let cachedSettings
|
||||
|
||||
// Latest timestamp that we started a props update.
|
||||
// Due to enrichment now being async, we need to avoid overwriting newer
|
||||
|
@ -44,7 +52,6 @@
|
|||
// Get contexts
|
||||
const context = getContext("context")
|
||||
const insideScreenslot = !!getContext("screenslot")
|
||||
const insideBlock = !!getContext("block")
|
||||
|
||||
// Create component context
|
||||
const componentStore = writable({})
|
||||
|
@ -63,14 +70,17 @@
|
|||
$: selected =
|
||||
$builderStore.inBuilder && $builderStore.selectedComponentId === id
|
||||
$: inSelectedPath = $builderStore.selectedComponentPath?.includes(id)
|
||||
$: inDragPath = inSelectedPath && $builderStore.editMode
|
||||
|
||||
// Interactive components can be selected, dragged and highlighted inside
|
||||
// the builder preview
|
||||
$: interactive =
|
||||
$builderStore.inBuilder &&
|
||||
($builderStore.previewType === "layout" || insideScreenslot) &&
|
||||
!insideBlock
|
||||
$: draggable = interactive && !isLayout && !isScreen
|
||||
!isBlock
|
||||
$: editable = definition?.editable
|
||||
$: editing = editable && selected && $builderStore.editMode
|
||||
$: draggable = !inDragPath && interactive && !isLayout && !isScreen
|
||||
$: droppable = interactive && !isLayout && !isScreen
|
||||
|
||||
// Empty components are those which accept children but do not have any.
|
||||
|
@ -79,44 +89,39 @@
|
|||
$: empty = interactive && !children.length && definition?.hasChildren
|
||||
$: 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)
|
||||
$: instanceKey = hashString(JSON.stringify(rawSettings))
|
||||
|
||||
// Component settings are those which are intended for this component and
|
||||
// which need to be enriched
|
||||
$: 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)
|
||||
// Update and enrich component settings
|
||||
$: updateSettings(rawSettings, instanceKey, settingsDefinition, $context)
|
||||
|
||||
// Evaluate conditional UI settings and store any component setting changes
|
||||
// which need to be made
|
||||
$: evaluateConditions(enrichedSettings?._conditions)
|
||||
|
||||
// Build up the final settings object to be passed to the component
|
||||
$: settings = {
|
||||
...enrichedSettings,
|
||||
...nestedSettings,
|
||||
...conditionalSettings,
|
||||
}
|
||||
|
||||
// Render key is used when in the builder preview to fully remount
|
||||
// components when settings are changed
|
||||
$: renderKey = `${hash}-${emptyState}`
|
||||
$: cacheSettings(enrichedSettings, nestedSettings, conditionalSettings)
|
||||
|
||||
// Update component context
|
||||
$: componentStore.set({
|
||||
id,
|
||||
children: children.length,
|
||||
styles: { ...instance._styles, id, empty: emptyState, interactive },
|
||||
styles: {
|
||||
...instance._styles,
|
||||
id,
|
||||
empty: emptyState,
|
||||
interactive,
|
||||
draggable,
|
||||
editable,
|
||||
},
|
||||
empty: emptyState,
|
||||
selected,
|
||||
name,
|
||||
editing,
|
||||
})
|
||||
|
||||
// Extracts all settings from the component instance
|
||||
const getRawSettings = instance => {
|
||||
let validSettings = {}
|
||||
Object.entries(instance)
|
||||
|
@ -137,12 +142,14 @@
|
|||
return AppComponents[name]
|
||||
}
|
||||
|
||||
// Gets this component's definition from the manifest
|
||||
const getComponentDefinition = component => {
|
||||
const prefix = "@budibase/standard-components/"
|
||||
const type = component?.replace(prefix, "")
|
||||
return type ? Manifest[type] : null
|
||||
}
|
||||
|
||||
// Gets the definition of this component's settings from the manifest
|
||||
const getSettingsDefinition = definition => {
|
||||
if (!definition) {
|
||||
return []
|
||||
|
@ -162,35 +169,50 @@
|
|||
return settings
|
||||
}
|
||||
|
||||
const getComponentSettings = (rawSettings, settingsDefinition) => {
|
||||
let clone = { ...rawSettings }
|
||||
settingsDefinition?.forEach(setting => {
|
||||
if (setting.nested) {
|
||||
delete clone[setting.key]
|
||||
}
|
||||
})
|
||||
return clone
|
||||
// Updates and enriches component settings when raw settings change
|
||||
const updateSettings = (settings, key, settingsDefinition, context) => {
|
||||
const instanceChanged = key !== lastInstanceKey
|
||||
|
||||
// Derive component and nested settings if the instance changed
|
||||
if (instanceChanged) {
|
||||
splitRawSettings(settings, settingsDefinition)
|
||||
}
|
||||
|
||||
// Enrich component settings
|
||||
enrichComponentSettings(componentSettings, context, instanceChanged)
|
||||
|
||||
// Update instance key
|
||||
if (instanceChanged) {
|
||||
lastInstanceKey = key
|
||||
}
|
||||
}
|
||||
|
||||
const getNestedSettings = (rawSettings, settingsDefinition) => {
|
||||
let clone = { ...rawSettings }
|
||||
// Splits the raw settings into those destined for the component itself
|
||||
// and nexted settings for child components inside blocks
|
||||
const splitRawSettings = (rawSettings, settingsDefinition) => {
|
||||
let newComponentSettings = { ...rawSettings }
|
||||
let newNestedSettings = { ...rawSettings }
|
||||
settingsDefinition?.forEach(setting => {
|
||||
if (!setting.nested) {
|
||||
delete clone[setting.key]
|
||||
if (setting.nested) {
|
||||
delete newComponentSettings[setting.key]
|
||||
} else {
|
||||
delete newNestedSettings[setting.key]
|
||||
}
|
||||
})
|
||||
return clone
|
||||
componentSettings = newComponentSettings
|
||||
nestedSettings = newNestedSettings
|
||||
}
|
||||
|
||||
// Enriches any string component props using handlebars
|
||||
const enrichComponentSettings = (rawSettings, instanceKey, context) => {
|
||||
const instanceSame = instanceKey === lastInstanceKey
|
||||
const contextSame = context.key === lastContextKey
|
||||
const enrichComponentSettings = (rawSettings, context, instanceChanged) => {
|
||||
const contextChanged = context.key !== lastContextKey
|
||||
|
||||
if (instanceSame && contextSame) {
|
||||
return
|
||||
// Skip enrichment if the context and instance are unchanged
|
||||
if (!contextChanged) {
|
||||
if (!instanceChanged) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
lastInstanceKey = instanceKey
|
||||
lastContextKey = context.key
|
||||
}
|
||||
|
||||
|
@ -206,31 +228,11 @@
|
|||
return
|
||||
}
|
||||
|
||||
// Update the component props.
|
||||
// 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))
|
||||
}
|
||||
enrichedSettings = newEnrichedSettings
|
||||
}
|
||||
|
||||
// Evaluates the list of conditional UI conditions and determines any setting
|
||||
// or visibility changes required
|
||||
const evaluateConditions = conditions => {
|
||||
if (!conditions?.length) {
|
||||
return
|
||||
|
@ -250,35 +252,51 @@
|
|||
conditionalSettings = result.settingUpdates
|
||||
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>
|
||||
|
||||
{#key renderKey}
|
||||
{#if constructor && settings && (visible || inSelectedPath)}
|
||||
<!-- The ID is used as a class because getElementsByClassName is O(1) -->
|
||||
<!-- and the performance matters for the selection indicators -->
|
||||
<div
|
||||
class={`component ${id}`}
|
||||
class:draggable
|
||||
class:droppable
|
||||
class:empty
|
||||
class:interactive
|
||||
data-id={id}
|
||||
data-name={name}
|
||||
>
|
||||
<svelte:component this={constructor} {...settings}>
|
||||
{#if children.length}
|
||||
{#each children as child (child._id)}
|
||||
<svelte:self instance={child} />
|
||||
{/each}
|
||||
{:else if emptyState}
|
||||
<Placeholder />
|
||||
{:else if insideBlock}
|
||||
<slot />
|
||||
{/if}
|
||||
</svelte:component>
|
||||
</div>
|
||||
{/if}
|
||||
{/key}
|
||||
{#if constructor && cachedSettings && (visible || inSelectedPath)}
|
||||
<!-- The ID is used as a class because getElementsByClassName is O(1) -->
|
||||
<!-- and the performance matters for the selection indicators -->
|
||||
<div
|
||||
class={`component ${id}`}
|
||||
class:draggable
|
||||
class:droppable
|
||||
class:empty
|
||||
class:interactive
|
||||
class:editing
|
||||
class:block={isBlock}
|
||||
data-id={id}
|
||||
data-name={name}
|
||||
>
|
||||
<svelte:component this={constructor} {...cachedSettings}>
|
||||
{#if children.length}
|
||||
{#each children as child (child._id)}
|
||||
<svelte:self instance={child} />
|
||||
{/each}
|
||||
{:else if emptyState}
|
||||
<Placeholder />
|
||||
{:else if isBlock}
|
||||
<slot />
|
||||
{/if}
|
||||
</svelte:component>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.component {
|
||||
|
@ -290,4 +308,7 @@
|
|||
.draggable :global(*:hover) {
|
||||
cursor: grab;
|
||||
}
|
||||
.editing :global(*:hover) {
|
||||
cursor: auto;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -79,4 +79,9 @@
|
|||
scrollbar-color: var(--spectrum-global-color-gray-400)
|
||||
var(--spectrum-alias-background-color-default);
|
||||
}
|
||||
|
||||
/* Remove border when editing contenteditable components */
|
||||
:global(*[contenteditable="true"]:focus) {
|
||||
outline: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { getContext } from "svelte"
|
||||
import "@spectrum-css/button/dist/index-vars.css"
|
||||
|
||||
const { styleable } = getContext("sdk")
|
||||
const { styleable, builderStore } = getContext("sdk")
|
||||
const component = getContext("component")
|
||||
|
||||
export let disabled = false
|
||||
|
@ -11,16 +11,35 @@
|
|||
export let size = "M"
|
||||
export let type = "primary"
|
||||
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>
|
||||
|
||||
<button
|
||||
class={`spectrum-Button spectrum-Button--size${size} spectrum-Button--${type}`}
|
||||
class:spectrum-Button--quiet={quiet}
|
||||
disabled={disabled || false}
|
||||
{disabled}
|
||||
use:styleable={$component.styles}
|
||||
on:click={onClick}
|
||||
contenteditable={$component.editing}
|
||||
on:blur={$component.editing ? updateText : null}
|
||||
bind:this={node}
|
||||
>
|
||||
{text || ""}
|
||||
{componentText}
|
||||
</button>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -34,12 +34,18 @@
|
|||
let bookmarks = [null]
|
||||
let pageNumber = 0
|
||||
let query = null
|
||||
let queryExtensions = {}
|
||||
|
||||
// Sorting can be overridden at run time, so we can't use the prop directly
|
||||
let currentSortColumn = sortColumn
|
||||
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"
|
||||
$: nestedProvider = dataSource?.type === "provider"
|
||||
$: hasNextPage = bookmarks[pageNumber + 1] != null
|
||||
|
@ -91,8 +97,12 @@
|
|||
metadata: { dataSource },
|
||||
},
|
||||
{
|
||||
type: ActionTypes.SetDataProviderQuery,
|
||||
callback: newQuery => (query = newQuery),
|
||||
type: ActionTypes.AddDataProviderQueryExtension,
|
||||
callback: addQueryExtension,
|
||||
},
|
||||
{
|
||||
type: ActionTypes.RemoveDataProviderQueryExtension,
|
||||
callback: removeQueryExtension,
|
||||
},
|
||||
{
|
||||
type: ActionTypes.SetDataProviderSorting,
|
||||
|
@ -183,7 +193,16 @@
|
|||
} else if (dataSource?.type === "provider") {
|
||||
// For providers referencing another provider, just use the rows it
|
||||
// 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 {
|
||||
// For other data sources like queries or views, fetch all rows from the
|
||||
// server
|
||||
|
@ -255,6 +274,38 @@
|
|||
pageNumber--
|
||||
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>
|
||||
|
||||
<div use:styleable={$component.styles} class="container">
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import { getContext } from "svelte"
|
||||
import dayjs from "dayjs"
|
||||
import utc from "dayjs/plugin/utc"
|
||||
import { onMount } from "svelte"
|
||||
import { onDestroy } from "svelte"
|
||||
|
||||
dayjs.extend(utc)
|
||||
|
||||
|
@ -14,7 +14,14 @@
|
|||
const component = getContext("component")
|
||||
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 = [
|
||||
"Last 1 day",
|
||||
"Last 7 days",
|
||||
|
@ -25,44 +32,30 @@
|
|||
]
|
||||
let value = options.includes(defaultValue) ? defaultValue : "Last 30 days"
|
||||
|
||||
const updateDateRange = option => {
|
||||
const query = dataProvider?.state?.query
|
||||
if (!query || !setQuery) {
|
||||
return
|
||||
}
|
||||
$: queryExtension = getQueryExtension(value)
|
||||
$: addExtension?.($component.id, "range", field, queryExtension)
|
||||
|
||||
value = option
|
||||
const getQueryExtension = value => {
|
||||
let low = dayjs.utc().subtract(1, "year")
|
||||
let high = dayjs.utc().add(1, "day")
|
||||
|
||||
if (option === "Last 1 day") {
|
||||
if (value === "Last 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")
|
||||
} else if (option === "Last 30 days") {
|
||||
} else if (value === "Last 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")
|
||||
} else if (option === "Last 6 months") {
|
||||
} else if (value === "Last 6 months") {
|
||||
low = dayjs.utc().subtract(6, "months")
|
||||
}
|
||||
|
||||
// Update data provider query with the new filter
|
||||
setQuery({
|
||||
...query,
|
||||
range: {
|
||||
...query.range,
|
||||
[field]: {
|
||||
high: high.format(),
|
||||
low: low.format(),
|
||||
},
|
||||
},
|
||||
})
|
||||
return { low: low.format(), high: high.format() }
|
||||
}
|
||||
|
||||
// Update the range on mount to the initial value
|
||||
onMount(() => {
|
||||
updateDateRange(value)
|
||||
onDestroy(() => {
|
||||
removeExtension?.($component.id)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -71,6 +64,6 @@
|
|||
placeholder={null}
|
||||
{options}
|
||||
{value}
|
||||
on:change={e => updateDateRange(e.detail)}
|
||||
on:change={e => (value = e.detail)}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -13,10 +13,11 @@
|
|||
export let underline
|
||||
export let size
|
||||
|
||||
$: placeholder = $builderStore.inBuilder && !text
|
||||
$: componentText = $builderStore.inBuilder
|
||||
? text || $component.name || "Placeholder text"
|
||||
: text || ""
|
||||
let node
|
||||
|
||||
$: $component.editing && node?.focus()
|
||||
$: placeholder = $builderStore.inBuilder && !text && !$component.editing
|
||||
$: componentText = getComponentText(text, $builderStore, $component)
|
||||
$: sizeClass = `spectrum-Heading--size${size || "M"}`
|
||||
$: alignClass = `align--${align || "left"}`
|
||||
|
||||
|
@ -24,6 +25,13 @@
|
|||
// overrides the color when it's passed as inline style.
|
||||
$: 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) => {
|
||||
if (!color) {
|
||||
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>
|
||||
|
||||
<h1
|
||||
bind:this={node}
|
||||
contenteditable={$component.editing}
|
||||
use:styleable={styles}
|
||||
class:placeholder
|
||||
class:bold
|
||||
class:italic
|
||||
class:underline
|
||||
class="spectrum-Heading {sizeClass} {alignClass}"
|
||||
on:blur={$component.editing ? updateText : null}
|
||||
>
|
||||
{componentText}
|
||||
</h1>
|
||||
|
|
|
@ -14,19 +14,25 @@
|
|||
export let underline
|
||||
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"
|
||||
$: placeholder = $builderStore.inBuilder && !text
|
||||
$: componentText = $builderStore.inBuilder
|
||||
? text || "Placeholder link"
|
||||
: text || ""
|
||||
$: componentText = getComponentText(text, $builderStore, $component)
|
||||
|
||||
// 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
|
||||
// overrides the color when it's passed as inline style.
|
||||
$: 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) => {
|
||||
if (!color) {
|
||||
return styles
|
||||
|
@ -39,10 +45,27 @@
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
const updateText = e => {
|
||||
builderStore.actions.updateProp("text", e.target.textContent)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $builderStore.inBuilder || componentText}
|
||||
{#if external}
|
||||
{#if $component.editing}
|
||||
<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
|
||||
{target}
|
||||
href={url || "/"}
|
||||
|
@ -72,12 +95,12 @@
|
|||
{/if}
|
||||
|
||||
<style>
|
||||
a {
|
||||
a,
|
||||
div {
|
||||
color: var(--spectrum-alias-text-color);
|
||||
white-space: nowrap;
|
||||
transition: color 130ms ease-in-out;
|
||||
}
|
||||
a:hover {
|
||||
a:not(.placeholder):hover {
|
||||
color: var(--spectrum-link-primary-m-text-color-hover) !important;
|
||||
}
|
||||
.placeholder {
|
||||
|
|
|
@ -12,10 +12,11 @@
|
|||
export let underline
|
||||
export let size
|
||||
|
||||
$: placeholder = $builderStore.inBuilder && !text
|
||||
$: componentText = $builderStore.inBuilder
|
||||
? text || $component.name || "Placeholder text"
|
||||
: text || ""
|
||||
let node
|
||||
|
||||
$: $component.editing && node?.focus()
|
||||
$: placeholder = $builderStore.inBuilder && !text && !$component.editing
|
||||
$: componentText = getComponentText(text, $builderStore, $component)
|
||||
$: sizeClass = `spectrum-Body--size${size || "M"}`
|
||||
$: alignClass = `align--${align || "left"}`
|
||||
|
||||
|
@ -23,6 +24,13 @@
|
|||
// overrides the color when it's passed as inline style.
|
||||
$: 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) => {
|
||||
if (!color) {
|
||||
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>
|
||||
|
||||
<p
|
||||
bind:this={node}
|
||||
contenteditable={$component.editing}
|
||||
use:styleable={styles}
|
||||
class:placeholder
|
||||
class:bold
|
||||
class:italic
|
||||
class:underline
|
||||
class="spectrum-Body {sizeClass} {alignClass}"
|
||||
on:blur={$component.editing ? updateText : null}
|
||||
>
|
||||
{componentText}
|
||||
</p>
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<script>
|
||||
import { onMount, getContext } from "svelte"
|
||||
import { getContext } from "svelte"
|
||||
import Block from "components/Block.svelte"
|
||||
import BlockComponent from "components/BlockComponent.svelte"
|
||||
import { Heading } from "@budibase/bbui"
|
||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||
|
||||
export let title
|
||||
export let dataSource
|
||||
|
@ -45,6 +46,7 @@
|
|||
let repeaterId
|
||||
let schema
|
||||
|
||||
$: fetchSchema(dataSource)
|
||||
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema)
|
||||
$: enrichedFilter = enrichFilter(filter, enrichedSearchColumns, formId)
|
||||
$: cardWidth = cardHorizontal ? 420 : 300
|
||||
|
@ -103,15 +105,15 @@
|
|||
}
|
||||
const col = linkColumn || "_id"
|
||||
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
|
||||
onMount(async () => {
|
||||
// Load the datasource schema so we can determine column types
|
||||
const fetchSchema = async dataSource => {
|
||||
if (dataSource) {
|
||||
schema = await API.fetchDatasourceSchema(dataSource)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<Block>
|
||||
|
@ -171,7 +173,7 @@
|
|||
bind:id={repeaterId}
|
||||
context="repeater"
|
||||
props={{
|
||||
dataProvider: `{{ literal [${dataProviderId}] }}`,
|
||||
dataProvider: `{{ literal ${safe(dataProviderId)} }}`,
|
||||
direction: "row",
|
||||
hAlign: "stretch",
|
||||
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>
|
||||
import { onMount, getContext } from "svelte"
|
||||
import { getContext } from "svelte"
|
||||
import Block from "components/Block.svelte"
|
||||
import BlockComponent from "components/BlockComponent.svelte"
|
||||
import { Heading } from "@budibase/bbui"
|
||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||
|
||||
export let title
|
||||
export let dataSource
|
||||
|
@ -40,6 +41,7 @@
|
|||
let dataProviderId
|
||||
let schema
|
||||
|
||||
$: fetchSchema(dataSource)
|
||||
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema)
|
||||
$: enrichedFilter = enrichFilter(filter, enrichedSearchColumns, formId)
|
||||
$: titleButtonAction = [
|
||||
|
@ -61,7 +63,7 @@
|
|||
operator: column.type === "string" ? "string" : "equal",
|
||||
type: "string",
|
||||
valueType: "Binding",
|
||||
value: `{{ [${formId}].[${column.name}] }}`,
|
||||
value: `{{ ${safe(formId)}.${safe(column.name)} }}`,
|
||||
})
|
||||
})
|
||||
return enrichedFilter
|
||||
|
@ -84,12 +86,12 @@
|
|||
return enrichedColumns.slice(0, 3)
|
||||
}
|
||||
|
||||
// Load the datasource schema on mount so we can determine column types
|
||||
onMount(async () => {
|
||||
// Load the datasource schema so we can determine column types
|
||||
const fetchSchema = async dataSource => {
|
||||
if (dataSource) {
|
||||
schema = await API.fetchDatasourceSchema(dataSource)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<Block>
|
||||
|
@ -147,7 +149,7 @@
|
|||
<BlockComponent
|
||||
type="table"
|
||||
props={{
|
||||
dataProvider: `{{ literal [${dataProviderId}] }}`,
|
||||
dataProvider: `{{ literal ${safe(dataProviderId)} }}`,
|
||||
columns: tableColumns,
|
||||
showAutoColumns,
|
||||
rowCount,
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export { default as tableblock } from "./TableBlock.svelte"
|
||||
export { default as cardsblock } from "./CardsBlock.svelte"
|
||||
export { default as repeaterblock } from "./RepeaterBlock.svelte"
|
||||
|
|
|
@ -17,54 +17,53 @@
|
|||
const formContext = getContext("form")
|
||||
const formStepContext = getContext("form-step")
|
||||
const fieldGroupContext = getContext("field-group")
|
||||
const { styleable } = getContext("sdk")
|
||||
const { styleable, builderStore } = getContext("sdk")
|
||||
const component = getContext("component")
|
||||
|
||||
// Register field with form
|
||||
const formApi = formContext?.formApi
|
||||
const labelPos = fieldGroupContext?.labelPosition || "above"
|
||||
const formField = formApi?.registerField(
|
||||
$: formStep = formStepContext ? $formStepContext || 1 : 1
|
||||
$: formField = formApi?.registerField(
|
||||
field,
|
||||
type,
|
||||
defaultValue,
|
||||
disabled,
|
||||
validation,
|
||||
formStepContext || 1
|
||||
formStep
|
||||
)
|
||||
|
||||
// Focus label when editing
|
||||
let labelNode
|
||||
$: $component.editing && labelNode?.focus()
|
||||
|
||||
// Update form properties in parent component on every store change
|
||||
const unsubscribe = formField?.subscribe(value => {
|
||||
$: unsubscribe = formField?.subscribe(value => {
|
||||
fieldState = value?.fieldState
|
||||
fieldApi = value?.fieldApi
|
||||
fieldSchema = value?.fieldSchema
|
||||
})
|
||||
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
|
||||
$: labelClass = labelPos === "above" ? "" : `spectrum-FieldLabel--${labelPos}`
|
||||
|
||||
const updateValidation = validation => {
|
||||
fieldApi?.updateValidation(validation)
|
||||
}
|
||||
|
||||
const updateDisabled = disabled => {
|
||||
fieldApi?.setDisabled(disabled)
|
||||
const updateLabel = e => {
|
||||
builderStore.actions.updateProp("label", e.target.textContent)
|
||||
}
|
||||
</script>
|
||||
|
||||
<FieldGroupFallback>
|
||||
<div class="spectrum-Form-item" use:styleable={$component.styles}>
|
||||
<label
|
||||
bind:this={labelNode}
|
||||
contenteditable={$component.editing}
|
||||
on:blur={$component.editing ? updateLabel : null}
|
||||
class:hidden={!label}
|
||||
for={fieldState?.fieldId}
|
||||
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${labelClass}`}
|
||||
>
|
||||
{label || ""}
|
||||
{label || " "}
|
||||
</label>
|
||||
<div class="spectrum-Form-itemField">
|
||||
{#if !formContext}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { getContext, onMount } from "svelte"
|
||||
import { getContext } from "svelte"
|
||||
import InnerForm from "./InnerForm.svelte"
|
||||
import { hashString } from "utils/helpers"
|
||||
|
||||
export let dataSource
|
||||
export let theme
|
||||
|
@ -15,6 +16,8 @@
|
|||
let schema
|
||||
let table
|
||||
|
||||
$: fetchSchema(dataSource)
|
||||
|
||||
// Returns the closes data context which isn't a built in context
|
||||
const getInitialValues = (type, dataSource, context) => {
|
||||
// Only inherit values for update forms
|
||||
|
@ -35,7 +38,7 @@
|
|||
}
|
||||
|
||||
// Fetches the form schema from this form's dataSource
|
||||
const fetchSchema = async () => {
|
||||
const fetchSchema = async dataSource => {
|
||||
if (!dataSource) {
|
||||
schema = {}
|
||||
}
|
||||
|
@ -62,14 +65,15 @@
|
|||
schema = dataSourceSchema || {}
|
||||
}
|
||||
|
||||
loaded = true
|
||||
if (!loaded) {
|
||||
loaded = true
|
||||
}
|
||||
}
|
||||
|
||||
$: initialValues = getInitialValues(actionType, dataSource, $context)
|
||||
$: resetKey = JSON.stringify(initialValues)
|
||||
|
||||
// Load the form schema on mount
|
||||
onMount(fetchSchema)
|
||||
$: resetKey = hashString(
|
||||
JSON.stringify(initialValues) + JSON.stringify(schema)
|
||||
)
|
||||
</script>
|
||||
|
||||
{#if loaded}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import { getContext, setContext } from "svelte"
|
||||
import { writable } from "svelte/store"
|
||||
import Placeholder from "../Placeholder.svelte"
|
||||
|
||||
export let step = 1
|
||||
|
@ -9,7 +10,9 @@
|
|||
const formContext = getContext("form")
|
||||
|
||||
// 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
|
||||
$: currentStep = $formState?.currentStep
|
||||
|
|
|
@ -96,15 +96,14 @@
|
|||
return
|
||||
}
|
||||
|
||||
// If we've already registered this field then wipe any errors and
|
||||
// return the existing field
|
||||
// If we've already registered this field then keep some existing state
|
||||
let initialValue = initialValues[field] ?? defaultValue
|
||||
let fieldId = `id-${generateID()}`
|
||||
const existingField = getField(field)
|
||||
if (existingField) {
|
||||
existingField.update(state => {
|
||||
state.fieldState.error = null
|
||||
return state
|
||||
})
|
||||
return existingField
|
||||
const { fieldState } = get(existingField)
|
||||
initialValue = fieldState.value ?? initialValue
|
||||
fieldId = fieldState.fieldId
|
||||
}
|
||||
|
||||
// Auto columns are always disabled
|
||||
|
@ -125,8 +124,8 @@
|
|||
type,
|
||||
step: step || 1,
|
||||
fieldState: {
|
||||
fieldId: `id-${generateID()}`,
|
||||
value: initialValues[field] ?? defaultValue,
|
||||
fieldId,
|
||||
value: initialValue,
|
||||
error: null,
|
||||
disabled: disabled || fieldDisabled || isAutoColumn,
|
||||
defaultValue,
|
||||
|
@ -137,7 +136,12 @@
|
|||
})
|
||||
|
||||
// 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
|
||||
},
|
||||
|
@ -287,7 +291,7 @@
|
|||
|
||||
// Provide form step context so that forms without any step components
|
||||
// register their fields to step 1
|
||||
setContext("form-step", 1)
|
||||
setContext("form-step", writable(1))
|
||||
|
||||
// Action context to pass to children
|
||||
const actions = [
|
||||
|
|
|
@ -17,10 +17,6 @@
|
|||
|
||||
const component = getContext("component")
|
||||
const { styleable, getAction, ActionTypes, routeStore } = getContext("sdk")
|
||||
const setSorting = getAction(
|
||||
dataProvider?.id,
|
||||
ActionTypes.SetDataProviderSorting
|
||||
)
|
||||
const customColumnKey = `custom-${Math.random()}`
|
||||
const customRenderers = [
|
||||
{
|
||||
|
@ -29,13 +25,16 @@
|
|||
},
|
||||
]
|
||||
|
||||
// Table state
|
||||
$: hasChildren = $component.children
|
||||
$: loading = dataProvider?.loading ?? false
|
||||
$: data = dataProvider?.rows || []
|
||||
$: fullSchema = dataProvider?.schema ?? {}
|
||||
$: fields = getFields(fullSchema, columns, showAutoColumns)
|
||||
$: schema = getFilteredSchema(fullSchema, fields, hasChildren)
|
||||
$: setSorting = getAction(
|
||||
dataProvider?.id,
|
||||
ActionTypes.SetDataProviderSorting
|
||||
)
|
||||
|
||||
const getFields = (schema, customColumns, showAutoColumns) => {
|
||||
// Check for an invalid column selection
|
||||
|
|
|
@ -147,7 +147,7 @@
|
|||
return
|
||||
}
|
||||
|
||||
const element = e.target.closest(".component")
|
||||
const element = e.target.closest(".component:not(.block)")
|
||||
if (
|
||||
element &&
|
||||
element.classList.contains("droppable") &&
|
||||
|
|
|
@ -17,10 +17,9 @@
|
|||
|
||||
<div
|
||||
in:fade={{
|
||||
delay: transition ? 50 : 0,
|
||||
delay: transition ? 130 : 0,
|
||||
duration: transition ? 130 : 0,
|
||||
}}
|
||||
out:fade={{ duration: transition ? 130 : 0 }}
|
||||
class="indicator"
|
||||
class:flipped
|
||||
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>
|
||||
import { builderStore } from "stores"
|
||||
import IndicatorSet from "./IndicatorSet.svelte"
|
||||
|
||||
$: color = $builderStore.editMode
|
||||
? "var(--spectrum-global-color-static-green-500)"
|
||||
: "var(--spectrum-global-color-static-blue-600)"
|
||||
</script>
|
||||
|
||||
<IndicatorSet
|
||||
componentId={$builderStore.selectedComponentId}
|
||||
color="var(--spectrum-global-color-static-blue-600)"
|
||||
{color}
|
||||
zIndex="910"
|
||||
transition
|
||||
/>
|
||||
|
|
|
@ -17,7 +17,19 @@
|
|||
|
||||
$: definition = $builderStore.selectedComponentDefinition
|
||||
$: 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 = () => {
|
||||
if (!showBar) {
|
||||
|
@ -110,7 +122,7 @@
|
|||
prop={setting.key}
|
||||
value={option.value}
|
||||
icon={option.barIcon}
|
||||
title={option.barTitle}
|
||||
title={option.barTitle || option.label}
|
||||
/>
|
||||
{/each}
|
||||
{:else}
|
||||
|
@ -124,7 +136,7 @@
|
|||
<SettingsButton
|
||||
prop={setting.key}
|
||||
icon={setting.barIcon}
|
||||
title={setting.barTitle}
|
||||
title={setting.barTitle || setting.label}
|
||||
bool
|
||||
/>
|
||||
{:else if setting.type === "color"}
|
||||
|
|
|
@ -25,7 +25,8 @@ export const UnsortableTypes = [
|
|||
export const ActionTypes = {
|
||||
ValidateForm: "ValidateForm",
|
||||
RefreshDatasource: "RefreshDatasource",
|
||||
SetDataProviderQuery: "SetDataProviderQuery",
|
||||
AddDataProviderQueryExtension: "AddDataProviderQueryExtension",
|
||||
RemoveDataProviderQueryExtension: "RemoveDataProviderQueryExtension",
|
||||
SetDataProviderSorting: "SetDataProviderSorting",
|
||||
ClearForm: "ClearForm",
|
||||
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 { findComponentById, findComponentPathById } from "../utils/components"
|
||||
import { pingEndUser } from "../api"
|
||||
|
@ -14,6 +14,7 @@ const createBuilderStore = () => {
|
|||
layout: null,
|
||||
screen: null,
|
||||
selectedComponentId: null,
|
||||
editMode: false,
|
||||
previewId: null,
|
||||
previewType: null,
|
||||
selectedPath: [],
|
||||
|
@ -50,6 +51,10 @@ const createBuilderStore = () => {
|
|||
|
||||
const actions = {
|
||||
selectComponent: id => {
|
||||
if (id === get(writableStore).selectedComponentId) {
|
||||
return
|
||||
}
|
||||
writableStore.update(state => ({ ...state, editMode: false }))
|
||||
dispatchEvent("select-component", { id })
|
||||
},
|
||||
updateProp: (prop, value) => {
|
||||
|
@ -65,10 +70,7 @@ const createBuilderStore = () => {
|
|||
pingEndUser()
|
||||
},
|
||||
setSelectedPath: path => {
|
||||
writableStore.update(state => {
|
||||
state.selectedPath = path
|
||||
return state
|
||||
})
|
||||
writableStore.update(state => ({ ...state, selectedPath: path }))
|
||||
},
|
||||
moveComponent: (componentId, destinationComponentId, mode) => {
|
||||
dispatchEvent("move-component", {
|
||||
|
@ -78,10 +80,16 @@ const createBuilderStore = () => {
|
|||
})
|
||||
},
|
||||
setDragging: dragging => {
|
||||
writableStore.update(state => {
|
||||
state.isDragging = dragging
|
||||
return state
|
||||
})
|
||||
if (dragging === get(writableStore).isDragging) {
|
||||
return
|
||||
}
|
||||
writableStore.update(state => ({ ...state, isDragging: dragging }))
|
||||
},
|
||||
setEditMode: enabled => {
|
||||
if (enabled === get(writableStore).editMode) {
|
||||
return
|
||||
}
|
||||
writableStore.update(state => ({ ...state, editMode: enabled }))
|
||||
},
|
||||
}
|
||||
return {
|
||||
|
|
|
@ -21,12 +21,7 @@ export const styleable = (node, styles = {}) => {
|
|||
let applyNormalStyles
|
||||
let applyHoverStyles
|
||||
let selectComponent
|
||||
|
||||
// Allow dragging if required
|
||||
const parent = node.closest(".component")
|
||||
if (parent && parent.classList.contains("draggable")) {
|
||||
node.setAttribute("draggable", true)
|
||||
}
|
||||
let editComponent
|
||||
|
||||
// Creates event listeners and applies initial styles
|
||||
const setupStyles = (newStyles = {}) => {
|
||||
|
@ -45,6 +40,9 @@ export const styleable = (node, styles = {}) => {
|
|||
...(newStyles.hover || {}),
|
||||
}
|
||||
|
||||
// Allow dragging if required
|
||||
node.setAttribute("draggable", !!newStyles.draggable)
|
||||
|
||||
// Applies a style string to a DOM node
|
||||
const applyStyles = styleString => {
|
||||
node.style = styleString
|
||||
|
@ -69,6 +67,17 @@ export const styleable = (node, styles = {}) => {
|
|||
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
|
||||
node.addEventListener("mouseover", applyHoverStyles)
|
||||
node.addEventListener("mouseout", applyNormalStyles)
|
||||
|
@ -76,6 +85,7 @@ export const styleable = (node, styles = {}) => {
|
|||
// Add builder preview click listener
|
||||
if (newStyles.interactive) {
|
||||
node.addEventListener("click", selectComponent, false)
|
||||
node.addEventListener("dblclick", editComponent, false)
|
||||
}
|
||||
|
||||
// Apply initial normal styles
|
||||
|
@ -87,6 +97,7 @@ export const styleable = (node, styles = {}) => {
|
|||
node.removeEventListener("mouseover", applyHoverStyles)
|
||||
node.removeEventListener("mouseout", applyNormalStyles)
|
||||
node.removeEventListener("click", selectComponent)
|
||||
node.removeEventListener("dblclick", editComponent)
|
||||
}
|
||||
|
||||
// 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-update="scripts/watchtower-hooks/pre-update.sh"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/server",
|
||||
"email": "hi@budibase.com",
|
||||
"version": "0.9.185-alpha.0",
|
||||
"version": "0.9.185-alpha.14",
|
||||
"description": "Budibase Web Server",
|
||||
"main": "src/index.js",
|
||||
"repository": {
|
||||
|
@ -68,9 +68,11 @@
|
|||
"author": "Budibase",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@budibase/auth": "^0.9.185-alpha.0",
|
||||
"@budibase/client": "^0.9.185-alpha.0",
|
||||
"@budibase/string-templates": "^0.9.185-alpha.0",
|
||||
"@budibase/auth": "^0.9.185-alpha.14",
|
||||
"@budibase/client": "^0.9.185-alpha.14",
|
||||
"@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",
|
||||
"@koa/router": "8.0.0",
|
||||
"@sendgrid/mail": "7.1.1",
|
||||
|
@ -80,7 +82,6 @@
|
|||
"aws-sdk": "^2.767.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"bull": "^3.22.4",
|
||||
"bull-board": "^2.0.1",
|
||||
"chmodr": "1.2.0",
|
||||
"csvtojson": "2.0.10",
|
||||
"dotenv": "8.2.0",
|
||||
|
@ -120,6 +121,7 @@
|
|||
"uuid": "3.3.2",
|
||||
"validate.js": "0.13.1",
|
||||
"vm2": "^3.9.3",
|
||||
"worker-farm": "^1.7.0",
|
||||
"yargs": "13.2.4",
|
||||
"zlib": "1.0.5"
|
||||
},
|
||||
|
@ -138,7 +140,6 @@
|
|||
"copyfiles": "^2.4.1",
|
||||
"docker-compose": "^0.23.6",
|
||||
"eslint": "^6.8.0",
|
||||
"express": "^4.17.1",
|
||||
"jest": "^27.0.5",
|
||||
"nodemon": "^2.0.4",
|
||||
"prettier": "^2.3.1",
|
||||
|
|
|
@ -50,6 +50,7 @@ async function init() {
|
|||
SELF_HOSTED: 1,
|
||||
DISABLE_ACCOUNT_PORTAL: "",
|
||||
MULTI_TENANCY: "",
|
||||
DISABLE_THREADING: 1,
|
||||
}
|
||||
let envFile = ""
|
||||
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