Merge remote-tracking branch 'origin/develop' into pc-bug-fixes

This commit is contained in:
Peter Clement 2021-08-17 14:11:47 +01:00
commit fc0d621124
67 changed files with 1797 additions and 2305 deletions

235
i18n/README.de.md Normal file
View File

@ -0,0 +1,235 @@
<p align="center">
<a href="https://www.budibase.com">
<img alt="Budibase" src="https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg" width="60" />
</a>
</p>
<h1 align="center">
Budibase
</h1>
<h3 align="center">
Entwickle, automatisiere und stelle interne Tools in Minuten bereit.
</h3>
<p align="center">
Budibase ist eine quelloffene Low-Code Plattform, die es Entwicklern und IT-Profis ermöglicht interne Tools auf eigener Infrastruktur zu entwickeln, zu automatisieren und bereitzustellen.
</p>
<h3 align="center">
🤖 🎨 🚀
</h3>
<p align="center">
<img alt="Budibase design ui" src="https://i.imgur.com/5BnXPsN.png">
</p>
<p align="center">
<a href="https://github.com/Budibase/budibase/releases">
<img alt="GitHub all releases" src="https://img.shields.io/github/downloads/Budibase/budibase/total">
</a>
<a href="https://github.com/Budibase/budibase/releases">
<img alt="GitHub release (latest by date)" src="https://img.shields.io/github/v/release/Budibase/budibase">
</a>
<a href="https://twitter.com/intent/follow?screen_name=budibase">
<img src="https://img.shields.io/twitter/follow/budibase?style=social" alt="Follow @budibase" />
</a>
<img src="https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg" alt="Code of conduct" />
<a href="https://codecov.io/gh/Budibase/budibase">
<img src="https://codecov.io/gh/Budibase/budibase/graph/badge.svg?token=E8W2ZFXQOH"/>
</a>
</p>
<h3 align="center">
<a href="https://docs.budibase.com/getting-started">Los Geht's</a>
<span> · </span>
<a href="https://docs.budibase.com">Dokumentation</a>
<span> · </span>
<a href="https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas">Featureanfrage</a>
<span> · </span>
<a href="https://github.com/Budibase/budibase/issues">Einen Bug melden</a>
<span> · </span>
Support: <a href="https://github.com/Budibase/budibase/discussions">Github Discussions</a>
</h3>
<br /><br />
## ✨ Features
- **Entwickle echte Webanwendungen.** Anders als ähnliche Plattformen entwickelst du mit Budibase echte Single-Page Webapplikationen (SPAs). Deine Budibase-Apps sind standardmäßig hochperformant und haben ein Responsive-Design für eine großartige Benutzererfahrung.
- **Quelloffen und erweiterbar.** Budibase ist quelloffen - lizenziert unter der GPL v3. Du kannst darauf vertrauen, dass Budibase auch in der Zukunft immer zur Verfügung steht. Budibase bietet eine Entwicklerfreundliche Plattform: du kannst Budibase erweitern, oder die Codebase forken und eigene Änderungen vornehmen.
- **Datenquellen einbinden oder von Null starten.** Budibase kann Daten aus vielen Quellen einbinden, unter anderem aus MongoDB, CouchDB, PostgreSQL, MySQL, Airtable, S3, DynamoDB, oder einer REST API. Und anders als ähnliche Plattformen erlaubt Budibase auch die App-Entwicklung komplett ohne Datenquellen mit einer internen Datenbank. Deine Datenquelle noch nicht dabei? [Frag einfach nach](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
- **Designe und entwickle Apps mit leistungsfähigen Komponenten.** Budibase kommt fertig mit optisch ansprechenden und leistungsfähigen Komponenten, die als Bausteine für deine UI dienen. Außerdem kannst du die UI mit vielen CSS-Styles nach deinem Geschmack anpassen. Fehlt dir eine Komponente? [Frag uns hier](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
- **Automatisiere Prozesse, integriere andere Tools und binde Web-APIs ein.** Spar dir Zeit, indem du manuelle Prozesse einfach automatisierst: Vom Verbinden mit Web-Hooks bis zum automatischen Senden von E-Mails, Budibase kann alles für dich erledigen. Eine Automatisierung ist noch nicht dabei? Du kannst einfach [deine eigene erstellen](https://github.com/Budibase/automations) oder [uns deine Idee mitteilen](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
- **Ein Paradies für Systemadministratoren** Budibase ist von Grund auf für das Skalieren ausgelegt. Du kannst Budibase einfach auf deiner eigenen Infrastruktur hosten und global Benutzer, Onboarding, SMTP, Applikationen, Gruppen, UI-Themes und mehr verwalten. Du kannst außerdem ein übersichtliches App-Portal für deine Benutzer bereitstellen und das Benutzermanagement an Gruppen-Manager delegieren.
<br />
---
<br />
## 🏁 Los geht's
Momentan existieren zwei Optionen mit Budibase loszulegen: Digital Ocean und Docker.
<br /><br />
### Los geht's mit Digital Ocean
Der einfachste und schnellste Weg loszulegen ist Digital Ocean:
<a href="https://marketplace.digitalocean.com/apps/budibase">1-Click Deploy auf Digital Ocean</a>
<a href="https://marketplace.digitalocean.com/apps/budibase">
<img src="https://user-images.githubusercontent.com/552074/87779219-5c3b7600-c824-11ea-9898-981a8ba94f6c.png" alt="digital ocean badge">
</a>
<br /><br />
### Los geht's mit Docker
Um loszulegen musst du bereits `docker` und `docker compose` auf deinem Computer installiert haben.
Sobald du Docker installiert hast brauchst du ca. 5 Minuten für diese 4 Schritte:
1. Installiere das Budibase CLI Tool.
```
$ npm i -g @budibase/cli
```
2. Installiere Budibase (wähle den Speicherort und den Port auf dem Budibase laufen soll.)
```
$ budi hosting --init
```
3. Führe Budibase aus.
```
$ budi hosting --start
```
4. Lege einen Admin-Benutzer an.
Gib die E-Mail und das Passwort für den neuen Admin-Benutzer ein.
Schon geschafft! Jetzt kann es losgehen mit der minutenschnellen Entwicklung deiner Tools. Für weitere Informationen und Tipps schau doch mal in unsere [Dokumentation](https://docs.budibase.com/getting-started).
<br />
---
<br />
## 🎓 Budibase lernen
Die Budibase Dokumentation [findest du hier](https://docs.budibase.com).
<br />
---
<br /><br />
## 💬 Community
Wenn du eine Frage hast, oder dich mit anderen Budibase-Nutzern unterhalten willst, schau doch mal in unsere
[Github Discussions](https://github.com/Budibase/budibase/discussions).
<img src="https://d33wubrfki0l68.cloudfront.net/e9241201fd89f9abbbdaac4fe44bb16312752abe/84013/img/hero-images/community.webp" />
<br /><br />
---
<br />
## ❗ Verhaltenskodex
Budibase steht für eine einladende und vielfältige Community frei von Belästigung. Wir erwarten dass sich jeder in der Budibase-Community an unseren [**Verhaltenskodex**](https://github.com/Budibase/budibase/blob/HEAD/.github/CODE_OF_CONDUCT.md) hält. Bitte les ihn dir durch.
<br />
---
<br />
## 🙌 Zu Budibase beitragen
Von einem gemeldeten Bug bis zum Erstellen einer Pull-Request: wir schätzen jeden Beitrag. Wenn du ein neues Feature implementieren willst oder eine Änderung an der API vornehmen willst, erstelle bitte zuerst ein Issue. So können wir sicherstellen, dass deine Arbeit nicht umsonst ist.
### Unsicher wo du anfangen sollst?
Gute Ideen für erste Beiträge zum Projekt [findest du hier](https://github.com/Budibase/budibase/projects/22).
### Wie die Repository strukturiert ist.
Budibase ist eine Monorepo, die von Lerna verwaltet wird. Lerna verwaltet das Erstellen und Veröffentlichen von Budibase-Paketen.
Grob besteht Budibase aus folgenden Modulen:
- [packages/builder](https://github.com/Budibase/budibase/tree/HEAD/packages/builder) - enthält Code für den clientseitigen Budibase Builder, mit dem Anwendungen erstellt werden.
- [packages/client](https://github.com/Budibase/budibase/tree/HEAD/packages/client) - Ein Modul, das im Browser läuft und aus JSON-Definitionen funktionsfähige Web-Apps erstellt.
- [packages/server](https://github.com/Budibase/budibase/tree/HEAD/packages/server) - Der Budibase Server. Diese Koa-Anwendung stellt den Javascript-Code für den Builder und den Client bereit, und bietet eine API für die Interaktion mit dem Budibase Backend, Datenbanken und dem Dateisystem.
Für mehr Informationen schau in die [CONTRIBUTING.md](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md)
<br /><br />
---
<br /><br />
## 📝 Lizenz
Budibase ist quelloffen, lizenziert unter der [GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html). Die Client- und Komponentenbibliotheken sind unter der [MPL](https://directory.fsf.org/wiki/License:MPL-2.0) lizenziert, damit du deine erstellten Apps unter deine präferierte Lizenz stellen kannst.
<br /><br />
---
<br />
## ⭐ Github-Sterne im Verlauf der Zeit
[![Stargazers over time](https://starchart.cc/Budibase/budibase.svg)](https://starchart.cc/Budibase/budibase)
Wenn du zwischen Updates des Builders Probleme auftreten, lies bitte den Guide [hier](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md#troubleshooting), um deine Umgebung zurückzusetzen.
<br />
---
<br /><br />
## Mitwirkende ✨
Vielen Dank an alle wundervollen Menschen, die zu Budibase beigetragen haben ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="http://martinmck.com"><img src="https://avatars1.githubusercontent.com/u/11256663?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Martin McKeaveney</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=shogunpurple" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=shogunpurple" title="Documentation">📖</a> <a href="https://github.com/Budibase/budibase/commits?author=shogunpurple" title="Tests">⚠️</a> <a href="#infra-shogunpurple" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center"><a href="http://www.michaeldrury.co.uk/"><img src="https://avatars2.githubusercontent.com/u/4407001?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michael Drury</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=mike12345567" title="Documentation">📖</a> <a href="https://github.com/Budibase/budibase/commits?author=mike12345567" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=mike12345567" title="Tests">⚠️</a> <a href="#infra-mike12345567" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center"><a href="https://github.com/aptkingston"><img src="https://avatars3.githubusercontent.com/u/9075550?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andrew Kingston</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=aptkingston" title="Documentation">📖</a> <a href="https://github.com/Budibase/budibase/commits?author=aptkingston" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=aptkingston" title="Tests">⚠️</a> <a href="#design-aptkingston" title="Design">🎨</a></td>
<td align="center"><a href="https://budibase.com/"><img src="https://avatars3.githubusercontent.com/u/3524181?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michael Shanks</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=mjashanks" title="Documentation">📖</a> <a href="https://github.com/Budibase/budibase/commits?author=mjashanks" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=mjashanks" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/kevmodrome"><img src="https://avatars3.githubusercontent.com/u/534488?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Åberg Kultalahti</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=kevmodrome" title="Documentation">📖</a> <a href="https://github.com/Budibase/budibase/commits?author=kevmodrome" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=kevmodrome" title="Tests">⚠️</a></td>
<td align="center"><a href="https://www.budibase.com/"><img src="https://avatars2.githubusercontent.com/u/49767913?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Joe</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=joebudi" title="Documentation">📖</a> <a href="https://github.com/Budibase/budibase/commits?author=joebudi" title="Code">💻</a> <a href="#content-joebudi" title="Content">🖋</a> <a href="#design-joebudi" title="Design">🎨</a></td>
<td align="center"><a href="https://github.com/Rory-Powell"><img src="https://avatars.githubusercontent.com/u/8755148?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rory Powell</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=Rory-Powell" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=Rory-Powell" title="Documentation">📖</a> <a href="https://github.com/Budibase/budibase/commits?author=Rory-Powell" title="Tests">⚠️</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/PClmnt"><img src="https://avatars.githubusercontent.com/u/5665926?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Peter Clement</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=PClmnt" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=PClmnt" title="Documentation">📖</a> <a href="https://github.com/Budibase/budibase/commits?author=PClmnt" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/Conor-Mack"><img src="https://avatars1.githubusercontent.com/u/36074859?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Conor_Mack</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=Conor-Mack" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=Conor-Mack" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/pngwn"><img src="https://avatars1.githubusercontent.com/u/12937446?v=4?s=100" width="100px;" alt=""/><br /><sub><b>pngwn</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=pngwn" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=pngwn" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/HugoLd"><img src="https://avatars0.githubusercontent.com/u/26521848?v=4?s=100" width="100px;" alt=""/><br /><sub><b>HugoLd</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=HugoLd" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/victoriasloan"><img src="https://avatars.githubusercontent.com/u/9913651?v=4?s=100" width="100px;" alt=""/><br /><sub><b>victoriasloan</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=victoriasloan" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/yashank09"><img src="https://avatars.githubusercontent.com/u/37672190?v=4?s=100" width="100px;" alt=""/><br /><sub><b>yashank09</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=yashank09" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/SOVLOOKUP"><img src="https://avatars.githubusercontent.com/u/53158137?v=4?s=100" width="100px;" alt=""/><br /><sub><b>SOVLOOKUP</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=SOVLOOKUP" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/seoulaja"><img src="https://avatars.githubusercontent.com/u/15101654?v=4?s=100" width="100px;" alt=""/><br /><sub><b>seoulaja</b></sub></a><br /><a href="#translation-seoulaja" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/mslourens"><img src="https://avatars.githubusercontent.com/u/1907152?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Maurits Lourens</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=mslourens" title="Tests">⚠️</a> <a href="https://github.com/Budibase/budibase/commits?author=mslourens" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/Rory-Powell"><img src="https://avatars.githubusercontent.com/u/8755148?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rory Powell</b></sub></a><br /><a href="#infra-Rory-Powell" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/Budibase/budibase/commits?author=Rory-Powell" title="Tests">⚠️</a> <a href="https://github.com/Budibase/budibase/commits?author=Rory-Powell" title="Code">💻</a></td>
</tr>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
Dieses Projekt folgt der [All-Contributors](https://github.com/all-contributors/all-contributors) Spezifikation. Wir heißen Beiträge aller Art willkommen!

View File

@ -1,5 +1,5 @@
{ {
"version": "0.9.105-alpha.5", "version": "0.9.105-alpha.16",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*" "packages/*"

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/auth", "name": "@budibase/auth",
"version": "0.9.105-alpha.5", "version": "0.9.105-alpha.16",
"description": "Authentication middlewares for budibase builder and apps", "description": "Authentication middlewares for budibase builder and apps",
"main": "src/index.js", "main": "src/index.js",
"author": "Budibase", "author": "Budibase",

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/bbui", "name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.", "description": "A UI solution used in the different Budibase projects.",
"version": "0.9.105-alpha.5", "version": "0.9.105-alpha.16",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"module": "dist/bbui.es.js", "module": "dist/bbui.es.js",

View File

@ -19,7 +19,7 @@
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const categories = [ const categories = [
{ {
label: "Grays", label: "Theme",
colors: [ colors: [
"gray-50", "gray-50",
"gray-75", "gray-75",
@ -72,6 +72,9 @@
"blue-700", "blue-700",
"indigo-700", "indigo-700",
"magenta-700", "magenta-700",
"static-white",
"static-black",
], ],
}, },
] ]
@ -101,10 +104,20 @@
} }
const getCheckColor = value => { const getCheckColor = value => {
return /^.*(white|(gray-(50|75|100|200|300|400|500)))\)$/.test(value) // Use dynamic color for theme grays
if (value?.includes("gray")) {
return /^.*(gray-(50|75|100|200|300|400|500))\)$/.test(value)
? "var(--spectrum-global-color-gray-900)" ? "var(--spectrum-global-color-gray-900)"
: "var(--spectrum-global-color-gray-50)" : "var(--spectrum-global-color-gray-50)"
} }
// Use black check for static white
if (value?.includes("static-black")) {
return "var(--spectrum-global-color-static-gray-50)"
}
return "var(--spectrum-global-color-static-gray-900)"
}
</script> </script>
<div class="container"> <div class="container">

View File

@ -5,6 +5,7 @@
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import { generateID } from "../../utils/helpers" import { generateID } from "../../utils/helpers"
import Icon from "../../Icon/Icon.svelte" import Icon from "../../Icon/Icon.svelte"
import Link from "../../Link/Link.svelte"
const BYTES_IN_KB = 1000 const BYTES_IN_KB = 1000
const BYTES_IN_MB = 1000000 const BYTES_IN_MB = 1000000
@ -117,7 +118,13 @@
{#if gallery} {#if gallery}
<div class="gallery"> <div class="gallery">
<div class="title"> <div class="title">
<div class="filename">{selectedImage.name}</div> <div class="filename">
{#if selectedUrl}
<Link href={selectedUrl}>{selectedImage.name}</Link>
{:else}
{selectedImage.name}
{/if}
</div>
{#if selectedImage.size} {#if selectedImage.size}
<div class="filesize"> <div class="filesize">
{#if selectedImage.size <= BYTES_IN_MB} {#if selectedImage.size <= BYTES_IN_MB}

View File

@ -23,7 +23,11 @@
export let readonly = false export let readonly = false
export let quiet = false export let quiet = false
export let autoWidth = false export let autoWidth = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
$: sortedOptions = getSortedOptions(options, getOptionLabel)
const onClick = () => { const onClick = () => {
dispatch("click") dispatch("click")
if (readonly) { if (readonly) {
@ -31,6 +35,17 @@
} }
open = true open = true
} }
const getSortedOptions = (options, getLabel) => {
if (!options?.length || !Array.isArray(options)) {
return []
}
return options.sort((a, b) => {
const labelA = getLabel(a)
const labelB = getLabel(b)
return labelA > labelB ? 1 : -1
})
}
</script> </script>
<button <button
@ -101,8 +116,8 @@
</svg> </svg>
</li> </li>
{/if} {/if}
{#if options && Array.isArray(options)} {#if sortedOptions.length}
{#each options as option, idx} {#each sortedOptions as option, idx}
<li <li
class="spectrum-Menu-item" class="spectrum-Menu-item"
class:is-selected={isOptionSelected(getOptionValue(option, idx))} class:is-selected={isOptionSelected(getOptionValue(option, idx))}
@ -121,9 +136,9 @@
/> />
</span> </span>
{/if} {/if}
<span class="spectrum-Menu-itemLabel" <span class="spectrum-Menu-itemLabel">
>{getOptionLabel(option, idx)}</span {getOptionLabel(option, idx)}
> </span>
<svg <svg
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon" class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
focusable="false" focusable="false"

View File

@ -13,8 +13,9 @@
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const onChange = e => { const onChange = e => {
value = e.detail const isoString = e.detail.toISOString()
dispatch("change", e.detail) value = isoString
dispatch("change", isoString)
} }
</script> </script>

View File

@ -48,6 +48,9 @@
padding-top: var(--spacing-l); padding-top: var(--spacing-l);
padding-bottom: var(--spacing-l); padding-bottom: var(--spacing-l);
} }
.gap-XXS {
grid-gap: var(--spacing-xs);
}
.gap-XS { .gap-XS {
grid-gap: var(--spacing-s); grid-gap: var(--spacing-s);
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/builder", "name": "@budibase/builder",
"version": "0.9.105-alpha.5", "version": "0.9.105-alpha.16",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"private": true, "private": true,
"scripts": { "scripts": {
@ -65,10 +65,10 @@
} }
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "^0.9.105-alpha.5", "@budibase/bbui": "^0.9.105-alpha.16",
"@budibase/client": "^0.9.105-alpha.5", "@budibase/client": "^0.9.105-alpha.16",
"@budibase/colorpicker": "1.1.2", "@budibase/colorpicker": "1.1.2",
"@budibase/string-templates": "^0.9.105-alpha.5", "@budibase/string-templates": "^0.9.105-alpha.16",
"@sentry/browser": "5.19.1", "@sentry/browser": "5.19.1",
"@spectrum-css/page": "^3.0.1", "@spectrum-css/page": "^3.0.1",
"@spectrum-css/vars": "^3.0.1", "@spectrum-css/vars": "^3.0.1",

View File

@ -17,7 +17,13 @@ export const getBindableProperties = (asset, componentId) => {
const contextBindings = getContextBindings(asset, componentId) const contextBindings = getContextBindings(asset, componentId)
const userBindings = getUserBindings() const userBindings = getUserBindings()
const urlBindings = getUrlBindings(asset) const urlBindings = getUrlBindings(asset)
return [...contextBindings, ...userBindings, ...urlBindings] const deviceBindings = getDeviceBindings()
return [
...deviceBindings,
...urlBindings,
...contextBindings,
...userBindings,
]
} }
/** /**
@ -221,6 +227,27 @@ const getUserBindings = () => {
return bindings return bindings
} }
/**
* Gets all device bindings that are globally available.
*/
const getDeviceBindings = () => {
let bindings = []
if (get(store).clientFeatures?.deviceAwareness) {
const safeDevice = makePropSafe("device")
bindings.push({
type: "context",
runtimeBinding: `${safeDevice}.${makePropSafe("mobile")}`,
readableBinding: `Device.Mobile`,
})
bindings.push({
type: "context",
runtimeBinding: `${safeDevice}.${makePropSafe("tablet")}`,
readableBinding: `Device.Tablet`,
})
}
return bindings
}
/** /**
* Gets all bindable properties from URL parameters. * Gets all bindable properties from URL parameters.
*/ */

View File

@ -35,6 +35,7 @@ const INITIAL_FRONTEND_STATE = {
clientFeatures: { clientFeatures: {
spectrumThemes: false, spectrumThemes: false,
intelligentLoading: false, intelligentLoading: false,
deviceAwareness: false,
}, },
currentFrontEndType: "none", currentFrontEndType: "none",
selectedScreenId: "", selectedScreenId: "",

View File

@ -31,7 +31,12 @@
.flat() .flat()
// Prevent modal closing if there were errors // Prevent modal closing if there were errors
return false return false
} else if (rowResponse.status === 400 || rowResponse.status === 500) { } else if (rowResponse.status === 400 && rowResponse.validationErrors) {
errors = Object.keys(rowResponse.validationErrors).map(field => ({
message: `${field} ${rowResponse.validationErrors[field][0]}`,
}))
return false
} else if (rowResponse.status === 500) {
errors = [{ message: rowResponse.message }] errors = [{ message: rowResponse.message }]
return false return false
} }

View File

@ -38,7 +38,7 @@
</section> </section>
{#if filteredColumns?.length} {#if filteredColumns?.length}
<section> <section>
<div class="heading">Columns</div> <div class="heading">Bindable Values</div>
<ul> <ul>
{#each filteredColumns as { readableBinding }} {#each filteredColumns as { readableBinding }}
<li <li

View File

@ -32,9 +32,29 @@
if (!control) { if (!control) {
return false return false
} }
if (setting.dependsOn && isEmpty(componentInstance[setting.dependsOn])) {
// Parse dependant settings
if (setting.dependsOn) {
let dependantSetting = setting.dependsOn
let dependantValue = null
if (typeof setting.dependsOn === "object") {
dependantSetting = setting.dependsOn.setting
dependantValue = setting.dependsOn.value
}
if (!dependantSetting) {
return false return false
} }
// If no specific value is depended upon, check if a value exists at all
// for the dependent setting
if (dependantValue == null) {
return !isEmpty(componentInstance[dependantSetting])
}
// Otherwise check the value matches
return componentInstance[dependantSetting] === dependantValue
}
return true return true
} }
</script> </script>
@ -67,6 +87,7 @@
placeholder: setting.placeholder, placeholder: setting.placeholder,
}} }}
{bindings} {bindings}
{componentDefinition}
/> />
{/if} {/if}
{/each} {/each}

View File

@ -1,5 +0,0 @@
<script>
import FormFieldSelect from "./FormFieldSelect.svelte"
</script>
<FormFieldSelect {...$$props} on:change type="attachment" />

View File

@ -1,5 +0,0 @@
<script>
import FormFieldSelect from "./FormFieldSelect.svelte"
</script>
<FormFieldSelect {...$$props} on:change type="boolean" />

View File

@ -282,12 +282,12 @@
gap: var(--spacing-l); gap: var(--spacing-l);
display: grid; display: grid;
align-items: center; align-items: center;
grid-template-columns: auto 1fr auto 1fr 1fr 1fr 1fr auto auto; grid-template-columns: auto 160px auto 1fr 130px 130px 1fr auto auto;
border-radius: var(--border-radius-s); border-radius: var(--border-radius-s);
transition: background-color ease-in-out 130ms; transition: background-color ease-in-out 130ms;
} }
.condition.update { .condition.update {
grid-template-columns: auto 1fr 1fr auto 1fr auto 1fr 1fr 1fr 1fr auto; grid-template-columns: auto 160px 1fr auto 1fr auto 1fr 130px 130px 1fr auto auto;
} }
.condition:hover { .condition:hover {
background-color: var(--spectrum-global-color-gray-100); background-color: var(--spectrum-global-color-gray-100);

View File

@ -1,5 +0,0 @@
<script>
import FormFieldSelect from "./FormFieldSelect.svelte"
</script>
<FormFieldSelect {...$$props} on:change type="datetime" />

View File

@ -0,0 +1,182 @@
<script>
import {
DatePicker,
Icon,
Button,
Select,
Combobox,
Input,
DrawerContent,
Layout,
Body,
} from "@budibase/bbui"
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
import { generate } from "shortid"
import { OperatorOptions, getValidOperatorsForType } from "helpers/lucene"
export let schemaFields
export let filters = []
export let bindings = []
const BannedTypes = ["link", "attachment", "formula"]
$: fieldOptions = (schemaFields ?? [])
.filter(field => !BannedTypes.includes(field.type))
.map(field => field.name)
const addFilter = () => {
filters = [
...filters,
{
id: generate(),
field: null,
operator: OperatorOptions.Equals.value,
value: null,
valueType: "Value",
},
]
}
const removeFilter = id => {
filters = filters.filter(field => field.id !== id)
}
const duplicateFilter = id => {
const existingFilter = filters.find(filter => filter.id === id)
const duplicate = { ...existingFilter, id: generate() }
filters = [...filters, duplicate]
}
const onFieldChange = (expression, field) => {
// Update the field type
expression.type = schemaFields.find(x => x.name === field)?.type
// Ensure a valid operator is set
const validOperators = getValidOperatorsForType(expression.type).map(
x => x.value
)
if (!validOperators.includes(expression.operator)) {
expression.operator = validOperators[0] ?? OperatorOptions.Equals.value
onOperatorChange(expression, expression.operator)
}
}
const onOperatorChange = (expression, operator) => {
const noValueOptions = [
OperatorOptions.Empty.value,
OperatorOptions.NotEmpty.value,
]
expression.noValue = noValueOptions.includes(operator)
if (expression.noValue) {
expression.value = null
}
}
const getFieldOptions = field => {
const schema = schemaFields.find(x => x.name === field)
return schema?.constraints?.inclusion || []
}
</script>
<DrawerContent>
<div class="container">
<Layout noPadding>
<Body size="S">
{#if !filters?.length}
Add your first filter column.
{:else}
Results are filtered to only those which match all of the following
constraints.
{/if}
</Body>
{#if filters?.length}
<div class="fields">
{#each filters as filter, idx}
<Select
bind:value={filter.field}
options={fieldOptions}
on:change={e => onFieldChange(filter, e.detail)}
placeholder="Column"
/>
<Select
disabled={!filter.field}
options={getValidOperatorsForType(filter.type)}
bind:value={filter.operator}
on:change={e => onOperatorChange(filter, e.detail)}
placeholder={null}
/>
<Select
disabled={filter.noValue || !filter.field}
options={["Value", "Binding"]}
bind:value={filter.valueType}
placeholder={null}
/>
{#if filter.valueType === "Binding"}
<DrawerBindableInput
disabled={filter.noValue}
title={`Value for "${filter.field}"`}
value={filter.value}
placeholder="Value"
{bindings}
on:change={event => (filter.value = event.detail)}
/>
{:else if ["string", "longform", "number"].includes(filter.type)}
<Input disabled={filter.noValue} bind:value={filter.value} />
{:else if filter.type === "options"}
<Combobox
disabled={filter.noValue}
options={getFieldOptions(filter.field)}
bind:value={filter.value}
/>
{:else if filter.type === "boolean"}
<Combobox
disabled={filter.noValue}
options={[
{ label: "True", value: "true" },
{ label: "False", value: "false" },
]}
bind:value={filter.value}
/>
{:else if filter.type === "datetime"}
<DatePicker disabled={filter.noValue} bind:value={filter.value} />
{:else}
<DrawerBindableInput disabled />
{/if}
<Icon
name="Duplicate"
hoverable
size="S"
on:click={() => duplicateFilter(filter.id)}
/>
<Icon
name="Close"
hoverable
size="S"
on:click={() => removeFilter(filter.id)}
/>
{/each}
</div>
{/if}
<div>
<Button icon="AddCircle" size="M" secondary on:click={addFilter}>
Add filter
</Button>
</div>
</Layout>
</div>
</DrawerContent>
<style>
.container {
width: 100%;
max-width: 1000px;
margin: 0 auto;
}
.fields {
display: grid;
column-gap: var(--spacing-l);
row-gap: var(--spacing-s);
align-items: center;
grid-template-columns: 1fr 120px 120px 1fr auto auto;
}
</style>

View File

@ -1,19 +1,11 @@
<script> <script>
import { import { notifications, ActionButton, Button, Drawer } from "@budibase/bbui"
notifications,
ActionButton,
Button,
Drawer,
Body,
DrawerContent,
Layout,
} from "@budibase/bbui"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import { import {
getDatasourceForProvider, getDatasourceForProvider,
getSchemaForDatasource, getSchemaForDatasource,
} from "builderStore/dataBinding" } from "builderStore/dataBinding"
import LuceneFilterBuilder from "./LuceneFilterBuilder.svelte" import FilterDrawer from "./FilterDrawer.svelte"
import { currentAsset } from "builderStore" import { currentAsset } from "builderStore"
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -25,9 +17,6 @@
let drawer let drawer
let tempValue = value || [] let tempValue = value || []
$: numFilters = Array.isArray(tempValue)
? tempValue.length
: Object.keys(tempValue || {}).length
$: dataSource = getDatasourceForProvider($currentAsset, componentInstance) $: dataSource = getDatasourceForProvider($currentAsset, componentInstance)
$: schema = getSchemaForDatasource($currentAsset, dataSource)?.schema $: schema = getSchemaForDatasource($currentAsset, dataSource)?.schema
$: schemaFields = Object.values(schema || {}) $: schemaFields = Object.values(schema || {})
@ -43,17 +32,10 @@
<ActionButton on:click={drawer.show}>Define filters</ActionButton> <ActionButton on:click={drawer.show}>Define filters</ActionButton>
<Drawer bind:this={drawer} title="Filtering"> <Drawer bind:this={drawer} title="Filtering">
<Button cta slot="buttons" on:click={saveFilter}>Save</Button> <Button cta slot="buttons" on:click={saveFilter}>Save</Button>
<DrawerContent slot="body"> <FilterDrawer
<Layout noPadding> slot="body"
<Body size="S"> bind:filters={tempValue}
{#if !numFilters} {bindings}
Add your first filter column. {schemaFields}
{:else} />
Results are filtered to only those which match all of the following
constraints.
{/if}
</Body>
<LuceneFilterBuilder bind:value={tempValue} {schemaFields} {bindings} />
</Layout>
</DrawerContent>
</Drawer> </Drawer>

View File

@ -1,151 +0,0 @@
<script>
import {
DatePicker,
ActionButton,
Button,
Select,
Combobox,
Input,
} from "@budibase/bbui"
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
import { generate } from "shortid"
import { OperatorOptions, getValidOperatorsForType } from "helpers/lucene"
export let schemaFields
export let value
export let bindings = []
const BannedTypes = ["link", "attachment"]
$: fieldOptions = (schemaFields ?? [])
.filter(field => !BannedTypes.includes(field.type))
.map(field => field.name)
const addField = () => {
value = [
...value,
{
id: generate(),
field: null,
operator: OperatorOptions.Equals.value,
value: null,
valueType: "Value",
},
]
}
const removeField = id => {
value = value.filter(field => field.id !== id)
}
const onFieldChange = (expression, field) => {
// Update the field type
expression.type = schemaFields.find(x => x.name === field)?.type
// Ensure a valid operator is set
const validOperators = getValidOperatorsForType(expression.type).map(
x => x.value
)
if (!validOperators.includes(expression.operator)) {
expression.operator = validOperators[0] ?? OperatorOptions.Equals.value
onOperatorChange(expression, expression.operator)
}
}
const onOperatorChange = (expression, operator) => {
const noValueOptions = [
OperatorOptions.Empty.value,
OperatorOptions.NotEmpty.value,
]
expression.noValue = noValueOptions.includes(operator)
if (expression.noValue) {
expression.value = null
}
}
const getFieldOptions = field => {
const schema = schemaFields.find(x => x.name === field)
return schema?.constraints?.inclusion || []
}
</script>
{#if value?.length}
<div class="fields">
{#each value as expression, idx}
<Select
bind:value={expression.field}
options={fieldOptions}
on:change={e => onFieldChange(expression, e.detail)}
placeholder="Column"
/>
<Select
disabled={!expression.field}
options={getValidOperatorsForType(expression.type)}
bind:value={expression.operator}
on:change={e => onOperatorChange(expression, e.detail)}
placeholder={null}
/>
<Select
disabled={expression.noValue || !expression.field}
options={["Value", "Binding"]}
bind:value={expression.valueType}
placeholder={null}
/>
{#if expression.valueType === "Binding"}
<DrawerBindableInput
disabled={expression.noValue}
title={`Value for "${expression.field}"`}
value={expression.value}
placeholder="Value"
{bindings}
on:change={event => (expression.value = event.detail)}
/>
{:else if ["string", "longform", "number"].includes(expression.type)}
<Input disabled={expression.noValue} bind:value={expression.value} />
{:else if expression.type === "options"}
<Combobox
disabled={expression.noValue}
options={getFieldOptions(expression.field)}
bind:value={expression.value}
/>
{:else if expression.type === "boolean"}
<Combobox
disabled={expression.noValue}
options={[
{ label: "True", value: "true" },
{ label: "False", value: "false" },
]}
bind:value={expression.value}
/>
{:else if expression.type === "datetime"}
<DatePicker
disabled={expression.noValue}
bind:value={expression.value}
/>
{:else}
<DrawerBindableInput disabled />
{/if}
<ActionButton
size="S"
quiet
icon="Close"
on:click={() => removeField(expression.id)}
/>
{/each}
</div>
{/if}
<div>
<Button icon="AddCircle" size="M" secondary on:click={addField}>
Add expression
</Button>
</div>
<style>
.fields {
display: grid;
column-gap: var(--spacing-l);
row-gap: var(--spacing-s);
align-items: center;
grid-template-columns: 1fr 120px 120px 1fr auto;
}
</style>

View File

@ -23,6 +23,7 @@
const getOptions = (schema, fieldType) => { const getOptions = (schema, fieldType) => {
let entries = Object.entries(schema ?? {}) let entries = Object.entries(schema ?? {})
if (fieldType) { if (fieldType) {
fieldType = fieldType.split("/")[1]
entries = entries.filter(entry => entry[1].type === fieldType) entries = entries.filter(entry => entry[1].type === fieldType)
} }
return entries.map(entry => entry[0]) return entries.map(entry => entry[0])

View File

@ -1,5 +0,0 @@
<script>
import FormFieldSelect from "./FormFieldSelect.svelte"
</script>
<FormFieldSelect {...$$props} on:change type="longform" />

View File

@ -1,5 +0,0 @@
<script>
import FormFieldSelect from "./FormFieldSelect.svelte"
</script>
<FormFieldSelect {...$$props} on:change type="number" />

View File

@ -0,0 +1,82 @@
<script>
import {
Icon,
Button,
Input,
DrawerContent,
Layout,
Body,
} from "@budibase/bbui"
import { generate } from "shortid"
export let options = []
const removeOption = id => {
options = options.filter(option => option.id !== id)
}
const addOption = () => {
options = [
...options,
{
id: generate(),
label: null,
value: null,
},
]
}
</script>
<DrawerContent>
<div class="container">
<Layout noPadding gap="S">
{#if !options.length}
<Body size="S">Add an option to get started.</Body>
{/if}
{#if options?.length}
<div class="options">
{#each options as option (option.id)}
<Input
placeholder="Label"
bind:value={option.label}
label="Label"
labelPosition="left"
/>
<Input
placeholder="Value"
bind:value={option.value}
label="Value"
labelPosition="left"
/>
<Icon
name="Close"
hoverable
size="S"
on:click={() => removeOption(option.id)}
/>
{/each}
</div>
{/if}
<div>
<Button icon="AddCircle" size="M" on:click={addOption} secondary>
Add Option
</Button>
</div>
</Layout>
</div>
</DrawerContent>
<style>
.container {
width: 100%;
max-width: 800px;
margin: 0 auto;
}
.options {
display: grid;
column-gap: var(--spacing-l);
row-gap: var(--spacing-s);
align-items: center;
grid-template-columns: 1fr 1fr auto;
}
</style>

View File

@ -0,0 +1,28 @@
<script>
import { ActionButton, Button, Drawer } from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
import OptionsDrawer from "./OptionsDrawer.svelte"
const dispatch = createEventDispatcher()
export let value
let drawer
let tempValue = value || []
const saveFilter = async () => {
// Filter out incomplete options
tempValue = tempValue.filter(option => option.value && option.label)
dispatch("change", tempValue)
drawer.hide()
}
</script>
<ActionButton on:click={drawer.show}>Define Options</ActionButton>
<Drawer bind:this={drawer} title="Options">
<svelte:fragment slot="description">
Define the options for this picker.
</svelte:fragment>
<Button cta slot="buttons" on:click={saveFilter}>Save</Button>
<OptionsDrawer bind:options={tempValue} slot="body" />
</Drawer>

View File

@ -1,5 +0,0 @@
<script>
import FormFieldSelect from "./FormFieldSelect.svelte"
</script>
<FormFieldSelect {...$$props} on:change type="options" />

View File

@ -1,5 +0,0 @@
<script>
import FormFieldSelect from "./FormFieldSelect.svelte"
</script>
<FormFieldSelect {...$$props} on:change type="link" />

View File

@ -1,5 +0,0 @@
<script>
import FormFieldSelect from "./FormFieldSelect.svelte"
</script>
<FormFieldSelect {...$$props} on:change type="string" />

View File

@ -0,0 +1,363 @@
<script>
import {
Button,
Icon,
DrawerContent,
Layout,
Select,
Heading,
Body,
Input,
DatePicker,
} from "@budibase/bbui"
import { currentAsset, selectedComponent } from "builderStore"
import { findClosestMatchingComponent } from "builderStore/storeUtils"
import { getSchemaForDatasource } from "builderStore/dataBinding"
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
import { generate } from "shortid"
export let rules = []
export let bindings = []
export let type
const Constraints = {
Required: {
label: "Required",
value: "required",
},
MinLength: {
label: "Min length",
value: "minLength",
},
MaxLength: {
label: "Max length",
value: "maxLength",
},
MaxValue: {
label: "Max value",
value: "maxValue",
},
MinValue: {
label: "Min value",
value: "minValue",
},
Equal: {
label: "Must equal",
value: "equal",
},
NotEqual: {
label: "Must not equal",
value: "notEqual",
},
Regex: {
label: "Must match regex",
value: "regex",
},
NotRegex: {
label: "Must not match regex",
value: "notRegex",
},
Contains: {
label: "Must contain row ID",
value: "contains",
},
NotContains: {
label: "Must not contain row ID",
value: "notContains",
},
}
const ConstraintMap = {
["string"]: [
Constraints.Required,
Constraints.MaxLength,
Constraints.Equal,
Constraints.NotEqual,
Constraints.Regex,
Constraints.NotRegex,
],
["number"]: [
Constraints.Required,
Constraints.MaxValue,
Constraints.MinValue,
Constraints.Equal,
Constraints.NotEqual,
],
["boolean"]: [
Constraints.Required,
Constraints.Equal,
Constraints.NotEqual,
],
["datetime"]: [
Constraints.Required,
Constraints.MaxValue,
Constraints.MinValue,
Constraints.Equal,
Constraints.NotEqual,
],
["attachment"]: [Constraints.Required],
["link"]: [
Constraints.Required,
Constraints.Contains,
Constraints.NotContains,
Constraints.MinLength,
Constraints.MaxLength,
],
}
$: dataSourceSchema = getDataSourceSchema($currentAsset, $selectedComponent)
$: field = $selectedComponent?.field
$: schemaRules = parseRulesFromSchema(field, dataSourceSchema || {})
$: fieldType = type?.split("/")[1] || "string"
$: constraintOptions = getConstraintsForType(fieldType)
const getConstraintsForType = type => {
return ConstraintMap[type]
}
const getDataSourceSchema = (asset, component) => {
if (!asset || !component) {
return null
}
const formParent = findClosestMatchingComponent(
asset.props,
component._id,
component => component._component.endsWith("/form")
)
return getSchemaForDatasource(asset, formParent?.dataSource)
}
const parseRulesFromSchema = (field, dataSourceSchema) => {
if (!field || !dataSourceSchema) {
return []
}
const fieldSchema = dataSourceSchema.schema?.[field]
const constraints = fieldSchema?.constraints
if (!constraints) {
return []
}
let rules = []
// Required constraint
if (
field === dataSourceSchema?.table?.primaryDisplay ||
constraints.presence?.allowEmpty === false
) {
rules.push({
constraint: "required",
error: "Required field",
})
}
// String length constraint
if (exists(constraints.length?.maximum)) {
const length = constraints.length.maximum
rules.push({
constraint: "maxLength",
value: length,
error: `Maximum ${length} characters`,
})
}
// Min / max number constraint
if (exists(constraints.numericality?.greaterThanOrEqualTo)) {
const min = constraints.numericality.greaterThanOrEqualTo
rules.push({
constraint: "minValue",
value: min,
error: `Minimum value is ${min}`,
})
}
if (exists(constraints.numericality?.lessThanOrEqualTo)) {
const max = constraints.numericality.lessThanOrEqualTo
rules.push({
constraint: "maxValue",
value: max,
error: `Maximum value is ${max}`,
})
}
return rules
}
const exists = value => {
return value != null && value !== ""
}
const addRule = () => {
rules = [
...(rules || []),
{
valueType: "Binding",
type: fieldType,
id: generate(),
},
]
}
const removeRule = id => {
rules = rules.filter(link => link.id !== id)
}
const duplicateRule = id => {
const existingRule = rules.find(rule => rule.id === id)
const newRule = { ...existingRule, id: generate() }
rules = [...rules, newRule]
}
</script>
<DrawerContent>
<div class="container">
<Layout noPadding gap="M">
<Layout noPadding gap={schemaRules?.length ? "S" : "XS"}>
<Heading size="XS">Schema validation rules</Heading>
{#if schemaRules?.length}
<div class="links">
{#each schemaRules as rule}
<div class="rule schema">
<Select
placeholder="Constraint"
value={rule.constraint}
options={constraintOptions}
disabled
/>
<Select
placeholder={null}
value="Value"
options={["Binding", "Value"]}
disabled
/>
<DrawerBindableInput
placeholder="Constraint value"
value={rule.value}
{bindings}
disabled
/>
<DrawerBindableInput
placeholder="Error message"
value={rule.error}
{bindings}
disabled
/>
<div />
</div>
{/each}
</div>
{:else}
<Body size="S">
There are no built-in validation rules from the schema.
</Body>
{/if}
</Layout>
<Layout noPadding gap="S">
<Heading size="XS">Custom validation rules</Heading>
{#if rules?.length}
<div class="links">
{#each rules as rule (rule.id)}
<div class="rule">
<Select
bind:value={rule.constraint}
options={constraintOptions}
placeholder="Constraint"
/>
<Select
disabled={rule.constraint === "required"}
placeholder={null}
bind:value={rule.valueType}
options={["Binding", "Value"]}
/>
{#if rule.valueType === "Binding"}
<!-- Bindings always get a bindable input -->
<DrawerBindableInput
placeholder="Constraint value"
value={rule.value}
{bindings}
disabled={rule.constraint === "required"}
on:change={e => (rule.value = e.detail)}
/>
{:else if ["maxLength", "minLength", "regex", "notRegex", "contains", "notContains"].includes(rule.constraint)}
<!-- Certain constraints always need string values-->
<Input
bind:value={rule.value}
placeholder="Constraint value"
/>
{:else}
<!-- Otherwise we render a component based on the type -->
{#if ["string", "number", "options", "longform"].includes(rule.type)}
<Input
disabled={rule.constraint === "required"}
bind:value={rule.value}
placeholder="Constraint value"
/>
{:else if rule.type === "boolean"}
<Select
disabled={rule.constraint === "required"}
options={[
{ label: "True", value: "true" },
{ label: "False", value: "false" },
]}
bind:value={rule.value}
/>
{:else if rule.type === "datetime"}
<DatePicker
enableTime={false}
disabled={rule.constraint === "required"}
bind:value={rule.value}
/>
{:else}
<DrawerBindableInput disabled />
{/if}
{/if}
<DrawerBindableInput
placeholder="Error message"
value={rule.error}
{bindings}
on:change={e => (rule.error = e.detail)}
/>
<Icon
name="Duplicate"
hoverable
size="S"
on:click={() => duplicateRule(rule.id)}
/>
<Icon
name="Close"
hoverable
size="S"
on:click={() => removeRule(rule.id)}
/>
</div>
{/each}
</div>
{/if}
<div class="button">
<Button secondary icon="Add" on:click={addRule}>Add Rule</Button>
</div>
</Layout>
</Layout>
</div>
</DrawerContent>
<style>
.container {
width: 100%;
max-width: 1000px;
margin: 0 auto;
}
.links {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: var(--spacing-m);
}
.rule {
gap: var(--spacing-l);
display: grid;
align-items: center;
grid-template-columns: 190px 120px 1fr 1fr auto auto;
border-radius: var(--border-radius-s);
transition: background-color ease-in-out 130ms;
}
</style>

View File

@ -0,0 +1,33 @@
<script>
import { Button, ActionButton, Drawer } from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
import ValidationDrawer from "./ValidationDrawer.svelte"
export let value = []
export let bindings = []
export let componentDefinition
export let type
let drawer
const dispatch = createEventDispatcher()
const save = () => {
dispatch("change", value)
drawer.hide()
}
</script>
<ActionButton on:click={drawer.show}>Configure Validation</ActionButton>
<Drawer bind:this={drawer} title="Validation Rules">
<svelte:fragment slot="description">
Configure validation rules for this field.
</svelte:fragment>
<Button cta slot="buttons" on:click={save}>Save</Button>
<ValidationDrawer
slot="body"
bind:rules={value}
{type}
{bindings}
{componentDefinition}
/>
</Drawer>

View File

@ -12,14 +12,9 @@ import SectionSelect from "./SectionSelect.svelte"
import NavigationEditor from "./NavigationEditor/NavigationEditor.svelte" import NavigationEditor from "./NavigationEditor/NavigationEditor.svelte"
import FilterEditor from "./FilterEditor/FilterEditor.svelte" import FilterEditor from "./FilterEditor/FilterEditor.svelte"
import URLSelect from "./URLSelect.svelte" import URLSelect from "./URLSelect.svelte"
import StringFieldSelect from "./StringFieldSelect.svelte" import OptionsEditor from "./OptionsEditor/OptionsEditor.svelte"
import NumberFieldSelect from "./NumberFieldSelect.svelte" import FormFieldSelect from "./FormFieldSelect.svelte"
import OptionsFieldSelect from "./OptionsFieldSelect.svelte" import ValidationEditor from "./ValidationEditor/ValidationEditor.svelte"
import BooleanFieldSelect from "./BooleanFieldSelect.svelte"
import LongFormFieldSelect from "./LongFormFieldSelect.svelte"
import DateTimeFieldSelect from "./DateTimeFieldSelect.svelte"
import AttachmentFieldSelect from "./AttachmentFieldSelect.svelte"
import RelationshipFieldSelect from "./RelationshipFieldSelect.svelte"
const componentMap = { const componentMap = {
text: Input, text: Input,
@ -34,19 +29,28 @@ const componentMap = {
icon: IconSelect, icon: IconSelect,
field: FieldSelect, field: FieldSelect,
multifield: MultiFieldSelect, multifield: MultiFieldSelect,
options: OptionsEditor,
schema: SchemaSelect, schema: SchemaSelect,
section: SectionSelect, section: SectionSelect,
navigation: NavigationEditor, navigation: NavigationEditor,
filter: FilterEditor, filter: FilterEditor,
url: URLSelect, url: URLSelect,
"field/string": StringFieldSelect, "field/string": FormFieldSelect,
"field/number": NumberFieldSelect, "field/number": FormFieldSelect,
"field/options": OptionsFieldSelect, "field/options": FormFieldSelect,
"field/boolean": BooleanFieldSelect, "field/boolean": FormFieldSelect,
"field/longform": LongFormFieldSelect, "field/longform": FormFieldSelect,
"field/datetime": DateTimeFieldSelect, "field/datetime": FormFieldSelect,
"field/attachment": AttachmentFieldSelect, "field/attachment": FormFieldSelect,
"field/link": RelationshipFieldSelect, "field/link": FormFieldSelect,
// Some validation types are the same as others, so not all types are
// explicitly listed here. e.g. options uses string validation
"validation/string": ValidationEditor,
"validation/number": ValidationEditor,
"validation/boolean": ValidationEditor,
"validation/datetime": ValidationEditor,
"validation/attachment": ValidationEditor,
"validation/link": ValidationEditor,
} }
export const getComponentForSettingType = type => { export const getComponentForSettingType = type => {

View File

@ -53,7 +53,7 @@
label={def.label} label={def.label}
key={def.key} key={def.key}
value={deepGet($currentAsset, def.key)} value={deepGet($currentAsset, def.key)}
on:change={event => setAssetProps(def.key, event.detail, def.parser)} onChange={val => setAssetProps(def.key, val, def.parser)}
{bindings} {bindings}
/> />
{/each} {/each}

View File

@ -14,6 +14,7 @@ export const margin = {
options: [ options: [
{ label: "4px", value: "4px" }, { label: "4px", value: "4px" },
{ label: "8px", value: "8px" }, { label: "8px", value: "8px" },
{ label: "12px", value: "12px" },
{ label: "16px", value: "16px" }, { label: "16px", value: "16px" },
{ label: "20px", value: "20px" }, { label: "20px", value: "20px" },
{ label: "32px", value: "32px" }, { label: "32px", value: "32px" },
@ -34,6 +35,7 @@ export const margin = {
options: [ options: [
{ label: "4px", value: "4px" }, { label: "4px", value: "4px" },
{ label: "8px", value: "8px" }, { label: "8px", value: "8px" },
{ label: "12px", value: "12px" },
{ label: "16px", value: "16px" }, { label: "16px", value: "16px" },
{ label: "20px", value: "20px" }, { label: "20px", value: "20px" },
{ label: "32px", value: "32px" }, { label: "32px", value: "32px" },
@ -54,6 +56,7 @@ export const margin = {
options: [ options: [
{ label: "4px", value: "4px" }, { label: "4px", value: "4px" },
{ label: "8px", value: "8px" }, { label: "8px", value: "8px" },
{ label: "12px", value: "12px" },
{ label: "16px", value: "16px" }, { label: "16px", value: "16px" },
{ label: "20px", value: "20px" }, { label: "20px", value: "20px" },
{ label: "32px", value: "32px" }, { label: "32px", value: "32px" },
@ -74,6 +77,7 @@ export const margin = {
options: [ options: [
{ label: "4px", value: "4px" }, { label: "4px", value: "4px" },
{ label: "8px", value: "8px" }, { label: "8px", value: "8px" },
{ label: "12px", value: "12px" },
{ label: "16px", value: "16px" }, { label: "16px", value: "16px" },
{ label: "20px", value: "20px" }, { label: "20px", value: "20px" },
{ label: "32px", value: "32px" }, { label: "32px", value: "32px" },
@ -101,6 +105,7 @@ export const padding = {
options: [ options: [
{ label: "4px", value: "4px" }, { label: "4px", value: "4px" },
{ label: "8px", value: "8px" }, { label: "8px", value: "8px" },
{ label: "12px", value: "12px" },
{ label: "16px", value: "16px" }, { label: "16px", value: "16px" },
{ label: "20px", value: "20px" }, { label: "20px", value: "20px" },
{ label: "32px", value: "32px" }, { label: "32px", value: "32px" },
@ -121,6 +126,7 @@ export const padding = {
options: [ options: [
{ label: "4px", value: "4px" }, { label: "4px", value: "4px" },
{ label: "8px", value: "8px" }, { label: "8px", value: "8px" },
{ label: "12px", value: "12px" },
{ label: "16px", value: "16px" }, { label: "16px", value: "16px" },
{ label: "20px", value: "20px" }, { label: "20px", value: "20px" },
{ label: "32px", value: "32px" }, { label: "32px", value: "32px" },
@ -141,6 +147,7 @@ export const padding = {
options: [ options: [
{ label: "4px", value: "4px" }, { label: "4px", value: "4px" },
{ label: "8px", value: "8px" }, { label: "8px", value: "8px" },
{ label: "12px", value: "12px" },
{ label: "16px", value: "16px" }, { label: "16px", value: "16px" },
{ label: "20px", value: "20px" }, { label: "20px", value: "20px" },
{ label: "32px", value: "32px" }, { label: "32px", value: "32px" },
@ -161,6 +168,7 @@ export const padding = {
options: [ options: [
{ label: "4px", value: "4px" }, { label: "4px", value: "4px" },
{ label: "8px", value: "8px" }, { label: "8px", value: "8px" },
{ label: "12px", value: "12px" },
{ label: "16px", value: "16px" }, { label: "16px", value: "16px" },
{ label: "20px", value: "20px" }, { label: "20px", value: "20px" },
{ label: "32px", value: "32px" }, { label: "32px", value: "32px" },

View File

@ -16,6 +16,7 @@
import { onMount } from "svelte" import { onMount } from "svelte"
import { capitalise } from "helpers" import { capitalise } from "helpers"
import { goto } from "@roxi/routify" import { goto } from "@roxi/routify"
import { APP_NAME_REGEX } from "constants"
export let template export let template
@ -23,7 +24,13 @@
const errors = writable({}) const errors = writable({})
const touched = writable({}) const touched = writable({})
const validator = { const validator = {
name: string().required("Your application must have a name"), name: string()
.trim()
.required("Your application must have a name")
.matches(
APP_NAME_REGEX,
"App name must be letters, numbers and spaces only"
),
file: template ? mixed().required("Please choose a file to import") : null, file: template ? mixed().required("Please choose a file to import") : null,
} }
@ -35,7 +42,9 @@
await hostingStore.actions.fetchDeployedApps() await hostingStore.actions.fetchDeployedApps()
const existingAppNames = svelteGet(hostingStore).deployedAppNames const existingAppNames = svelteGet(hostingStore).deployedAppNames
validator.name = string() validator.name = string()
.trim()
.required("Your application must have a name") .required("Your application must have a name")
.matches(APP_NAME_REGEX, "App name must be letters and numbers only")
.test( .test(
"non-existing-app-name", "non-existing-app-name",
"Another app with the same name already exists", "Another app with the same name already exists",
@ -74,7 +83,7 @@
try { try {
// Create form data to create app // Create form data to create app
let data = new FormData() let data = new FormData()
data.append("name", $values.name) data.append("name", $values.name.trim())
data.append("useTemplate", template != null) data.append("useTemplate", template != null)
if (template) { if (template) {
data.append("templateName", template.name) data.append("templateName", template.name)

View File

@ -12,12 +12,19 @@
import { string, object } from "yup" import { string, object } from "yup"
import { onMount } from "svelte" import { onMount } from "svelte"
import { capitalise } from "helpers" import { capitalise } from "helpers"
import { APP_NAME_REGEX } from "constants"
const values = writable({ name: null }) const values = writable({ name: null })
const errors = writable({}) const errors = writable({})
const touched = writable({}) const touched = writable({})
const validator = { const validator = {
name: string().required("Your application must have a name"), name: string()
.trim()
.required("Your application must have a name")
.matches(
APP_NAME_REGEX,
"App name must be letters, numbers and spaces only"
),
} }
export let app export let app
@ -37,7 +44,12 @@
await hostingStore.actions.fetchDeployedApps() await hostingStore.actions.fetchDeployedApps()
const existingAppNames = svelteGet(hostingStore).deployedAppNames const existingAppNames = svelteGet(hostingStore).deployedAppNames
validator.name = string() validator.name = string()
.trim()
.required("Your application must have a name") .required("Your application must have a name")
.matches(
APP_NAME_REGEX,
"App name must be letters, numbers and spaces only"
)
.test( .test(
"non-existing-app-name", "non-existing-app-name",
"Another app with the same name already exists", "Another app with the same name already exists",
@ -65,7 +77,7 @@
async function updateApp() { async function updateApp() {
try { try {
// Update App // Update App
await apps.update(app.instance._id, $values.name) await apps.update(app.instance._id, $values.name.trim())
hide() hide()
} catch (error) { } catch (error) {
console.error(error) console.error(error)

View File

@ -33,3 +33,5 @@ export const LAYOUT_NAMES = {
} }
export const BUDIBASE_INTERNAL_DB = "bb_internal" export const BUDIBASE_INTERNAL_DB = "bb_internal"
export const APP_NAME_REGEX = /^[\w\s]+$/

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/cli", "name": "@budibase/cli",
"version": "0.9.105-alpha.5", "version": "0.9.105-alpha.16",
"description": "Budibase CLI, for developers, self hosting and migrations.", "description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "src/index.js", "main": "src/index.js",
"bin": { "bin": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/client", "name": "@budibase/client",
"version": "0.9.105-alpha.5", "version": "0.9.105-alpha.16",
"license": "MPL-2.0", "license": "MPL-2.0",
"module": "dist/budibase-client.js", "module": "dist/budibase-client.js",
"main": "dist/budibase-client.js", "main": "dist/budibase-client.js",
@ -18,9 +18,9 @@
"dev:builder": "rollup -cw" "dev:builder": "rollup -cw"
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "^0.9.105-alpha.5", "@budibase/bbui": "^0.9.105-alpha.16",
"@budibase/standard-components": "^0.9.105-alpha.5", "@budibase/standard-components": "^0.9.105-alpha.16",
"@budibase/string-templates": "^0.9.105-alpha.5", "@budibase/string-templates": "^0.9.105-alpha.16",
"regexparam": "^1.3.0", "regexparam": "^1.3.0",
"shortid": "^2.2.15", "shortid": "^2.2.15",
"svelte-spa-router": "^3.0.5" "svelte-spa-router": "^3.0.5"

View File

@ -5,7 +5,6 @@
import NotificationDisplay from "./NotificationDisplay.svelte" import NotificationDisplay from "./NotificationDisplay.svelte"
import ConfirmationDisplay from "./ConfirmationDisplay.svelte" import ConfirmationDisplay from "./ConfirmationDisplay.svelte"
import PeekScreenDisplay from "./PeekScreenDisplay.svelte" import PeekScreenDisplay from "./PeekScreenDisplay.svelte"
import Provider from "./Provider.svelte"
import SDK from "../sdk" import SDK from "../sdk"
import { import {
createContextStore, createContextStore,
@ -16,12 +15,13 @@
builderStore, builderStore,
appStore, appStore,
} from "../store" } from "../store"
import { TableNames, ActionTypes } from "../constants"
import SettingsBar from "./preview/SettingsBar.svelte" import SettingsBar from "./preview/SettingsBar.svelte"
import SelectionIndicator from "./preview/SelectionIndicator.svelte" import SelectionIndicator from "./preview/SelectionIndicator.svelte"
import HoverIndicator from "./preview/HoverIndicator.svelte" import HoverIndicator from "./preview/HoverIndicator.svelte"
import { Layout, Heading, Body } from "@budibase/bbui" import { Layout, Heading, Body } from "@budibase/bbui"
import ErrorSVG from "../../../builder/assets/error.svg" import ErrorSVG from "../../../builder/assets/error.svg"
import UserBindingsProvider from "./UserBindingsProvider.svelte"
import DeviceBindingsProvider from "./DeviceBindingsProvider.svelte"
// Provide contexts // Provide contexts
setContext("sdk", SDK) setContext("sdk", SDK)
@ -41,16 +41,6 @@
} }
}) })
// Register this as a refreshable datasource so that user changes cause
// the user object to be refreshed
$: actions = [
{
type: ActionTypes.RefreshDatasource,
callback: () => authStore.actions.fetchUser(),
metadata: { dataSource: { type: "table", tableId: TableNames.USERS } },
},
]
// Handle no matching route - this is likely a permission error // Handle no matching route - this is likely a permission error
$: { $: {
if (dataLoaded && $routeStore.routerLoaded && !$routeStore.activeRoute) { if (dataLoaded && $routeStore.routerLoaded && !$routeStore.activeRoute) {
@ -93,7 +83,8 @@
</Layout> </Layout>
</div> </div>
{:else if $screenStore.activeLayout} {:else if $screenStore.activeLayout}
<Provider key="user" data={$authStore} {actions}> <UserBindingsProvider>
<DeviceBindingsProvider>
<div id="app-root" class:preview={$builderStore.inBuilder}> <div id="app-root" class:preview={$builderStore.inBuilder}>
{#key $screenStore.activeLayout._id} {#key $screenStore.activeLayout._id}
<Component instance={$screenStore.activeLayout.props} /> <Component instance={$screenStore.activeLayout.props} />
@ -116,7 +107,8 @@
<SelectionIndicator /> <SelectionIndicator />
<HoverIndicator /> <HoverIndicator />
{/if} {/if}
</Provider> </DeviceBindingsProvider>
</UserBindingsProvider>
{/if} {/if}
</div> </div>
{/if} {/if}

View File

@ -0,0 +1,31 @@
<script>
import Provider from "./Provider.svelte"
import { onMount } from "svelte"
let width = window.innerWidth
const tabletBreakpoint = 768
const desktopBreakpoint = 1280
const resizeObserver = new ResizeObserver(entries => {
if (entries?.[0]) {
width = entries[0].contentRect?.width
}
})
$: data = {
mobile: width && width < tabletBreakpoint,
tablet: width && width >= tabletBreakpoint && width < desktopBreakpoint,
}
onMount(() => {
const doc = document.documentElement
resizeObserver.observe(doc)
return () => {
resizeObserver.unobserve(doc)
}
})
</script>
<Provider key="device" {data}>
<slot />
</Provider>

View File

@ -0,0 +1,19 @@
<script>
import Provider from "./Provider.svelte"
import { authStore } from "../store"
import { ActionTypes, TableNames } from "../constants"
// Register this as a refreshable datasource so that user changes cause
// the user object to be refreshed
$: actions = [
{
type: ActionTypes.RefreshDatasource,
callback: () => authStore.actions.fetchUser(),
metadata: { dataSource: { type: "table", tableId: TableNames.USERS } },
},
]
</script>
<Provider key="user" data={$authStore} {actions}>
<slot />
</Provider>

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/server", "name": "@budibase/server",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "0.9.105-alpha.5", "version": "0.9.105-alpha.16",
"description": "Budibase Web Server", "description": "Budibase Web Server",
"main": "src/index.js", "main": "src/index.js",
"repository": { "repository": {
@ -62,9 +62,9 @@
"author": "Budibase", "author": "Budibase",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@budibase/auth": "^0.9.105-alpha.5", "@budibase/auth": "^0.9.105-alpha.16",
"@budibase/client": "^0.9.105-alpha.5", "@budibase/client": "^0.9.105-alpha.16",
"@budibase/string-templates": "^0.9.105-alpha.5", "@budibase/string-templates": "^0.9.105-alpha.16",
"@elastic/elasticsearch": "7.10.0", "@elastic/elasticsearch": "7.10.0",
"@koa/router": "8.0.0", "@koa/router": "8.0.0",
"@sendgrid/mail": "7.1.1", "@sendgrid/mail": "7.1.1",
@ -117,7 +117,7 @@
"devDependencies": { "devDependencies": {
"@babel/core": "^7.14.3", "@babel/core": "^7.14.3",
"@babel/preset-env": "^7.14.4", "@babel/preset-env": "^7.14.4",
"@budibase/standard-components": "^0.9.105-alpha.5", "@budibase/standard-components": "^0.9.105-alpha.16",
"@jest/test-sequencer": "^24.8.0", "@jest/test-sequencer": "^24.8.0",
"@types/bull": "^3.15.1", "@types/bull": "^3.15.1",
"@types/jest": "^26.0.23", "@types/jest": "^26.0.23",

View File

@ -57,7 +57,7 @@ exports.patch = async ctx => {
}) })
if (!validateResult.valid) { if (!validateResult.valid) {
throw validateResult.errors throw { validation: validateResult.errors }
} }
// returned row is cleaned and prepared for writing to DB // returned row is cleaned and prepared for writing to DB
@ -105,7 +105,7 @@ exports.save = async function (ctx) {
}) })
if (!validateResult.valid) { if (!validateResult.valid) {
throw validateResult.errors throw { validation: validateResult.errors }
} }
// make sure link rows are up to date // make sure link rows are up to date

View File

@ -58,6 +58,7 @@ router.use(async (ctx, next) => {
ctx.body = { ctx.body = {
message: err.message, message: err.message,
status: ctx.status, status: ctx.status,
validationErrors: err.validation,
} }
if (env.NODE_ENV !== "jest") { if (env.NODE_ENV !== "jest") {
ctx.log.error(err) ctx.log.error(err)

View File

@ -1,6 +1,10 @@
const CouchDB = require("../index") const CouchDB = require("../index")
const { IncludeDocs, getLinkDocuments } = require("./linkUtils") const { IncludeDocs, getLinkDocuments } = require("./linkUtils")
const { generateLinkID } = require("../utils") const {
generateLinkID,
InternalTables,
getUserMetadataParams,
} = require("../utils")
const Sentry = require("@sentry/node") const Sentry = require("@sentry/node")
const { FieldTypes, RelationshipTypes } = require("../../constants") const { FieldTypes, RelationshipTypes } = require("../../constants")
@ -208,6 +212,19 @@ class LinkController {
const linkedTable = await this._db.get(field.tableId) const linkedTable = await this._db.get(field.tableId)
const linkedSchema = linkedTable.schema[field.fieldName] const linkedSchema = linkedTable.schema[field.fieldName]
// We need to map the global users to metadata in each app for relationships
if (field.tableId === InternalTables.USER_METADATA) {
const users = await this._db.allDocs(getUserMetadataParams(null, {}))
const metadataRequired = rowField.filter(
userId => !users.rows.some(user => user.id === userId)
)
// ensure non-existing user metadata is created in the app DB
await this._db.bulkDocs(
metadataRequired.map(userId => ({ _id: userId }))
)
}
// iterate through the link IDs in the row field, see if any don't exist already // iterate through the link IDs in the row field, see if any don't exist already
for (let linkId of rowField) { for (let linkId of rowField) {
if (linkedSchema.relationshipType === RelationshipTypes.ONE_TO_MANY) { if (linkedSchema.relationshipType === RelationshipTypes.ONE_TO_MANY) {

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,8 @@
{ {
"features": { "features": {
"spectrumThemes": true, "spectrumThemes": true,
"intelligentLoading": true "intelligentLoading": true,
"deviceAwareness": true
}, },
"layout": { "layout": {
"name": "Layout", "name": "Layout",
@ -267,6 +268,10 @@
{ {
"label": "Warning", "label": "Warning",
"value": "warning" "value": "warning"
},
{
"label": "Over Background",
"value": "overBackground"
} }
], ],
"defaultValue": "primary" "defaultValue": "primary"
@ -595,6 +600,9 @@
"showInBar": true, "showInBar": true,
"barStyle": "picker", "barStyle": "picker",
"options": [{ "options": [{
"label": "Extra Small",
"value": "XS"
}, {
"label": "Small", "label": "Small",
"value": "S" "value": "S"
}, { }, {
@ -603,6 +611,15 @@
}, { }, {
"label": "Large", "label": "Large",
"value": "L" "value": "L"
}, {
"label": "Extra Large",
"value": "XL"
}, {
"label": "2XL",
"value": "XXL"
}, {
"label": "3XL",
"value": "XXXL"
}] }]
}, },
{ {
@ -689,6 +706,9 @@
"showInBar": true, "showInBar": true,
"barStyle": "picker", "barStyle": "picker",
"options": [{ "options": [{
"label": "Extra Small",
"value": "XS"
}, {
"label": "Small", "label": "Small",
"value": "S" "value": "S"
}, { }, {
@ -697,6 +717,15 @@
}, { }, {
"label": "Large", "label": "Large",
"value": "L" "value": "L"
}, {
"label": "Extra Large",
"value": "XL"
}, {
"label": "2XL",
"value": "XXL"
}, {
"label": "3XL",
"value": "XXXL"
}] }]
}, },
{ {
@ -1767,6 +1796,11 @@
"label": "Disabled", "label": "Disabled",
"key": "disabled", "key": "disabled",
"defaultValue": false "defaultValue": false
},
{
"type": "validation/string",
"label": "Validation",
"key": "validation"
} }
] ]
}, },
@ -1801,6 +1835,11 @@
"label": "Disabled", "label": "Disabled",
"key": "disabled", "key": "disabled",
"defaultValue": false "defaultValue": false
},
{
"type": "validation/number",
"label": "Validation",
"key": "validation"
} }
] ]
}, },
@ -1835,6 +1874,11 @@
"label": "Disabled", "label": "Disabled",
"key": "disabled", "key": "disabled",
"defaultValue": false "defaultValue": false
},
{
"type": "validation/string",
"label": "Validation",
"key": "validation"
} }
] ]
}, },
@ -1864,6 +1908,7 @@
"type": "select", "type": "select",
"label": "Type", "label": "Type",
"key": "optionsType", "key": "optionsType",
"defaultValue": "select",
"placeholder": "Pick an options type", "placeholder": "Pick an options type",
"options": [ "options": [
{ {
@ -1886,6 +1931,67 @@
"label": "Disabled", "label": "Disabled",
"key": "disabled", "key": "disabled",
"defaultValue": false "defaultValue": false
},
{
"type": "select",
"label": "Options source",
"key": "optionsSource",
"defaultValue": "schema",
"placeholder": "Pick an options source",
"options": [
{
"label": "Schema",
"value": "schema"
},
{
"label": "Data provider",
"value": "provider"
},
{
"label": "Custom",
"value": "custom"
}
]
},
{
"type": "dataProvider",
"label": "Options Provider",
"key": "dataProvider",
"dependsOn": {
"setting": "optionsSource",
"value": "provider"
}
},
{
"type": "field",
"label": "Label Column",
"key": "labelColumn",
"dependsOn": {
"setting": "optionsSource",
"value": "provider"
}
},
{
"type": "field",
"label": "Value Column",
"key": "valueColumn",
"dependsOn": {
"setting": "optionsSource",
"value": "provider"
}
},
{
"type": "options",
"key": "customOptions",
"dependsOn": {
"setting": "optionsSource",
"value": "custom"
}
},
{
"type": "validation/string",
"label": "Validation",
"key": "validation"
} }
] ]
}, },
@ -1943,6 +2049,11 @@
"label": "Disabled", "label": "Disabled",
"key": "disabled", "key": "disabled",
"defaultValue": false "defaultValue": false
},
{
"type": "validation/boolean",
"label": "Validation",
"key": "validation"
} }
] ]
}, },
@ -1978,6 +2089,11 @@
"label": "Disabled", "label": "Disabled",
"key": "disabled", "key": "disabled",
"defaultValue": false "defaultValue": false
},
{
"type": "validation/string",
"label": "Validation",
"key": "validation"
} }
] ]
}, },
@ -2018,6 +2134,11 @@
"label": "Disabled", "label": "Disabled",
"key": "disabled", "key": "disabled",
"defaultValue": false "defaultValue": false
},
{
"type": "validation/datetime",
"label": "Validation",
"key": "validation"
} }
] ]
}, },
@ -2042,6 +2163,11 @@
"label": "Disabled", "label": "Disabled",
"key": "disabled", "key": "disabled",
"defaultValue": false "defaultValue": false
},
{
"type": "validation/attachment",
"label": "Validation",
"key": "validation"
} }
] ]
}, },
@ -2071,6 +2197,11 @@
"label": "Disabled", "label": "Disabled",
"key": "disabled", "key": "disabled",
"defaultValue": false "defaultValue": false
},
{
"type": "validation/link",
"label": "Validation",
"key": "validation"
} }
] ]
}, },

View File

@ -29,11 +29,12 @@
"keywords": [ "keywords": [
"svelte" "svelte"
], ],
"version": "0.9.105-alpha.5", "version": "0.9.105-alpha.16",
"license": "MIT", "license": "MIT",
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc", "gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc",
"dependencies": { "dependencies": {
"@budibase/bbui": "^0.9.105-alpha.5", "@budibase/bbui": "^0.9.105-alpha.16",
"@spectrum-css/button": "^3.0.3",
"@spectrum-css/card": "^3.0.3", "@spectrum-css/card": "^3.0.3",
"@spectrum-css/divider": "^1.0.3", "@spectrum-css/divider": "^1.0.3",
"@spectrum-css/link": "^3.1.3", "@spectrum-css/link": "^3.1.3",

View File

@ -1,5 +1,6 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
import "@spectrum-css/button/dist/index-vars.css"
const { styleable } = getContext("sdk") const { styleable } = getContext("sdk")
const component = getContext("component") const component = getContext("component")
@ -27,4 +28,7 @@
width: fit-content; width: fit-content;
width: -moz-fit-content; width: -moz-fit-content;
} }
.spectrum-Button--overBackground:hover {
color: #555;
}
</style> </style>

View File

@ -16,6 +16,8 @@
$: componentText = $builderStore.inBuilder $: componentText = $builderStore.inBuilder
? text || $component.name || "Placeholder text" ? text || $component.name || "Placeholder text"
: text || "" : text || ""
$: sizeClass = `spectrum-Body--size${size || "M"}`
$: alignClass = `align--${align || "left"}`
// Add color styles to main styles object, otherwise the styleable helper // Add color styles to main styles object, otherwise the styleable helper
// overrides the color when it's passed as inline style. // overrides the color when it's passed as inline style.
@ -41,7 +43,7 @@
class:bold class:bold
class:italic class:italic
class:underline class:underline
class="align--{align || 'left'} size--{size || 'M'}" class="spectrum-Body {sizeClass} {alignClass}"
> >
{componentText} {componentText}
</p> </p>
@ -65,15 +67,6 @@
.underline { .underline {
text-decoration: underline; text-decoration: underline;
} }
.size--S {
font-size: 14px;
}
.size--M {
font-size: 16px;
}
.size--L {
font-size: 18px;
}
.align--left { .align--left {
text-align: left; text-align: left;
} }

View File

@ -6,6 +6,7 @@
export let field export let field
export let label export let label
export let disabled = false export let disabled = false
export let validation
let fieldState let fieldState
let fieldApi let fieldApi
@ -35,6 +36,7 @@
{label} {label}
{field} {field}
{disabled} {disabled}
{validation}
type="attachment" type="attachment"
bind:fieldState bind:fieldState
bind:fieldApi bind:fieldApi
@ -44,6 +46,7 @@
<CoreDropzone <CoreDropzone
value={$fieldState.value} value={$fieldState.value}
disabled={$fieldState.disabled} disabled={$fieldState.disabled}
error={$fieldState.error}
on:change={e => { on:change={e => {
fieldApi.setValue(e.detail) fieldApi.setValue(e.detail)
}} }}

View File

@ -7,6 +7,7 @@
export let text export let text
export let disabled = false export let disabled = false
export let size export let size
export let validation
export let defaultValue export let defaultValue
let fieldState let fieldState
@ -30,6 +31,7 @@
{label} {label}
{field} {field}
{disabled} {disabled}
{validation}
defaultValue={isTruthy(defaultValue)} defaultValue={isTruthy(defaultValue)}
type="boolean" type="boolean"
bind:fieldState bind:fieldState

View File

@ -7,6 +7,7 @@
export let placeholder export let placeholder
export let disabled = false export let disabled = false
export let enableTime = false export let enableTime = false
export let validation
export let defaultValue export let defaultValue
let fieldState let fieldState
@ -42,6 +43,7 @@
{label} {label}
{field} {field}
{disabled} {disabled}
{validation}
defaultValue={parseDate(defaultValue)} defaultValue={parseDate(defaultValue)}
type="datetime" type="datetime"
bind:fieldState bind:fieldState

View File

@ -11,6 +11,7 @@
export let defaultValue export let defaultValue
export let type export let type
export let disabled = false export let disabled = false
export let validation
// Get contexts // Get contexts
const formContext = getContext("form") const formContext = getContext("form")
@ -21,13 +22,21 @@
// Register field with form // Register field with form
const formApi = formContext?.formApi const formApi = formContext?.formApi
const labelPosition = fieldGroupContext?.labelPosition || "above" const labelPosition = fieldGroupContext?.labelPosition || "above"
const formField = formApi?.registerField(field, defaultValue, disabled) const formField = formApi?.registerField(
field,
defaultValue,
disabled,
validation
)
// Expose field properties to parent component // Expose field properties to parent component
fieldState = formField?.fieldState fieldState = formField?.fieldState
fieldApi = formField?.fieldApi fieldApi = formField?.fieldApi
fieldSchema = formField?.fieldSchema fieldSchema = formField?.fieldSchema
// Keep validation rules up to date
$: fieldApi?.updateValidation(validation)
// Extract label position from field group context // Extract label position from field group context
$: labelPositionClass = $: labelPositionClass =
labelPosition === "above" ? "" : `spectrum-FieldLabel--${labelPosition}` labelPosition === "above" ? "" : `spectrum-FieldLabel--${labelPosition}`

View File

@ -25,4 +25,7 @@
.spectrum-Form { .spectrum-Form {
width: 100%; width: 100%;
} }
.spectrum-Form--labelsAbove {
gap: var(--spectrum-global-dimension-size-100);
}
</style> </style>

View File

@ -21,7 +21,12 @@
// Form API contains functions to control the form // Form API contains functions to control the form
const formApi = { const formApi = {
registerField: (field, defaultValue = null, fieldDisabled = false) => { registerField: (
field,
defaultValue = null,
fieldDisabled = false,
validationRules
) => {
if (!field) { if (!field) {
return return
} }
@ -30,17 +35,23 @@
const isAutoColumn = !!schema?.[field]?.autocolumn const isAutoColumn = !!schema?.[field]?.autocolumn
// Create validation function based on field schema // Create validation function based on field schema
const constraints = schema?.[field]?.constraints const schemaConstraints = schema?.[field]?.constraints
const validate = createValidatorFromConstraints(constraints, field, table) const validator = createValidatorFromConstraints(
schemaConstraints,
validationRules,
field,
table
)
// Construct field object // Construct field object
fieldMap[field] = { fieldMap[field] = {
fieldState: makeFieldState( fieldState: makeFieldState(
field, field,
validator,
defaultValue, defaultValue,
disabled || fieldDisabled || isAutoColumn disabled || fieldDisabled || isAutoColumn
), ),
fieldApi: makeFieldApi(field, defaultValue, validate), fieldApi: makeFieldApi(field, defaultValue),
fieldSchema: schema?.[field] ?? {}, fieldSchema: schema?.[field] ?? {},
} }
@ -83,9 +94,11 @@
] ]
// Creates an API for a specific field // Creates an API for a specific field
const makeFieldApi = (field, defaultValue, validate) => { const makeFieldApi = field => {
// Sets the value for a certain field and invokes validation
const setValue = (value, skipCheck = false) => { const setValue = (value, skipCheck = false) => {
const { fieldState } = fieldMap[field] const { fieldState } = fieldMap[field]
const { validator } = get(fieldState)
// Skip if the value is the same // Skip if the value is the same
if (!skipCheck && get(fieldState).value === value) { if (!skipCheck && get(fieldState).value === value) {
@ -93,7 +106,7 @@
} }
// Update field state // Update field state
const error = validate ? validate(value) : null const error = validator ? validator(value) : null
fieldState.update(state => { fieldState.update(state => {
state.value = value state.value = value
state.error = error state.error = error
@ -115,15 +128,20 @@
return !error return !error
} }
// Clears the value of a certain field back to the initial value
const clearValue = () => { const clearValue = () => {
const { fieldState } = fieldMap[field] const { fieldState } = fieldMap[field]
const { defaultValue } = get(fieldState)
const newValue = initialValues[field] ?? defaultValue const newValue = initialValues[field] ?? defaultValue
// Update field state
fieldState.update(state => { fieldState.update(state => {
state.value = newValue state.value = newValue
state.error = null state.error = null
return state return state
}) })
// Update form state
formState.update(state => { formState.update(state => {
state.values = { ...state.values, [field]: newValue } state.values = { ...state.values, [field]: newValue }
delete state.errors[field] delete state.errors[field]
@ -132,9 +150,37 @@
}) })
} }
// Updates the validator rules for a certain field
const updateValidation = validationRules => {
const { fieldState } = fieldMap[field]
const { value, error } = get(fieldState)
// Create new validator
const schemaConstraints = schema?.[field]?.constraints
const validator = createValidatorFromConstraints(
schemaConstraints,
validationRules,
field,
table
)
// Update validator
fieldState.update(state => {
state.validator = validator
return state
})
// If there is currently an error, run the validator again in case
// the error should be cleared by the new validation rules
if (error) {
setValue(value, true)
}
}
return { return {
setValue, setValue,
clearValue, clearValue,
updateValidation,
validate: () => { validate: () => {
const { fieldState } = fieldMap[field] const { fieldState } = fieldMap[field]
setValue(get(fieldState).value, true) setValue(get(fieldState).value, true)
@ -143,13 +189,15 @@
} }
// Creates observable state data about a specific field // Creates observable state data about a specific field
const makeFieldState = (field, defaultValue, fieldDisabled) => { const makeFieldState = (field, validator, defaultValue, fieldDisabled) => {
return writable({ return writable({
field, field,
fieldId: `id-${generateID()}`, fieldId: `id-${generateID()}`,
value: initialValues[field] ?? defaultValue, value: initialValues[field] ?? defaultValue,
error: null, error: null,
disabled: fieldDisabled, disabled: fieldDisabled,
defaultValue,
validator,
}) })
} }

View File

@ -6,6 +6,7 @@
export let label export let label
export let placeholder export let placeholder
export let disabled = false export let disabled = false
export let validation
export let defaultValue = "" export let defaultValue = ""
let fieldState let fieldState
@ -16,6 +17,7 @@
{label} {label}
{field} {field}
{disabled} {disabled}
{validation}
{defaultValue} {defaultValue}
type="longform" type="longform"
bind:fieldState bind:fieldState

View File

@ -7,17 +7,66 @@
export let placeholder export let placeholder
export let disabled = false export let disabled = false
export let optionsType = "select" export let optionsType = "select"
export let validation
export let defaultValue export let defaultValue
export let optionsSource = "schema"
export let dataProvider
export let labelColumn
export let valueColumn
export let customOptions
let fieldState let fieldState
let fieldApi let fieldApi
let fieldSchema let fieldSchema
$: flatOptions = optionsSource == null || optionsSource === "schema"
$: options = getOptions(
optionsSource,
fieldSchema,
dataProvider,
labelColumn,
valueColumn
)
const getOptions = (
optionsSource,
fieldSchema,
dataProvider,
labelColumn,
valueColumn
) => {
// Take options from schema
if (optionsSource == null || optionsSource === "schema") {
return fieldSchema?.constraints?.inclusion ?? []
}
// Extract options from data provider
if (optionsSource === "provider" && valueColumn) {
let optionsSet = {}
dataProvider?.rows?.forEach(row => {
const value = row?.[valueColumn]
if (value) {
const label = row[labelColumn] || value
optionsSet[value] = { value, label }
}
})
return Object.values(optionsSet)
}
// Extract custom options
if (optionsSource === "custom" && customOptions) {
return customOptions
}
return []
}
</script> </script>
<Field <Field
{field} {field}
{label} {label}
{disabled} {disabled}
{validation}
{defaultValue} {defaultValue}
type="options" type="options"
bind:fieldState bind:fieldState
@ -31,9 +80,11 @@
id={$fieldState.fieldId} id={$fieldState.fieldId}
disabled={$fieldState.disabled} disabled={$fieldState.disabled}
error={$fieldState.error} error={$fieldState.error}
options={fieldSchema?.constraints?.inclusion ?? []} {options}
{placeholder} {placeholder}
on:change={e => fieldApi.setValue(e.detail)} on:change={e => fieldApi.setValue(e.detail)}
getOptionLabel={flatOptions ? x => x : x => x.label}
getOptionValue={flatOptions ? x => x : x => x.value}
/> />
{:else if optionsType === "radio"} {:else if optionsType === "radio"}
<RadioGroup <RadioGroup

View File

@ -9,6 +9,7 @@
export let label export let label
export let placeholder export let placeholder
export let disabled = false export let disabled = false
export let validation
let fieldState let fieldState
let fieldApi let fieldApi
@ -64,6 +65,7 @@
{label} {label}
{field} {field}
{disabled} {disabled}
{validation}
type="link" type="link"
bind:fieldState bind:fieldState
bind:fieldApi bind:fieldApi

View File

@ -7,6 +7,7 @@
export let placeholder export let placeholder
export let type = "text" export let type = "text"
export let disabled = false export let disabled = false
export let validation
export let defaultValue = "" export let defaultValue = ""
let fieldState let fieldState
@ -17,6 +18,7 @@
{label} {label}
{field} {field}
{disabled} {disabled}
{validation}
{defaultValue} {defaultValue}
type={type === "number" ? "number" : "string"} type={type === "number" ? "number" : "string"}
bind:fieldState bind:fieldState

View File

@ -1,54 +1,108 @@
import flatpickr from "flatpickr" import flatpickr from "flatpickr"
export const createValidatorFromConstraints = (constraints, field, table) => { /**
let checks = [] * Creates a validation function from a combination of schema-level constraints
* and custom validation rules
* @param schemaConstraints any schema level constraints from the table
* @param customRules any custom validation rules
* @param field the field name we are evaluating
* @param table the definition of the table we are evaluating
* @returns {function} a validator function which accepts test values
*/
export const createValidatorFromConstraints = (
schemaConstraints,
customRules,
field,
table
) => {
let rules = []
if (constraints) { // Convert schema constraints into validation rules
if (schemaConstraints) {
// Required constraint // Required constraint
if ( if (
field === table?.primaryDisplay || field === table?.primaryDisplay ||
constraints.presence?.allowEmpty === false schemaConstraints.presence?.allowEmpty === false
) { ) {
checks.push(presenceConstraint) rules.push({
type: "string",
constraint: "required",
error: "Required",
})
} }
// String length constraint // String length constraint
if (exists(constraints.length?.maximum)) { if (exists(schemaConstraints.length?.maximum)) {
const length = constraints.length.maximum const length = schemaConstraints.length.maximum
checks.push(lengthConstraint(length)) rules.push({
type: "string",
constraint: "length",
value: length,
error: `Maximum length is ${length}`,
})
} }
// Min / max number constraint // Min / max number constraint
if (exists(constraints.numericality?.greaterThanOrEqualTo)) { if (exists(schemaConstraints.numericality?.greaterThanOrEqualTo)) {
const min = constraints.numericality.greaterThanOrEqualTo const min = schemaConstraints.numericality.greaterThanOrEqualTo
checks.push(numericalConstraint(x => x >= min, `Minimum value is ${min}`)) rules.push({
type: "number",
constraint: "minValue",
value: min,
error: `Minimum value is ${min}`,
})
} }
if (exists(constraints.numericality?.lessThanOrEqualTo)) { if (exists(schemaConstraints.numericality?.lessThanOrEqualTo)) {
const max = constraints.numericality.lessThanOrEqualTo const max = schemaConstraints.numericality.lessThanOrEqualTo
checks.push(numericalConstraint(x => x <= max, `Maximum value is ${max}`)) rules.push({
type: "number",
constraint: "maxValue",
value: max,
error: `Maximum value is ${max}`,
})
} }
// Inclusion constraint // Inclusion constraint
if (exists(constraints.inclusion)) { if (exists(schemaConstraints.inclusion)) {
const options = constraints.inclusion const options = schemaConstraints.inclusion || []
checks.push(inclusionConstraint(options)) rules.push({
type: "string",
constraint: "inclusion",
value: options,
error: "Invalid value",
})
} }
// Date constraint // Date constraint
if (exists(constraints.datetime?.earliest)) { if (exists(schemaConstraints.datetime?.earliest)) {
const limit = constraints.datetime.earliest const limit = schemaConstraints.datetime.earliest
checks.push(dateConstraint(limit, true)) const limitString = flatpickr.formatDate(new Date(limit), "F j Y, H:i")
rules.push({
type: "datetime",
constraint: "minValue",
value: limit,
error: `Earliest date is ${limitString}`,
})
} }
if (exists(constraints.datetime?.latest)) { if (exists(schemaConstraints.datetime?.latest)) {
const limit = constraints.datetime.latest const limit = schemaConstraints.datetime.latest
checks.push(dateConstraint(limit, false)) const limitString = flatpickr.formatDate(new Date(limit), "F j Y, H:i")
rules.push({
type: "datetime",
constraint: "maxValue",
value: limit,
error: `Latest date is ${limitString}`,
})
} }
} }
// Add custom validation rules
rules = rules.concat(customRules || [])
// Evaluate each constraint // Evaluate each constraint
return value => { return value => {
for (let check of checks) { for (let rule of rules) {
const error = check(value) const error = evaluateRule(rule, value)
if (error) { if (error) {
return error return error
} }
@ -57,61 +111,197 @@ export const createValidatorFromConstraints = (constraints, field, table) => {
} }
} }
const exists = value => value != null && value !== "" /**
* Evaluates a validation rule against a value and optionally returns
const presenceConstraint = value => { * an error if the validation fails.
let invalid * @param rule the rule object to evaluate
if (Array.isArray(value)) { * @param value the value to validate against
invalid = value.length === 0 * @returns {null|*} an error if validation fails or null if it passes
} else { */
invalid = value == null || value === "" const evaluateRule = (rule, value) => {
} if (!rule) {
return invalid ? "Required" : null
}
const lengthConstraint = maxLength => value => {
if (value && value.length > maxLength) {
return `Maximum ${maxLength} characters`
}
return null return null
} }
const numericalConstraint = (constraint, error) => value => { // Determine the correct handler for this rule
if (value == null || value === "") { const handler = handlerMap[rule.constraint]
if (!handler) {
return null return null
} }
// Coerce input value into correct type
value = parseType(value, rule.type)
// Evaluate the rule
const pass = handler(value, rule)
return pass ? null : rule.error || "Error"
}
/**
* Parses a value to the specified type so that values are always compared
* in the same format.
* @param value the value to parse
* @param type the type to parse
* @returns {boolean|string|*|number|null} the parsed value, or null if invalid
*/
const parseType = (value, type) => {
// Treat nulls or empty strings as null
if (!exists(value) || !type) {
return null
}
// Parse as string
if (type === "string") {
if (typeof value === "string" || Array.isArray(value)) {
return value
}
if (value.length === 0) {
return null
}
return `${value}`
}
// Parse as number
if (type === "number") {
if (isNaN(value)) { if (isNaN(value)) {
return "Must be a number" return null
} }
const number = parseFloat(value) return parseFloat(value)
if (!constraint(number)) {
return error
} }
// Parse as date
if (type === "datetime") {
if (value instanceof Date) {
return value.getTime()
}
const time = isNaN(value) ? Date.parse(value) : new Date(value).getTime()
return isNaN(time) ? null : time
}
// Parse as boolean
if (type === "boolean") {
if (typeof value === "string") {
return value.toLowerCase() === "true"
}
return value === true
}
// Parse attachments, treating no elements as null
if (type === "attachment") {
if (!Array.isArray(value) || !value.length) {
return null
}
return value
}
// Parse links, treating no elements as null
if (type === "link") {
if (!Array.isArray(value) || !value.length) {
return null
}
return value
}
// If some unknown type, treat as null to avoid breaking validators
return null return null
} }
const inclusionConstraint = // Evaluates a required constraint
(options = []) => const requiredHandler = value => {
value => { return value != null
if (value == null || value === "") {
return null
}
if (!options.includes(value)) {
return "Invalid value"
}
return null
} }
const dateConstraint = (dateString, isEarliest) => { // Evaluates a min length constraint
const dateLimit = Date.parse(dateString) const minLengthHandler = (value, rule) => {
return value => { const limit = parseType(rule.value, "number")
if (value == null || value === "") { return value && value.length >= limit
return null
} }
const dateValue = Date.parse(value)
const valid = isEarliest ? dateValue >= dateLimit : dateValue <= dateLimit // Evaluates a max length constraint
const adjective = isEarliest ? "Earliest" : "Latest" const maxLengthHandler = (value, rule) => {
const limitString = flatpickr.formatDate(new Date(dateLimit), "F j Y, H:i") const limit = parseType(rule.value, "number")
return valid ? null : `${adjective} is ${limitString}` return value == null || value.length <= limit
} }
// Evaluates a min value constraint
const minValueHandler = (value, rule) => {
// Use same type as the value so that things can be compared
const limit = parseType(rule.value, rule.type)
return value && value >= limit
}
// Evaluates a max value constraint
const maxValueHandler = (value, rule) => {
// Use same type as the value so that things can be compared
const limit = parseType(rule.value, rule.type)
return value == null || value <= limit
}
// Evaluates an inclusion constraint
const inclusionHandler = (value, rule) => {
return value == null || rule.value.includes(value)
}
// Evaluates an equal constraint
const equalHandler = (value, rule) => {
const ruleValue = parseType(rule.value, rule.type)
return value === ruleValue
}
// Evaluates a not equal constraint
const notEqualHandler = (value, rule) => {
const ruleValue = parseType(rule.value, rule.type)
if (value == null && ruleValue == null) {
return true
}
return value !== ruleValue
}
// Evaluates a regex constraint
const regexHandler = (value, rule) => {
const regex = parseType(rule.value, "string")
return new RegExp(regex).test(value)
}
// Evaluates a not regex constraint
const notRegexHandler = (value, rule) => {
return !regexHandler(value, rule)
}
// Evaluates a contains constraint
const containsHandler = (value, rule) => {
const expectedValue = parseType(rule.value, "string")
return value && value.includes(expectedValue)
}
// Evaluates a not contains constraint
const notContainsHandler = (value, rule) => {
return !containsHandler(value, rule)
}
/**
* Map of constraint types to handlers.
*/
const handlerMap = {
required: requiredHandler,
minLength: minLengthHandler,
maxLength: maxLengthHandler,
minValue: minValueHandler,
maxValue: maxValueHandler,
inclusion: inclusionHandler,
equal: equalHandler,
notEqual: notEqualHandler,
regex: regexHandler,
notRegex: notRegexHandler,
contains: containsHandler,
notContains: notContainsHandler,
}
/**
* Helper to check for null, undefined or empty string values
* @param value the value to test
* @returns {boolean} whether the value exists or not
*/
const exists = value => {
return value != null && value !== ""
} }

View File

@ -2,59 +2,6 @@
# yarn lockfile v1 # yarn lockfile v1
"@adobe/spectrum-css-workflow-icons@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@adobe/spectrum-css-workflow-icons/-/spectrum-css-workflow-icons-1.2.1.tgz#7e2cb3fcfb5c8b12d7275afafbb6ec44913551b4"
integrity sha512-uVgekyBXnOVkxp+CUssjN/gefARtudZC8duEn1vm0lBQFwGRZFlDEzU1QC+aIRWCrD1Z8OgRpmBYlSZ7QS003w==
"@budibase/bbui@^0.9.105-alpha.3":
version "0.9.105-alpha.3"
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.105-alpha.3.tgz#19a773df87511fe58ff347d0499f368bc777d50e"
integrity sha512-Xm3jT89COeYu9JXb3woZXov9ae9mNHBD5YRxPTs7jyjcD2obyfIt/q6TIPvl9x4qkCi8v5fWiXpBv0cEqR8u/A==
dependencies:
"@adobe/spectrum-css-workflow-icons" "^1.2.1"
"@spectrum-css/actionbutton" "^1.0.1"
"@spectrum-css/actiongroup" "^1.0.1"
"@spectrum-css/avatar" "^3.0.2"
"@spectrum-css/button" "^3.0.1"
"@spectrum-css/buttongroup" "^3.0.2"
"@spectrum-css/checkbox" "^3.0.2"
"@spectrum-css/dialog" "^3.0.1"
"@spectrum-css/divider" "^1.0.1"
"@spectrum-css/dropzone" "^3.0.2"
"@spectrum-css/fieldgroup" "^3.0.2"
"@spectrum-css/fieldlabel" "^3.0.1"
"@spectrum-css/icon" "^3.0.1"
"@spectrum-css/illustratedmessage" "^3.0.2"
"@spectrum-css/inputgroup" "^3.0.2"
"@spectrum-css/label" "^2.0.10"
"@spectrum-css/link" "^3.1.1"
"@spectrum-css/menu" "^3.0.1"
"@spectrum-css/modal" "^3.0.1"
"@spectrum-css/pagination" "^3.0.3"
"@spectrum-css/picker" "^1.0.1"
"@spectrum-css/popover" "^3.0.1"
"@spectrum-css/progressbar" "^1.0.2"
"@spectrum-css/progresscircle" "^1.0.2"
"@spectrum-css/radio" "^3.0.2"
"@spectrum-css/search" "^3.0.2"
"@spectrum-css/sidenav" "^3.0.2"
"@spectrum-css/statuslight" "^3.0.2"
"@spectrum-css/switch" "^1.0.2"
"@spectrum-css/table" "^3.0.1"
"@spectrum-css/tabs" "^3.0.1"
"@spectrum-css/tags" "^3.0.2"
"@spectrum-css/textfield" "^3.0.1"
"@spectrum-css/toast" "^3.0.1"
"@spectrum-css/tooltip" "^3.0.3"
"@spectrum-css/treeview" "^3.0.2"
"@spectrum-css/typography" "^3.0.1"
"@spectrum-css/underlay" "^2.0.9"
"@spectrum-css/vars" "^3.0.1"
dayjs "^1.10.4"
svelte-flatpickr "^3.1.0"
svelte-portal "^1.0.0"
"@rollup/pluginutils@^4.1.1": "@rollup/pluginutils@^4.1.1":
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.1.1.tgz#1d4da86dd4eded15656a57d933fda2b9a08d47ec" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.1.1.tgz#1d4da86dd4eded15656a57d933fda2b9a08d47ec"
@ -63,103 +10,28 @@
estree-walker "^2.0.1" estree-walker "^2.0.1"
picomatch "^2.2.2" picomatch "^2.2.2"
"@spectrum-css/actionbutton@^1.0.1": "@spectrum-css/button@^3.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/actionbutton/-/actionbutton-1.0.3.tgz#8f7342a69b303c5acdcfa0a59f5e9267b9f3cb30"
integrity sha512-P9qoCPSiZ1SB6ZYqK5hub0vGty00YYqonQE0KTjtb1i+T1nYR/87vWqVPERx9j63uhgZncjwFYaThTvRqye7eQ==
"@spectrum-css/actiongroup@^1.0.1":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/actiongroup/-/actiongroup-1.0.3.tgz#4713ce65e6f5c72c404a7b638fbc3b4fd7e3874f"
integrity sha512-NlB9Q4ZlWixXxymoPIYG6g2hkwAGKFodHWPFfxHD8ddkjXWRB9G2akUP7cfsJ4DcYn4VisUORCOYQwqDiSmboQ==
"@spectrum-css/avatar@^3.0.2":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/avatar/-/avatar-3.0.2.tgz#4f1826223eae330e64b6d3cc899e9bc2e98dac95"
integrity sha512-wEczvSqxttTWSiL3cOvXV/RmGRwSkw2w6+slcHhnf0kb7ovymMM+9oz8vvEpEsSeo5u598bc+7ktrKFpAd6soQ==
"@spectrum-css/button@^3.0.1":
version "3.0.3" version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.3.tgz#2df1efaab6c7e0b3b06cb4b59e1eae59c7f1fc84" resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.3.tgz#2df1efaab6c7e0b3b06cb4b59e1eae59c7f1fc84"
integrity sha512-6CnLPqqtaU/PcSSIGeGRi0iFIIxIUByYLKFO6zn5NEUc12KQ28dJ4PLwB6WBa0L8vRoAGlnWWH2ZZweTijbXgg== integrity sha512-6CnLPqqtaU/PcSSIGeGRi0iFIIxIUByYLKFO6zn5NEUc12KQ28dJ4PLwB6WBa0L8vRoAGlnWWH2ZZweTijbXgg==
"@spectrum-css/buttongroup@^3.0.2":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/buttongroup/-/buttongroup-3.0.3.tgz#719d868845ac9d2c4f939c1b9f6044507902d5aa"
integrity sha512-eXl8U4QWMWXqyTu654FdQdEGnmczgOYlpIFSHyCMVjhtPqZp2xwnLFiGh6LKw+bLio6eeOZ0L+vpk1GcoYqgkw==
"@spectrum-css/card@^3.0.3": "@spectrum-css/card@^3.0.3":
version "3.0.3" version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/card/-/card-3.0.3.tgz#56b2e2da6b80c1583228baa279de7407383bfb6b" resolved "https://registry.yarnpkg.com/@spectrum-css/card/-/card-3.0.3.tgz#56b2e2da6b80c1583228baa279de7407383bfb6b"
integrity sha512-+oKLUI2a0QmQP9EzySeq/G4FpUkkdaDNbuEbqCj2IkPMc/2v/nwzsPhh1fj2UIghGAiiUwXfPpzax1e8fyhQUg== integrity sha512-+oKLUI2a0QmQP9EzySeq/G4FpUkkdaDNbuEbqCj2IkPMc/2v/nwzsPhh1fj2UIghGAiiUwXfPpzax1e8fyhQUg==
"@spectrum-css/checkbox@^3.0.2": "@spectrum-css/divider@^1.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/checkbox/-/checkbox-3.0.3.tgz#8577067fc8b97e4609f92bd242364937a533a7bb"
integrity sha512-QVG9uMHq+lh70Dh6mDNnY+vEvNz2p7VC6xgLfDYfijp2qeiqYPq72fQK6p/SiyqPk96ZACzNRwgeROU6Xf6Wgg==
"@spectrum-css/dialog@^3.0.1":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/dialog/-/dialog-3.0.3.tgz#7715a4ea435e753afb623d99ca5917ed1bcd6f34"
integrity sha512-AhmKgfRIVyTe3ABiJ8lLUQL34VB/H6fN16na2LlbDRJvyRMzkdN1Xf2i6U3f4OMd3qQ8Gm5xat4BvMxIQPBAUQ==
"@spectrum-css/divider@^1.0.1", "@spectrum-css/divider@^1.0.3":
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/divider/-/divider-1.0.3.tgz#639e2ebaa0834efa40f42397668bbd5c153ea385" resolved "https://registry.yarnpkg.com/@spectrum-css/divider/-/divider-1.0.3.tgz#639e2ebaa0834efa40f42397668bbd5c153ea385"
integrity sha512-Zy4Rn40w8UtzMh3wx/U9+CepSCpm1aOCGftHgWDub0XZuVTzh0c1WwyzTuLCx2Hf21z5VRGNiDh8bGEEzSbtNA== integrity sha512-Zy4Rn40w8UtzMh3wx/U9+CepSCpm1aOCGftHgWDub0XZuVTzh0c1WwyzTuLCx2Hf21z5VRGNiDh8bGEEzSbtNA==
dependencies: dependencies:
"@spectrum-css/vars" "^3.0.2" "@spectrum-css/vars" "^3.0.2"
"@spectrum-css/dropzone@^3.0.2": "@spectrum-css/link@^3.1.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/dropzone/-/dropzone-3.0.3.tgz#aee71697a2c195947599d7541b858c3c198741dc"
integrity sha512-ujrswdtB6bHigklyHsm6zomFd6rUnKJ3xVVRjroVF4+ouK4DxK5tX0iVd0EW6AOfOjx4Cc28uyFot3fpxp+MQw==
"@spectrum-css/fieldgroup@^3.0.2":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/fieldgroup/-/fieldgroup-3.0.3.tgz#85d85da048d08200f25ceab378026dd2b11e0bb2"
integrity sha512-wXUXTXN1CPnR7M4Ltd+3uh7BfcNGUV1+Xe0/h0tCpq/j+S2Sd4xo7/pUMdU19sIDrAAtpEFp1tt+nouHcU5HGQ==
"@spectrum-css/fieldlabel@^3.0.1":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/fieldlabel/-/fieldlabel-3.0.3.tgz#f73c04d20734d4718ffb620dc46458904685b449"
integrity sha512-nEvIkEXCD5n4fW67Unq6Iu7VXoauEd/JGpfTY02VsC5p4FJLnwKfPDbJUuUsqClAxqw7nAsmXVKtn4zQFf5yPQ==
"@spectrum-css/icon@^3.0.1":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/icon/-/icon-3.0.3.tgz#5c612822380927087aebd526855d82ed2c3e2cba"
integrity sha512-hyloKOejPCXhP3MBNsm3SjR9j8xT1R1S19p32KW/0Qhj+VMUtfyEPmevyFptpn7wcovQccdl/vZVIVDuML/imw==
"@spectrum-css/illustratedmessage@^3.0.2":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/illustratedmessage/-/illustratedmessage-3.0.2.tgz#6a480be98b027e050b086e7899e40d87adb0a8c0"
integrity sha512-dqnE8X27bGcO0HN8+dYx8O4o0dNNIAqeivOzDHhe2El+V4dTzMrNIerF6G0NLm3GjVf6XliwmitsZK+K6FmbtA==
"@spectrum-css/inputgroup@^3.0.2":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/inputgroup/-/inputgroup-3.0.3.tgz#00c9a370ddc2c55cf0f37dd6069faa9501fd7eb5"
integrity sha512-FqRJTiLL7jiGfzDVXWUGVLqHryJjCcqQIrqAi+Tq0oenapzsBe2qc/zIrKgh2wtMI+NTIBLXTECvij3L1HlqAg==
"@spectrum-css/label@^2.0.10":
version "2.0.10"
resolved "https://registry.yarnpkg.com/@spectrum-css/label/-/label-2.0.10.tgz#2368651d7636a19385b5d300cdf6272db1916001"
integrity sha512-xCbtEiQkZIlLdWFikuw7ifDCC21DOC/KMgVrrVJHXFc4KRQe9LTZSqmGF3tovm+CSq1adE59mYoTbojVQ9YuEQ==
"@spectrum-css/link@^3.1.1", "@spectrum-css/link@^3.1.3":
version "3.1.3" version "3.1.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/link/-/link-3.1.3.tgz#b0e560a7e0acdb4a2b329b6669696aa3438f5993" resolved "https://registry.yarnpkg.com/@spectrum-css/link/-/link-3.1.3.tgz#b0e560a7e0acdb4a2b329b6669696aa3438f5993"
integrity sha512-8Pmy5d73MwKcATTPaj+fSrZYnIw7GmfX40AvpC1xx5LauOxsbUb4AVNp1kn2H8rrOBmxF7czyhoBBhEiv66QUg== integrity sha512-8Pmy5d73MwKcATTPaj+fSrZYnIw7GmfX40AvpC1xx5LauOxsbUb4AVNp1kn2H8rrOBmxF7czyhoBBhEiv66QUg==
"@spectrum-css/menu@^3.0.1":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/menu/-/menu-3.0.3.tgz#46a9b221bb5f470a2f8a934bdfd512d84d2fdc4d"
integrity sha512-qKA9J/MrikNKIpCEHsAazG2vY3im5tjGCmo6p9Pdnu8/aQMsiuZDHZayukeCttJUZkrb9guDVL9OIHlK5RZvcQ==
"@spectrum-css/modal@^3.0.1":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/modal/-/modal-3.0.2.tgz#58b6621cab65f90788d310374f40df1f7090473f"
integrity sha512-YnIivJhoaao7Otu+HV7sgebPyFbO6sd/oMvTN/Rb2wwgnaMnIIuIRdGandSrcgotN2uNgs+P0knG6mv/xA1/dg==
"@spectrum-css/page@^3.0.1": "@spectrum-css/page@^3.0.1":
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/page/-/page-3.0.2.tgz#8f0c03d25f5565fb13115541a8fcaf0e1d3a8ee0" resolved "https://registry.yarnpkg.com/@spectrum-css/page/-/page-3.0.2.tgz#8f0c03d25f5565fb13115541a8fcaf0e1d3a8ee0"
@ -167,101 +39,11 @@
dependencies: dependencies:
"@spectrum-css/vars" "^3.0.2" "@spectrum-css/vars" "^3.0.2"
"@spectrum-css/pagination@^3.0.3": "@spectrum-css/typography@^3.0.2":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/pagination/-/pagination-3.0.3.tgz#b204c3ada384c4af751a354bc428346d82eeea65"
integrity sha512-OJ/v9GeNXJOZ9Yr9LDBYPrR2NCiLOWP9wANT/a5sqFuugRnQbn/HYMnRp9TBxwpDY6ihaPo0T/wi7kLiAJFdDw==
"@spectrum-css/picker@^1.0.1":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/picker/-/picker-1.0.3.tgz#21379bcf8ae94277deeb6ad65dcd9e2bbfacb487"
integrity sha512-oHLGxBx5BwVCSGo7/T1C9PTHX1+/5AmVjyLiTJ4UoIdSJmOERw9YcRZbcGZgBJNWbxcjr4TyGtwj1EcSjEy97w==
"@spectrum-css/popover@^3.0.1":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/popover/-/popover-3.0.3.tgz#6fb69873474fb968afb738eacb9e121f93e83a09"
integrity sha512-KvmXv4TV19FBx39KfmgVlDYtvtBqv/8RRK7RRLDDHGViTxZtShjVsVpwIgfkfgn4iJztCnXpWzFqRXWUu2XCpQ==
"@spectrum-css/progressbar@^1.0.2":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/progressbar/-/progressbar-1.0.3.tgz#f70bcc38a2a21cff2f422ec825724ebbb9455e67"
integrity sha512-vJHplefUuy8+NjCw1X7fLbqHVGNVBpvGFXNAeaIj4SFf4ygxiUq/5c9iRhhsCQixEsJlfD/b7BnGXU7BUDkr6Q==
"@spectrum-css/progresscircle@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/progresscircle/-/progresscircle-1.0.2.tgz#258ea9170fb70f795edda03e38a61d93bef4487c"
integrity sha512-JLULpyzjIY95lzlWR1yE1gv4l1K6p+scQ+edmuZZUHBzwM3pUtkvHJmUlA9TYdResUYW6Uka60VRdY6lZ8gnFQ==
"@spectrum-css/radio@^3.0.2":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/radio/-/radio-3.0.3.tgz#25c3bc5e9c30a8a8ae728717b7c7fb736cdae640"
integrity sha512-LaLGfz/eGNR2iyqouXYILIA+pKRqF769iPdwM0REm5RpWvMQDD7rPZ/kWlg18owjaFsyllEp25gEjmhRJIIVOw==
"@spectrum-css/search@^3.0.2":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/search/-/search-3.0.3.tgz#3415dc106aca0d5dd996e87084a1b47c2b95a882"
integrity sha512-kdLpKTt0obljuhS1q1tukujRlvSs8sBwij76D4Qp8KpMzwePfZyvv1kYzuWPNZfTeISxWsmyZ6Wxd1uvzjn+UA==
"@spectrum-css/sidenav@^3.0.2":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/sidenav/-/sidenav-3.0.3.tgz#132141fbd2500a927c312fa4e1d712c438b3d597"
integrity sha512-cQ+CgwjxGftrcc79i1XnGd66QTl7H7zopSU0UTV4Qq7hvqfrjjWxfZ6b+3tezrrhNlDope1ff9o8sm67PsPXtg==
"@spectrum-css/statuslight@^3.0.2":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/statuslight/-/statuslight-3.0.2.tgz#dc54b6cd113413dcdb909c486b5d7bae60db65c5"
integrity sha512-xodB8g8vGJH20XmUj9ZsPlM1jHrGeRbvmVXkz0q7YvQrYAhim8pP3W+XKKZAletPFAuu8cmUOc6SWn6i4X4z6w==
"@spectrum-css/switch@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/switch/-/switch-1.0.2.tgz#f0b4c69271964573e02b08e90998096e49e1de44"
integrity sha512-zqmHpgWPNg1gEwdUNFYV3CBX5JaeALfIqcJIxE0FLZqr9d1C4+oLE0ItIFzt1bwr4bFAOmkEpvtiY+amluzGxQ==
"@spectrum-css/table@^3.0.1":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/table/-/table-3.0.3.tgz#7f7f19905ef3275cbf907ce3a5818e63c30b2caf"
integrity sha512-nxwzVjLPsXoY/v4sdxOVYLcC+cEbGgJyLcLclT5LT9MGSbngFeUMJzzVR4EvehzuN4dH7hrATG7Mbuq29Mf0Hg==
"@spectrum-css/tabs@^3.0.1":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/tabs/-/tabs-3.0.3.tgz#51dd6f168c897b0fdc3a7e9f901df7bd2288b4fc"
integrity sha512-iLP1I72bJWz9APdQB1jiw+pOv5a7N+hYOCJvRoc56Us/hJKVzowkyGRe3uH+8v36nCG9bHxiAQNLoU8eXisVrg==
"@spectrum-css/tags@^3.0.2":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/tags/-/tags-3.0.3.tgz#fc76d2735cdc442de91b7eb3bee49a928c0767ac"
integrity sha512-SL8vPxVDfWcY5VdIuyl0TImEXcOU1I7yCyXkk7MudMwfnYs81FaIyY32hFV9OHj0Tz/36UzRzc7AVMSuRQ53pw==
"@spectrum-css/textfield@^3.0.1":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/textfield/-/textfield-3.0.2.tgz#907f62d2dc82852dd6236a820be99e252b531631"
integrity sha512-nkFgAb0cP4jUodkUBErMNfyF78jJLtgL1Mrr9/rvGpGobo10IAbb8zZY4CkZ64o8XmMy/85+wZTKcx+KHatqpg==
"@spectrum-css/toast@^3.0.1":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/toast/-/toast-3.0.3.tgz#97c1527384707600832ecda35643ed304615250f"
integrity sha512-CjLeaMs+cjUXojCCRtbj0YkD2BoZW16kjj2o5omkEpUTjA34IJ8xJ1a+CCtDILWekhXvN0MBN4sbumcnwcnx8w==
"@spectrum-css/tooltip@^3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/tooltip/-/tooltip-3.0.3.tgz#26b8ca3b3d30e29630244d85eb4fc11d0c841281"
integrity sha512-ztRF7WW1FzyNavXBRc+80z67UoOrY9wl3cMYsVD3MpDnyxdzP8cjza1pCcolKBaFqRTcQKkxKw3GWtGICRKR5A==
"@spectrum-css/treeview@^3.0.2":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/treeview/-/treeview-3.0.3.tgz#aeda5175158b9f8d7529cb2b394428eb2a428046"
integrity sha512-D5gGzZC/KtRArdx86Mesc9+99W9nTbUOeyYGqoJoAfJSOttoT6Tk5CrDvlCmAqjKf5rajemAkGri1ChqvUIwkw==
"@spectrum-css/typography@^3.0.1", "@spectrum-css/typography@^3.0.2":
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/typography/-/typography-3.0.2.tgz#ea3ca0a60e18064527819d48c8c4364cab4fcd38" resolved "https://registry.yarnpkg.com/@spectrum-css/typography/-/typography-3.0.2.tgz#ea3ca0a60e18064527819d48c8c4364cab4fcd38"
integrity sha512-5ZOLmQe0edzsDMyhghUd4hBb5uxGsFrxzf+WasfcUw9klSfTsRZ09n1BsaaWbgrLjlMQ+EEHS46v5VNo0Ms2CA== integrity sha512-5ZOLmQe0edzsDMyhghUd4hBb5uxGsFrxzf+WasfcUw9klSfTsRZ09n1BsaaWbgrLjlMQ+EEHS46v5VNo0Ms2CA==
"@spectrum-css/underlay@^2.0.9":
version "2.0.10"
resolved "https://registry.yarnpkg.com/@spectrum-css/underlay/-/underlay-2.0.10.tgz#8b75b646605a311850f6620caa18d4996cd64ed7"
integrity sha512-PmsmkzeGD/rY4pp3ILXHt9w8BW7uaEqXe08hQRS7rGki7wqCpG4mE0/8N3yEcA3QxWQclmG9gdkg5uz6wMmYzA==
"@spectrum-css/vars@^3.0.1", "@spectrum-css/vars@^3.0.2": "@spectrum-css/vars@^3.0.1", "@spectrum-css/vars@^3.0.2":
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-3.0.2.tgz#ea9062c3c98dfc6ba59e5df14a03025ad8969999" resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-3.0.2.tgz#ea9062c3c98dfc6ba59e5df14a03025ad8969999"
@ -296,7 +78,7 @@ colorette@^1.2.2:
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
dayjs@^1.10.4, dayjs@^1.10.5: dayjs@^1.10.5:
version "1.10.6" version "1.10.6"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.6.tgz#288b2aa82f2d8418a6c9d4df5898c0737ad02a63" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.6.tgz#288b2aa82f2d8418a6c9d4df5898c0737ad02a63"
integrity sha512-AztC/IOW4L1Q41A86phW5Thhcrco3xuAA+YX/BLpLWWjRcTj5TOt/QImBLmCKlrF7u7k47arTnOyL6GnbG8Hvw== integrity sha512-AztC/IOW4L1Q41A86phW5Thhcrco3xuAA+YX/BLpLWWjRcTj5TOt/QImBLmCKlrF7u7k47arTnOyL6GnbG8Hvw==
@ -437,11 +219,6 @@ svelte-hmr@^0.14.7:
resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.14.7.tgz#7fa8261c7b225d9409f0a86f3b9ea5c3ca6f6607" resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.14.7.tgz#7fa8261c7b225d9409f0a86f3b9ea5c3ca6f6607"
integrity sha512-pDrzgcWSoMaK6AJkBWkmgIsecW0GChxYZSZieIYfCP0v2oPyx2CYU/zm7TBIcjLVUPP714WxmViE9Thht4etog== integrity sha512-pDrzgcWSoMaK6AJkBWkmgIsecW0GChxYZSZieIYfCP0v2oPyx2CYU/zm7TBIcjLVUPP714WxmViE9Thht4etog==
svelte-portal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-1.0.0.tgz#36a47c5578b1a4d9b4dc60fa32a904640ec4cdd3"
integrity sha512-nHf+DS/jZ6jjnZSleBMSaZua9JlG5rZv9lOGKgJuaZStfevtjIlUJrkLc3vbV8QdBvPPVmvcjTlazAzfKu0v3Q==
svelte@^3.38.2: svelte@^3.38.2:
version "3.42.1" version "3.42.1"
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.42.1.tgz#d4b4d9068cce911835f7c6b8ff1d290e1a0b5657" resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.42.1.tgz#d4b4d9068cce911835f7c6b8ff1d290e1a0b5657"

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/string-templates", "name": "@budibase/string-templates",
"version": "0.9.105-alpha.5", "version": "0.9.105-alpha.16",
"description": "Handlebars wrapper for Budibase templating.", "description": "Handlebars wrapper for Budibase templating.",
"main": "src/index.cjs", "main": "src/index.cjs",
"module": "dist/bundle.mjs", "module": "dist/bundle.mjs",

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/worker", "name": "@budibase/worker",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "0.9.105-alpha.5", "version": "0.9.105-alpha.16",
"description": "Budibase background service", "description": "Budibase background service",
"main": "src/index.js", "main": "src/index.js",
"repository": { "repository": {
@ -23,8 +23,8 @@
"author": "Budibase", "author": "Budibase",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@budibase/auth": "^0.9.105-alpha.5", "@budibase/auth": "^0.9.105-alpha.16",
"@budibase/string-templates": "^0.9.105-alpha.5", "@budibase/string-templates": "^0.9.105-alpha.16",
"@koa/router": "^8.0.0", "@koa/router": "^8.0.0",
"@techpass/passport-openidconnect": "^0.3.0", "@techpass/passport-openidconnect": "^0.3.0",
"aws-sdk": "^2.811.0", "aws-sdk": "^2.811.0",

File diff suppressed because it is too large Load Diff