Merge remote-tracking branch 'origin/develop' into pc-bug-fixes
This commit is contained in:
commit
fc0d621124
|
@ -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!
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "0.9.105-alpha.5",
|
"version": "0.9.105-alpha.16",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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: "",
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
<script>
|
|
||||||
import FormFieldSelect from "./FormFieldSelect.svelte"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<FormFieldSelect {...$$props} on:change type="attachment" />
|
|
|
@ -1,5 +0,0 @@
|
||||||
<script>
|
|
||||||
import FormFieldSelect from "./FormFieldSelect.svelte"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<FormFieldSelect {...$$props} on:change type="boolean" />
|
|
|
@ -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);
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
<script>
|
|
||||||
import FormFieldSelect from "./FormFieldSelect.svelte"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<FormFieldSelect {...$$props} on:change type="datetime" />
|
|
|
@ -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>
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
|
@ -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])
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
<script>
|
|
||||||
import FormFieldSelect from "./FormFieldSelect.svelte"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<FormFieldSelect {...$$props} on:change type="longform" />
|
|
|
@ -1,5 +0,0 @@
|
||||||
<script>
|
|
||||||
import FormFieldSelect from "./FormFieldSelect.svelte"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<FormFieldSelect {...$$props} on:change type="number" />
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -1,5 +0,0 @@
|
||||||
<script>
|
|
||||||
import FormFieldSelect from "./FormFieldSelect.svelte"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<FormFieldSelect {...$$props} on:change type="options" />
|
|
|
@ -1,5 +0,0 @@
|
||||||
<script>
|
|
||||||
import FormFieldSelect from "./FormFieldSelect.svelte"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<FormFieldSelect {...$$props} on:change type="link" />
|
|
|
@ -1,5 +0,0 @@
|
||||||
<script>
|
|
||||||
import FormFieldSelect from "./FormFieldSelect.svelte"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<FormFieldSelect {...$$props} on:change type="string" />
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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 => {
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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" },
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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]+$/
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
@ -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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}`
|
||||||
|
|
|
@ -25,4 +25,7 @@
|
||||||
.spectrum-Form {
|
.spectrum-Form {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
.spectrum-Form--labelsAbove {
|
||||||
|
gap: var(--spectrum-global-dimension-size-100);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 !== ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
Loading…
Reference in New Issue