merge
This commit is contained in:
commit
284474db4b
|
@ -0,0 +1,18 @@
|
|||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 60
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
- roadmap
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
|
@ -28,12 +28,17 @@ jobs:
|
|||
- run: yarn lint
|
||||
- run: yarn bootstrap
|
||||
- run: yarn build
|
||||
env:
|
||||
POSTHOG_TOKEN: ${{ secrets.POSTHOG_TOKEN }}
|
||||
POSTHOG_URL: ${{ secrets.POSTHOG_URL }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
- run: yarn test
|
||||
|
||||
- name: Prepare for app notarization (macOS)
|
||||
if: startsWith(matrix.os, 'macos')
|
||||
# Import Apple API key for app notarization on macOS
|
||||
run: |
|
||||
xattr -cr *
|
||||
mkdir -p ~/private_keys/
|
||||
echo '${{ secrets.api_key }}' > ~/private_keys/AuthKey_${{ secrets.api_key_id }}.p8
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"editor.formatOnSave": true,
|
||||
"eslint.format.enable": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": true
|
||||
},
|
||||
"[svelte]": {
|
||||
"editor.defaultFormatter": "JamesBirtles.svelte-vscode"
|
||||
},
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
|
@ -27,15 +27,15 @@ A client represents a single budibase customer. Each budibase client will have 1
|
|||
|
||||
### App
|
||||
|
||||
A client can have one or more budibase applications. Think of a budibase application as a tree. Budibase applications have one definition of what the front end will look like,
|
||||
A client can have one or more budibase applications. Budibase applications would be things like "Developer Inventory Management" or "Goat Herder CRM". Think of a budibase application as a tree.
|
||||
|
||||
### Database
|
||||
|
||||
An App can have one or more databases. Keeping with our [dendrology](https://en.wikipedia.org/wiki/Dendrology) analogy - think of an database as a branch on the tree. Databases are used to keep data separate for different instances of your app. For example, if you had a CRM app, you may create a database for your US office, and a database for your Australian office. Databases allow us to support [multitenancy](https://www.gartner.com/en/information-technology/glossary/multitenancy) in budibase applications.
|
||||
|
||||
### Model
|
||||
### Table
|
||||
|
||||
Models in budibase are almost akin to tables in relational databases. A model may be a "Car" or an "Employee". They are the main building blocks for the creation and management of backend data in budibase.
|
||||
Tables in budibase are almost akin to tables in relational databases. A table may be a "Car" or an "Employee". They are the main building blocks for the creation and management of backend data in budibase.
|
||||
|
||||
### View
|
||||
|
||||
|
@ -95,7 +95,7 @@ then `cd ` into your local copy.
|
|||
|
||||
### 4. Initialising Budibase and Creating a Budibase App
|
||||
|
||||
`yarn initialise` will initialise your budibase installation. A Budibase apps folder will have been created in `~/.budibase`.
|
||||
`yarn initialise` will initialise your budibase installation. A Budibase apps folder will have been created in `~/.budibase`. You can also just start up the budibase electron app and it should initialise budibase for you.
|
||||
|
||||
This is a blank apps folder, so you will need to create yourself an app.
|
||||
|
||||
|
@ -149,7 +149,25 @@ The backend schema, models and records are stored using PouchDB when developing
|
|||
|
||||
### Publishing Budibase to NPM
|
||||
|
||||
You can publish all the latest versions of the monorepo packages by running:
|
||||
#### Testing In Electron
|
||||
|
||||
At budibase, we pride ourselves on giving our users a fast, native and slick local development experience. As a result, we use the electron to provide a native GUI for the budibase builder. In order to release budibase out into the wild, you should test your changes in a packaged electron application. To do this, first build budibase from the root directory.
|
||||
```
|
||||
yarn build
|
||||
```
|
||||
|
||||
Now everything is built, you can package up your electron application.
|
||||
```
|
||||
cd packages/server
|
||||
yarn build:electron
|
||||
```
|
||||
|
||||
Your new electron application will be stored in `packages/server/dist/<operating-system>`. Open up the executable and make sure everything is working smoothly.
|
||||
|
||||
|
||||
#### Publishing to NPM
|
||||
|
||||
Once you are happy that your changes work in electron, you can publish all the latest versions of the monorepo packages by running:
|
||||
|
||||
```
|
||||
yarn publishnpm
|
||||
|
@ -157,6 +175,10 @@ yarn publishnpm
|
|||
|
||||
from your root directory.
|
||||
|
||||
#### CI Release
|
||||
|
||||
After NPM has successfully published the budibase packages, a new tag will be pushed to master. This will kick off a github action (can be found at `.github/workflows/release.yml`) this will build and package the electron application for every OS (Windows, Mac, Linux). The binaries will be stored under the new tag on the [budibase releases page](https://github.com/Budibase/budibase/releases).
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
Sometimes, things go wrong. This can be due to incompatible updates on the budibase platform. To clear down your development environment and start again:
|
||||
|
@ -186,4 +208,4 @@ Or if you are in the builder you can run `yarn cy:test`.
|
|||
* This project uses a modified version of the MPLv2 license, see [LICENSE](https://github.com/budibase/server/blob/master/LICENSE).
|
||||
|
||||
* We use the [C4 (Collective Code Construction Contract)](https://rfc.zeromq.org/spec:42/C4/) process for contributions.
|
||||
Please read this if you are unfamiliar with it.
|
||||
Please read this if you are unfamiliar with it.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "0.0.32",
|
||||
"version": "0.1.12",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 132 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.4 MiB |
Binary file not shown.
Before Width: | Height: | Size: 201 KiB |
Binary file not shown.
Before Width: | Height: | Size: 105 KiB |
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/builder",
|
||||
"version": "0.0.32",
|
||||
"version": "0.1.11",
|
||||
"license": "AGPL-3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
@ -55,18 +55,20 @@
|
|||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "^1.15.0",
|
||||
"@budibase/client": "^0.0.32",
|
||||
"@budibase/bbui": "^1.16.0",
|
||||
"@budibase/client": "^0.1.1",
|
||||
"@budibase/colorpicker": "^1.0.1",
|
||||
"@nx-js/compiler-util": "^2.0.0",
|
||||
"@sentry/browser": "5.19.1",
|
||||
"codemirror": "^5.51.0",
|
||||
"date-fns": "^1.29.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"feather-icons": "^4.21.0",
|
||||
"flatpickr": "^4.5.7",
|
||||
"lodash": "^4.17.13",
|
||||
"logrocket": "^1.0.6",
|
||||
"lunr": "^2.3.5",
|
||||
"mustache": "^4.0.1",
|
||||
"posthog-js": "^1.3.1",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"shortid": "^2.2.8",
|
||||
"string_decoder": "^1.2.0",
|
||||
|
@ -87,6 +89,7 @@
|
|||
"babel-jest": "^24.8.0",
|
||||
"browser-sync": "^2.26.7",
|
||||
"cypress": "^4.8.0",
|
||||
"eslint-plugin-cypress": "^2.11.1",
|
||||
"http-proxy-middleware": "^0.19.1",
|
||||
"jest": "^24.8.0",
|
||||
"ncp": "^2.0.0",
|
||||
|
@ -109,4 +112,4 @@
|
|||
"svelte-jester": "^1.0.6"
|
||||
},
|
||||
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -180,6 +180,9 @@ export default {
|
|||
"process.env.NODE_ENV": JSON.stringify(
|
||||
production ? "production" : "development"
|
||||
),
|
||||
"process.env.POSTHOG_TOKEN": JSON.stringify(process.env.POSTHOG_TOKEN),
|
||||
"process.env.POSTHOG_URL": JSON.stringify(process.env.POSTHOG_URL),
|
||||
"process.env.SENTRY_DSN": JSON.stringify(process.env.SENTRY_DSN),
|
||||
}),
|
||||
|
||||
svelte({
|
||||
|
|
|
@ -5,17 +5,9 @@
|
|||
import { routes } from "../routify/routes"
|
||||
import { store, initialise } from "builderStore"
|
||||
import NotificationDisplay from "components/common/Notification/NotificationDisplay.svelte"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
|
||||
function showErrorBanner() {
|
||||
notifier.danger(
|
||||
"Whoops! Looks like we're having trouble. Please refresh the page."
|
||||
)
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
window.addEventListener("error", showErrorBanner)
|
||||
window.addEventListener("unhandledrejection", showErrorBanner)
|
||||
await initialise()
|
||||
})
|
||||
|
||||
$basepath = "/_builder"
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import * as Sentry from "@sentry/browser"
|
||||
import posthog from "posthog-js"
|
||||
|
||||
function activate() {
|
||||
Sentry.init({ dsn: process.env.SENTRY_DSN })
|
||||
posthog.init(process.env.POSTHOG_TOKEN, {
|
||||
api_host: process.env.POSTHOG_URL,
|
||||
})
|
||||
}
|
||||
|
||||
function captureException(err) {
|
||||
Sentry.captureException(err)
|
||||
}
|
||||
|
||||
function captureEvent(event) {
|
||||
if (process.env.NODE_ENV !== "production") return
|
||||
posthog.capture(event)
|
||||
}
|
||||
|
||||
export default {
|
||||
activate,
|
||||
captureException,
|
||||
captureEvent,
|
||||
}
|
|
@ -1,11 +1,5 @@
|
|||
/* Budibase Component Styles */
|
||||
.header {
|
||||
font-size: 0.75rem;
|
||||
color: var(--ink);
|
||||
text-transform: uppercase;
|
||||
margin-top: 1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
|
||||
.budibase__title {
|
||||
font-weight: 900;
|
||||
|
@ -69,7 +63,7 @@
|
|||
|
||||
.budibase__nav-item.selected {
|
||||
color: var(--ink);
|
||||
background: var(--blue-light);
|
||||
background: var(--grey-2);
|
||||
}
|
||||
|
||||
.budibase__nav-item:hover {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { getStore } from "./store"
|
||||
import { getBackendUiStore } from "./store/backend"
|
||||
import { getWorkflowStore } from "./store/workflow/"
|
||||
import LogRocket from "logrocket"
|
||||
import analytics from "../analytics"
|
||||
|
||||
export const store = getStore()
|
||||
export const backendUiStore = getBackendUiStore()
|
||||
|
@ -10,7 +10,7 @@ export const workflowStore = getWorkflowStore()
|
|||
export const initialise = async () => {
|
||||
try {
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
LogRocket.init("knlald/budibase")
|
||||
analytics.activate()
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
|
|
|
@ -2,23 +2,24 @@ import { writable } from "svelte/store"
|
|||
import { cloneDeep } from "lodash/fp"
|
||||
import api from "../api"
|
||||
|
||||
export const getBackendUiStore = () => {
|
||||
const INITIAL_BACKEND_UI_STATE = {
|
||||
models: [],
|
||||
views: [],
|
||||
users: [],
|
||||
selectedDatabase: {},
|
||||
selectedModel: {},
|
||||
draftModel: {},
|
||||
tabs: {
|
||||
SETUP_PANEL: "SETUP",
|
||||
NAVIGATION_PANEL: "NAVIGATE",
|
||||
},
|
||||
}
|
||||
const INITIAL_BACKEND_UI_STATE = {
|
||||
models: [],
|
||||
views: [],
|
||||
users: [],
|
||||
selectedDatabase: {},
|
||||
selectedModel: {},
|
||||
draftModel: {},
|
||||
tabs: {
|
||||
SETUP_PANEL: "SETUP",
|
||||
NAVIGATION_PANEL: "NAVIGATE",
|
||||
},
|
||||
}
|
||||
|
||||
const store = writable(INITIAL_BACKEND_UI_STATE)
|
||||
export const getBackendUiStore = () => {
|
||||
const store = writable({ ...INITIAL_BACKEND_UI_STATE })
|
||||
|
||||
store.actions = {
|
||||
reset: () => store.set({ ...INITIAL_BACKEND_UI_STATE }),
|
||||
database: {
|
||||
select: async db => {
|
||||
const modelsResponse = await api.get(`/api/models`)
|
||||
|
@ -78,7 +79,6 @@ export const getBackendUiStore = () => {
|
|||
}
|
||||
|
||||
const SAVE_MODEL_URL = `/api/models`
|
||||
console.log(updatedModel)
|
||||
const response = await api.post(SAVE_MODEL_URL, updatedModel)
|
||||
const savedModel = await response.json()
|
||||
await store.actions.models.fetch()
|
||||
|
|
|
@ -29,7 +29,8 @@ import {
|
|||
export const getStore = () => {
|
||||
const initial = {
|
||||
apps: [],
|
||||
appname: "",
|
||||
name: "",
|
||||
description: "",
|
||||
pages: DEFAULT_PAGES_OBJECT,
|
||||
mainUi: {},
|
||||
unauthenticatedUi: {},
|
||||
|
@ -52,7 +53,6 @@ export const getStore = () => {
|
|||
store.createDatabaseForApp = backendStoreActions.createDatabaseForApp(store)
|
||||
|
||||
store.saveScreen = saveScreen(store)
|
||||
store.deleteScreen = deleteScreen(store)
|
||||
store.setCurrentScreen = setCurrentScreen(store)
|
||||
store.setCurrentPage = setCurrentPage(store)
|
||||
store.createScreen = createScreen(store)
|
||||
|
@ -101,7 +101,8 @@ const setPackage = (store, initial) => async pkg => {
|
|||
|
||||
initial.libraries = pkg.application.componentLibraries
|
||||
initial.components = await fetchComponentLibDefinitions(pkg.application._id)
|
||||
initial.appname = pkg.application.name
|
||||
initial.name = pkg.application.name
|
||||
initial.description = pkg.application.description
|
||||
initial.appId = pkg.application._id
|
||||
initial.pages = pkg.pages
|
||||
initial.hasAppPackage = true
|
||||
|
@ -160,6 +161,7 @@ const createScreen = store => (screenName, route, layoutComponentName) => {
|
|||
props: createProps(rootComponent).props,
|
||||
}
|
||||
newScreen.route = route
|
||||
newScreen.name = newScreen.props._id
|
||||
newScreen.props._instanceName = screenName || ""
|
||||
state.currentPreviewItem = newScreen
|
||||
state.currentComponentInfo = newScreen.props
|
||||
|
@ -189,24 +191,6 @@ const setCurrentScreen = store => screenName => {
|
|||
})
|
||||
}
|
||||
|
||||
const deleteScreen = store => name => {
|
||||
store.update(s => {
|
||||
const components = s.components.filter(c => c.name !== name)
|
||||
const screens = s.screens.filter(c => c.name !== name)
|
||||
|
||||
s.components = components
|
||||
s.screens = screens
|
||||
if (s.currentPreviewItem.name === name) {
|
||||
s.currentPreviewItem = null
|
||||
s.currentFrontEndType = ""
|
||||
}
|
||||
|
||||
api.delete(`/_builder/api/${s.appId}/screen/${name}`)
|
||||
|
||||
return s
|
||||
})
|
||||
}
|
||||
|
||||
const savePage = store => async page => {
|
||||
store.update(state => {
|
||||
if (state.currentFrontEndType !== "page" || !state.currentPageName) {
|
||||
|
|
|
@ -28,12 +28,15 @@
|
|||
}
|
||||
|
||||
i {
|
||||
font-size: 30px;
|
||||
font-size: 24px;
|
||||
color: var(--grey-7);
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
margin-top: 8px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
div:hover {
|
||||
|
|
|
@ -1,13 +1,112 @@
|
|||
<script>
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let checked = false
|
||||
export let label = ""
|
||||
|
||||
function handleChange() {
|
||||
checked = !checked
|
||||
dispatch("change", checked)
|
||||
}
|
||||
</script>
|
||||
|
||||
{label}
|
||||
<input class="uk-checkbox" type="checkbox" bind:checked on:change />
|
||||
<input type="checkbox" class="checkbox" id="_checkbox" />
|
||||
<label for="_checkbox" class:checked on:click={handleChange}>
|
||||
<div class="tick_mark" />
|
||||
</label>
|
||||
|
||||
<style>
|
||||
input {
|
||||
margin-right: 7px;
|
||||
.checkbox {
|
||||
display: none;
|
||||
}
|
||||
|
||||
label {
|
||||
position: relative;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
/* background-color: #5e17e9; */
|
||||
background-color: var(--grey-2);
|
||||
transform: translateY(-50%);
|
||||
cursor: pointer;
|
||||
transition: 0.2s ease transform, 0.2s ease background-color,
|
||||
0.2s ease box-shadow;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
label:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
left: 0;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin: 0 auto;
|
||||
background-color: #fff;
|
||||
transform: translateY(-50%);
|
||||
transition: 0.2s ease width, 0.2s ease height;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
label:active {
|
||||
transform: translateY(-50%) scale(0.9);
|
||||
}
|
||||
|
||||
.tick_mark {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 6px;
|
||||
width: 5px;
|
||||
height: 4px;
|
||||
margin: 0 auto;
|
||||
transform: rotateZ(-40deg);
|
||||
}
|
||||
|
||||
.tick_mark:before,
|
||||
.tick_mark:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
background-color: #000;
|
||||
border-radius: 2px;
|
||||
opacity: 0;
|
||||
transition: 0.2s ease transform, 0.2s ease opacity;
|
||||
}
|
||||
|
||||
.tick_mark:before {
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 2px;
|
||||
height: 6px;
|
||||
box-shadow: -2px 0 5px rgba(0, 0, 0, 0.23);
|
||||
transform: translateY(-68px);
|
||||
}
|
||||
|
||||
.tick_mark:after {
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 12px;
|
||||
height: 2px;
|
||||
box-shadow: 0 3px 5px rgba(0, 0, 0, 0.23);
|
||||
transform: translateX(78px);
|
||||
}
|
||||
|
||||
.checked {
|
||||
/* background-color: #5e17e9; */
|
||||
background-color: var(--grey-2);
|
||||
/* box-shadow: 0 7px 10px #5e17e9; */
|
||||
}
|
||||
|
||||
.checked:before {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.checked .tick_mark:before,
|
||||
.checked .tick_mark:after {
|
||||
transform: translate(0);
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
// import { HsvPicker } from "svelte-color-picker"
|
||||
|
||||
// export let initialValue = "#ffffff"
|
||||
export let onChange = color => {}
|
||||
export let open = false
|
||||
let value = "#ffffff"
|
||||
|
||||
let _justMounted = true //see onColorChange
|
||||
let pickerHeight = 275
|
||||
let colorPreview
|
||||
let pickerTopPosition = null
|
||||
|
||||
function rbgaToHexa({ r, g, b, a }) {
|
||||
r = r.toString(16)
|
||||
g = g.toString(16)
|
||||
b = b.toString(16)
|
||||
a = Math.round(a * 255).toString(16)
|
||||
|
||||
if (r.length == 1) r = "0" + r
|
||||
if (g.length == 1) g = "0" + g
|
||||
if (b.length == 1) b = "0" + b
|
||||
if (a.length == 1) a = "0" + a
|
||||
|
||||
return "#" + r + g + b + a
|
||||
}
|
||||
|
||||
function onColourChange(rgba) {
|
||||
value = rbgaToHexa(rgba.detail)
|
||||
|
||||
//Hack: so that color change doesn't fire onMount
|
||||
if (!_justMounted) {
|
||||
// onChange(value)
|
||||
}
|
||||
_justMounted = false
|
||||
}
|
||||
|
||||
function toggleColorpicker(isOpen) {
|
||||
if (isOpen) {
|
||||
const {
|
||||
y: previewYPosition,
|
||||
height: previewHeight,
|
||||
} = colorPreview.getBoundingClientRect()
|
||||
|
||||
let wiggleRoom = window.innerHeight - previewYPosition
|
||||
let displayTop = wiggleRoom < pickerHeight
|
||||
|
||||
if (displayTop) {
|
||||
pickerTopPosition = previewYPosition - (pickerHeight - window.scrollY)
|
||||
} else {
|
||||
pickerTopPosition = null
|
||||
}
|
||||
}
|
||||
open = isOpen
|
||||
}
|
||||
|
||||
$: style = open ? "display: block;" : "display: none;"
|
||||
$: pickerStyle = pickerTopPosition ? `top: ${pickerTopPosition}px;` : ""
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={colorPreview}
|
||||
on:click={() => toggleColorpicker(true)}
|
||||
class="color-preview"
|
||||
style={`background: ${value}`} />
|
||||
|
||||
<div class="colorpicker" {style}>
|
||||
<div class="overlay" on:click|self={() => toggleColorpicker(false)} />
|
||||
<div class="cp" style={pickerStyle}>
|
||||
<!-- <HsvPicker on:colorChange={onColourChange} startColor={value} /> -->
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
OLD LOCAL STORAGE OPTIONS. INCLUDING FOR ADDING LATER
|
||||
function getRecentColors() {
|
||||
let colorStore = localStorage.getItem("bb:recentColors")
|
||||
if (!!colorStore) {
|
||||
swatches = JSON.parse(colorStore)
|
||||
}
|
||||
}
|
||||
|
||||
function setRecentColor(color) {
|
||||
if (swatches.length >= 15) {
|
||||
swatches.splice(0, 1)
|
||||
picker.removeSwatch(0)
|
||||
}
|
||||
if (!swatches.includes(color)) {
|
||||
swatches = [...swatches, color]
|
||||
picker.addSwatch(color)
|
||||
localStorage.setItem("bb:recentColors", JSON.stringify(swatches))
|
||||
}
|
||||
} -->
|
||||
|
||||
<style>
|
||||
.overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
/* background: rgba(5, 5, 5, 0.25); */
|
||||
}
|
||||
|
||||
.cp {
|
||||
position: absolute;
|
||||
right: 25px;
|
||||
}
|
||||
.color-preview {
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
margin: 5px;
|
||||
cursor: pointer;
|
||||
border: 1px solid gainsboro;
|
||||
}
|
||||
</style>
|
|
@ -3,9 +3,12 @@
|
|||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
height="24">
|
||||
<path d="M0 0h24v24H0z" fill="none" />
|
||||
<path fill="none" d="M0 0h24v24H0z" />
|
||||
<path
|
||||
d="M12 1l9.5 5.5v11L12 23l-9.5-5.5v-11L12 1zm0 14a3 3 0 1 0 0-6 3 3 0 0 0 0
|
||||
6z"
|
||||
fill="currentColor" />
|
||||
fill="currentColor"
|
||||
d="M8.686 4l2.607-2.607a1 1 0 0 1 1.414 0L15.314 4H19a1 1 0 0 1 1
|
||||
1v3.686l2.607 2.607a1 1 0 0 1 0 1.414L20 15.314V19a1 1 0 0 1-1
|
||||
1h-3.686l-2.607 2.607a1 1 0 0 1-1.414 0L8.686 20H5a1 1 0 0
|
||||
1-1-1v-3.686l-2.607-2.607a1 1 0 0 1 0-1.414L4 8.686V5a1 1 0 0 1
|
||||
1-1h3.686zM12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z" />
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 263 B After Width: | Height: | Size: 494 B |
|
@ -60,7 +60,8 @@
|
|||
|
||||
<style>
|
||||
.fields.selected {
|
||||
background: var(--grey-1);
|
||||
background: var(--grey-2);
|
||||
border: var(--purple) 1px solid;
|
||||
}
|
||||
|
||||
h3 {
|
||||
|
|
|
@ -65,6 +65,7 @@
|
|||
margin-right: 20px;
|
||||
background: none;
|
||||
outline: none;
|
||||
font-family: Inter;
|
||||
}
|
||||
|
||||
.switcher > .selected {
|
||||
|
|
|
@ -113,15 +113,15 @@
|
|||
on:click={() => {
|
||||
editRecord(row)
|
||||
}}>
|
||||
<div>Edit</div>
|
||||
<i class="ri-edit-line" />
|
||||
<div class="label">Edit</div>
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
on:click={() => {
|
||||
deleteRecord(row)
|
||||
}}>
|
||||
Delete
|
||||
</div>
|
||||
<li
|
||||
on:click={() => {
|
||||
deleteRecord(row)
|
||||
}}>
|
||||
<i class="ri-delete-bin-2-line" />
|
||||
<div class="label">Delete</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -146,6 +146,9 @@
|
|||
</section>
|
||||
|
||||
<style>
|
||||
section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
|
@ -177,7 +180,7 @@
|
|||
border-bottom: 1px solid var(--grey-4);
|
||||
transition: 0.3s background-color;
|
||||
color: var(--ink);
|
||||
font-size: 14px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
|
@ -204,4 +207,28 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
i {
|
||||
color: var(--grey-7);
|
||||
margin-right: 8px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: var(--grey-7);
|
||||
font-size: 14px;
|
||||
font-family: inter;
|
||||
font-weight: 400;
|
||||
margin: 12px 0px;
|
||||
}
|
||||
.label:hover {
|
||||
color: var(--ink);
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
<div class="heading">
|
||||
{#if !showFieldView}
|
||||
<i class="ri-list-settings-line button--toggled" />
|
||||
<h3 class="budibase__title--3">Create / Edit Model</h3>
|
||||
<h3 class="budibase__title--3">Create / Edit Table</h3>
|
||||
{:else}
|
||||
<i class="ri-file-list-line button--toggled" />
|
||||
<h3 class="budibase__title--3">Create / Edit Field</h3>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import { store, backendUiStore } from "builderStore"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import { compose, map, get, flatten } from "lodash/fp"
|
||||
import { Button } from "@budibase/bbui"
|
||||
import { Input, TextArea, Button } from "@budibase/bbui"
|
||||
import LinkedRecordSelector from "components/common/LinkedRecordSelector.svelte"
|
||||
import Select from "components/common/Select.svelte"
|
||||
import RecordFieldControl from "./RecordFieldControl.svelte"
|
||||
|
@ -70,7 +70,7 @@
|
|||
<div class="actions">
|
||||
<header>
|
||||
<i class="ri-file-user-fill" />
|
||||
<h4 class="budibase__title--4">Create / Edit Record</h4>
|
||||
<h4>Create / Edit Record</h4>
|
||||
</header>
|
||||
<ErrorsBox {errors} />
|
||||
<form on:submit|preventDefault class="uk-form-stacked">
|
||||
|
@ -117,15 +117,16 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--blue-light);
|
||||
color: var(--ink);
|
||||
color: var(--grey-7);
|
||||
font-size: 20px;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
display: inline-block;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
font-weight: 600;
|
||||
font-family: sans-serif;
|
||||
color: var(--ink);
|
||||
margin: 0;
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
class={determineClassName(type)}
|
||||
bind:value
|
||||
class:uk-form-danger={errors.length > 0}>
|
||||
<option />
|
||||
{#each options as opt}
|
||||
<option value={opt}>{opt}</option>
|
||||
{/each}
|
||||
|
|
|
@ -52,17 +52,28 @@
|
|||
}
|
||||
|
||||
span {
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
padding: 0px 16px;
|
||||
height: 36px;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
font-weight: 500;
|
||||
border-radius: 3px;
|
||||
color: var(--ink-lighter);
|
||||
background: #ffffff;
|
||||
color: var(--grey-7);
|
||||
border-radius: 5px;
|
||||
font-family: inter;
|
||||
font-size: 14px;
|
||||
background: var(--grey-1);
|
||||
font-weight: 400;
|
||||
transition: all 0.3s;
|
||||
text-rendering: optimizeLegibility;
|
||||
border: none !important;
|
||||
transition: 0.2s;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
span:hover {
|
||||
background: var(--blue-light);
|
||||
color: var(--ink);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,19 +19,19 @@
|
|||
...rest,
|
||||
},
|
||||
})
|
||||
notifier.success(`${model.name} model created.`)
|
||||
notifier.success(`${model.name} table created.`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<section transition:fade>
|
||||
<header>
|
||||
<h2>Create New Model</h2>
|
||||
<p>Before you can view your model, you need to set it up.</p>
|
||||
<h2>Create New Table</h2>
|
||||
<p>Before you can view your table, you need to set it up.</p>
|
||||
</header>
|
||||
|
||||
<div class="block-row">
|
||||
<span class="block-row-title">Fields</span>
|
||||
<p>Blocks are pre-made fields and help you build your model quicker.</p>
|
||||
<p>Blocks are pre-made fields and help you build your table quicker.</p>
|
||||
<div class="blocks">
|
||||
{#each Object.values(FIELDS) as field}
|
||||
<Block
|
||||
|
@ -45,7 +45,7 @@
|
|||
|
||||
<div class="block-row">
|
||||
<span class="block-row-title">Blocks</span>
|
||||
<p>Blocks are pre-made fields and help you build your model quicker.</p>
|
||||
<p>Blocks are pre-made fields and help you build your table quicker.</p>
|
||||
<div class="blocks">
|
||||
{#each Object.values(BLOCKS) as field}
|
||||
<Block
|
||||
|
@ -58,8 +58,8 @@
|
|||
</div>
|
||||
|
||||
<div class="block-row">
|
||||
<span class="block-row-title">Models</span>
|
||||
<p>Blocks are pre-made fields and help you build your model quicker.</p>
|
||||
<span class="block-row-title">Tables</span>
|
||||
<p>Blocks are pre-made fields and help you build your table quicker.</p>
|
||||
<div class="blocks">
|
||||
{#each Object.values(MODELS) as model}
|
||||
<Block
|
||||
|
|
|
@ -31,11 +31,11 @@
|
|||
}
|
||||
|
||||
.selected {
|
||||
background-color: var(--blue-light);
|
||||
background-color: var(--grey-2);
|
||||
}
|
||||
|
||||
div:hover {
|
||||
background-color: var(--blue-light);
|
||||
background-color: var(--grey-1);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
bind:value={$backendUiStore.tabs.NAVIGATION_PANEL}>
|
||||
{#if selectedTab === 'NAVIGATE'}
|
||||
<Button purple wide on:click={setupForNewModel}>
|
||||
Create New Model
|
||||
Create New Table
|
||||
</Button>
|
||||
<div class="hierarchy-items-container">
|
||||
{#each $backendUiStore.models as model}
|
||||
|
@ -68,7 +68,7 @@
|
|||
<ListItem
|
||||
selected={model._id === $backendUiStore.selectedModel._id && fieldName === $backendUiStore.selectedField}
|
||||
indented
|
||||
icon="ri-layout-column-fill"
|
||||
icon="ri-layout-column-line"
|
||||
title={model.schema[fieldName].name}
|
||||
on:click={() => selectModel(model, fieldName)} />
|
||||
{/each}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
$: required =
|
||||
field.constraints &&
|
||||
field.constraints.presence &&
|
||||
!constraints.presence.allowEmpty
|
||||
!field.constraints.presence.allowEmpty
|
||||
</script>
|
||||
|
||||
<div class="info">
|
||||
|
@ -41,7 +41,10 @@
|
|||
<div class="info">
|
||||
<div class="field">
|
||||
<label>Required</label>
|
||||
<input type="checkbox" />
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={required}
|
||||
on:change={() => (field.constraints.presence.allowEmpty = required)} />
|
||||
</div>
|
||||
|
||||
{#if field.type === 'string'}
|
||||
|
|
|
@ -52,7 +52,35 @@
|
|||
})
|
||||
}
|
||||
|
||||
function validate() {
|
||||
let errors = []
|
||||
for (let field of Object.values($backendUiStore.draftModel.schema)) {
|
||||
const restrictedFieldNames = ["type", "modelId"]
|
||||
if (field.name.startsWith("_")) {
|
||||
errors.push(`field '${field.name}' - name cannot begin with '_''`)
|
||||
} else if (restrictedFieldNames.includes(field.name)) {
|
||||
errors.push(
|
||||
`field '${field.name}' - is a restricted name, please rename`
|
||||
)
|
||||
} else if (!field.name || !field.name.trim()) {
|
||||
errors.push("field name cannot be blank")
|
||||
}
|
||||
}
|
||||
|
||||
if (!$backendUiStore.draftModel.name) {
|
||||
errors.push("Table name cannot be blank")
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
async function saveModel() {
|
||||
const errors = validate()
|
||||
if (errors.length > 0) {
|
||||
notifier.danger(errors.join("/n"))
|
||||
return
|
||||
}
|
||||
|
||||
await backendUiStore.actions.models.save({
|
||||
model: $backendUiStore.draftModel,
|
||||
})
|
||||
|
@ -75,10 +103,12 @@
|
|||
class="budibase__input"
|
||||
bind:value={$backendUiStore.draftModel.name} />
|
||||
</div>
|
||||
<!-- dont have this capability yet..
|
||||
<div class="titled-input">
|
||||
<header>Import Data</header>
|
||||
<Button wide secondary>Import CSV</Button>
|
||||
</div>
|
||||
-->
|
||||
{/if}
|
||||
<footer>
|
||||
<Button disabled={!edited} green={edited} wide on:click={saveModel}>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
}
|
||||
|
||||
$: currentAppInfo = {
|
||||
appname: $store.appname,
|
||||
name: $store.name,
|
||||
}
|
||||
|
||||
async function fetchUsers() {
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
}
|
||||
.topnavitemright {
|
||||
cursor: pointer;
|
||||
color: var(--ink-light);
|
||||
color: var(--grey-7);
|
||||
margin: 0px 20px 0px 0px;
|
||||
padding-top: 4px;
|
||||
font-weight: 500;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { General, Users, DangerZone } from "./tabs"
|
||||
import { General, Users, DangerZone, APIKeys } from "./tabs"
|
||||
|
||||
import { Input, TextArea, Button, Switcher } from "@budibase/bbui"
|
||||
import { SettingsIcon, CloseIcon } from "components/common/Icons/"
|
||||
|
@ -20,6 +20,11 @@
|
|||
key: "USERS",
|
||||
component: Users,
|
||||
},
|
||||
{
|
||||
title: "API Keys",
|
||||
key: "API_KEYS",
|
||||
component: APIKeys,
|
||||
},
|
||||
{
|
||||
title: "Danger Zone",
|
||||
key: "DANGERZONE",
|
||||
|
@ -50,6 +55,7 @@
|
|||
<style>
|
||||
.container {
|
||||
position: relative;
|
||||
height: 36rem;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
|
@ -83,9 +89,10 @@
|
|||
width: 20px;
|
||||
padding: 10px;
|
||||
background-color: var(--blue-light);
|
||||
color: var(--grey-7);
|
||||
}
|
||||
.body {
|
||||
padding: 40px 40px 80px 40px;
|
||||
padding: 40px 40px 40px 40px;
|
||||
display: grid;
|
||||
grid-gap: 20px;
|
||||
}
|
||||
|
|
|
@ -39,4 +39,16 @@
|
|||
grid-gap: 18px;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
.inputs :global(input) {
|
||||
padding: 10px 12px;
|
||||
border-radius: var(--rounded-small);
|
||||
}
|
||||
.inputs :global(select) {
|
||||
padding: 9px 12px;
|
||||
border-radius: var(--rounded-small);
|
||||
}
|
||||
.inputs :global(button) {
|
||||
border-radius: var(--rounded-small);
|
||||
height: initial;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
<script>
|
||||
import { Input, Button } from "@budibase/bbui"
|
||||
import { store } from "builderStore"
|
||||
import api from "builderStore/api"
|
||||
import posthog from "posthog-js"
|
||||
|
||||
let keys = { budibase: "", sendGrid: "" }
|
||||
|
||||
async function updateKey([key, value]) {
|
||||
const response = await api.put(`/api/keys/${key}`, { value })
|
||||
const res = await response.json()
|
||||
if (key === "budibase") posthog.identify(value)
|
||||
keys = { ...keys, ...res }
|
||||
}
|
||||
|
||||
// Get Keys
|
||||
async function fetchKeys() {
|
||||
const response = await api.get(`/api/keys/`)
|
||||
const res = await response.json()
|
||||
keys = res
|
||||
}
|
||||
|
||||
fetchKeys()
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<div class="background">
|
||||
<Input
|
||||
on:save={e => updateKey(['budibase', e.detail])}
|
||||
thin
|
||||
edit
|
||||
value={keys.budibase}
|
||||
label="Budibase" />
|
||||
</div>
|
||||
<div class="background">
|
||||
<Input
|
||||
on:save={e => updateKey(['sendgrid', e.detail])}
|
||||
thin
|
||||
edit
|
||||
value={keys.sendgrid}
|
||||
label="Sendgrid" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
display: grid;
|
||||
grid-gap: var(--space);
|
||||
}
|
||||
.background {
|
||||
border-radius: 5px;
|
||||
padding: 12px 0px;
|
||||
}
|
||||
</style>
|
|
@ -1,31 +1,42 @@
|
|||
<script>
|
||||
import { params, goto } from "@sveltech/routify"
|
||||
import { Input, TextArea, Button } from "@budibase/bbui"
|
||||
import Title from "../TabTitle.svelte"
|
||||
import { del } from "builderStore/api"
|
||||
|
||||
let value = ""
|
||||
let loading = false
|
||||
|
||||
const deleteApp = () => {
|
||||
async function deleteApp() {
|
||||
loading = true
|
||||
// Do stuff here to delete app!
|
||||
// Navigate to start
|
||||
const id = $params.application
|
||||
const res = await del(`/api/${id}`)
|
||||
const json = await res.json()
|
||||
|
||||
loading = false
|
||||
if (res.ok) {
|
||||
$goto("/")
|
||||
return json
|
||||
} else {
|
||||
throw new Error(json)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Title>Danger Zone</Title>
|
||||
<div class="background">
|
||||
<p>
|
||||
Type DELETE into the textbox, then click the following button to delete your
|
||||
web app:
|
||||
</p>
|
||||
<Input
|
||||
on:change={e => (value = e.target.value)}
|
||||
on:input={e => (value = e.target.value)}
|
||||
thin
|
||||
disabled={loading}
|
||||
placeholder="Enter your name"
|
||||
label="Type DELETE into the textbox, then click the following button to
|
||||
delete your web app:" />
|
||||
placeholder="" />
|
||||
|
||||
<Button
|
||||
disabled={value !== 'DELETE' || loading}
|
||||
primary
|
||||
red
|
||||
wide
|
||||
on:click={deleteApp}>
|
||||
Delete Entire Web App
|
||||
|
@ -35,10 +46,12 @@
|
|||
<style>
|
||||
.background {
|
||||
display: grid;
|
||||
grid-gap: var(--space);
|
||||
grid-gap: 16px;
|
||||
border-radius: 5px;
|
||||
background-color: var(--light-grey);
|
||||
padding: 12px 12px 18px 12px;
|
||||
padding: 12px 0px;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
.background :global(button) {
|
||||
max-width: 100%;
|
||||
|
|
|
@ -1,26 +1,40 @@
|
|||
<script>
|
||||
import { Input, TextArea, Button } from "@budibase/bbui"
|
||||
import Title from "../TabTitle.svelte"
|
||||
import { store } from "builderStore"
|
||||
import api from "builderStore/api"
|
||||
|
||||
async function updateApplication(data) {
|
||||
const response = await api.put(`/api/${$store.appId}`, data)
|
||||
const app = await response.json()
|
||||
store.update(state => {
|
||||
state = {
|
||||
...state,
|
||||
...data,
|
||||
}
|
||||
return state
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<Title>General</Title>
|
||||
<div class="container">
|
||||
<div class="background">
|
||||
<Input thin edit placeholder="Enter your name" label="Name" />
|
||||
</div>
|
||||
<div class="background">
|
||||
<TextArea thin edit placeholder="Enter your name" label="Name" />
|
||||
</div>
|
||||
<Input
|
||||
on:save={e => updateApplication({ name: e.detail })}
|
||||
thin
|
||||
edit
|
||||
value={$store.name}
|
||||
label="Name" />
|
||||
<TextArea
|
||||
on:save={e => updateApplication({ description: e.detail })}
|
||||
thin
|
||||
edit
|
||||
value={$store.description}
|
||||
label="Description" />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
display: grid;
|
||||
grid-gap: var(--space);
|
||||
}
|
||||
.background {
|
||||
border-radius: 5px;
|
||||
background-color: var(--light-grey);
|
||||
padding: 12px 12px 18px 12px;
|
||||
grid-gap: 32px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<script>
|
||||
import { Input, Select, Button } from "@budibase/bbui"
|
||||
import Title from "../TabTitle.svelte"
|
||||
import UserRow from "../UserRow.svelte"
|
||||
|
||||
import { store, backendUiStore } from "builderStore"
|
||||
|
@ -52,9 +51,8 @@
|
|||
let fetchUsersPromise = fetchUsers()
|
||||
</script>
|
||||
|
||||
<Title>Users</Title>
|
||||
<div class="container">
|
||||
<div class="background create">
|
||||
<div class="background">
|
||||
<div class="title">Create new user</div>
|
||||
<div class="inputs">
|
||||
<Input thin bind:value={username} name="Name" placeholder="Username" />
|
||||
|
@ -69,10 +67,10 @@
|
|||
</Select>
|
||||
</div>
|
||||
<div class="create-button">
|
||||
<Button on:click={createUser} small blue>Create</Button>
|
||||
<Button on:click={createUser} small primary>Create</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="background">
|
||||
<div class="background-users">
|
||||
<div class="title">Current Users</div>
|
||||
{#await fetchUsersPromise}
|
||||
Loading state!
|
||||
|
@ -87,7 +85,8 @@
|
|||
{/each}
|
||||
</ul>
|
||||
{:catch error}
|
||||
err0r
|
||||
Something went wrong when trying to fetch users. Please refresh (CMD + R /
|
||||
CTRL + R) the page and try again.
|
||||
{/await}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -95,27 +94,32 @@
|
|||
<style>
|
||||
.container {
|
||||
display: grid;
|
||||
grid-gap: 14px;
|
||||
grid-gap: 32px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
.background {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-gap: 12px;
|
||||
border-radius: 5px;
|
||||
background-color: var(--grey-2);
|
||||
padding: 12px 12px 18px 12px;
|
||||
}
|
||||
.background.create {
|
||||
background-color: var(--blue-light);
|
||||
}
|
||||
.inputs :global(select) {
|
||||
padding: 12px 9px;
|
||||
height: initial;
|
||||
|
||||
.background-users {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-gap: 12px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.create-button {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
}
|
||||
.create-button :global(button) {
|
||||
font-size: var(--font-size-sm);
|
||||
min-width: 100px;
|
||||
border-radius: var(--rounded-small);
|
||||
}
|
||||
.title {
|
||||
font-size: 14px;
|
||||
|
@ -123,13 +127,24 @@
|
|||
}
|
||||
.inputs {
|
||||
display: grid;
|
||||
margin-top: 12px;
|
||||
grid-gap: 18px;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
.inputs :global(input) {
|
||||
padding: 10px 12px;
|
||||
border-radius: var(--rounded-small);
|
||||
}
|
||||
.inputs :global(select) {
|
||||
padding: 10px 12px;
|
||||
border-radius: var(--rounded-small);
|
||||
background-color: var(--grey-2);
|
||||
}
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-gap: 8px;
|
||||
margin-top: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -2,4 +2,5 @@ export { default as General } from "./General.svelte"
|
|||
export { default as Integrations } from "./Integrations.svelte"
|
||||
export { default as Permissions } from "./Permissions.svelte"
|
||||
export { default as Users } from "./Users.svelte"
|
||||
export { default as APIKeys } from "./APIKeys.svelte"
|
||||
export { default as DangerZone } from "./DangerZone.svelte"
|
||||
|
|
|
@ -10,18 +10,19 @@
|
|||
<h3 class="app-title">{name}</h3>
|
||||
<p class="app-desc">{description}</p>
|
||||
<div class="card-footer">
|
||||
<a href={`/_builder/${_id}`} class="app-button">Open Web App</a>
|
||||
<a href={`/_builder/${_id}`} class="app-button">Open {name}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.apps-card {
|
||||
background-color: var(--white);
|
||||
padding: 20px 20px 30px 20px;
|
||||
padding: 20px 20px 20px 20px;
|
||||
max-width: 400px;
|
||||
max-height: 150px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid var(--grey-4);
|
||||
font-family: Inter;
|
||||
}
|
||||
|
||||
.app-button:hover {
|
||||
|
@ -34,12 +35,15 @@
|
|||
font-weight: 600;
|
||||
color: var(--ink);
|
||||
text-transform: capitalize;
|
||||
font-family: Inter;
|
||||
}
|
||||
|
||||
.app-desc {
|
||||
color: var(--grey-7);
|
||||
font-family: Inter;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<style>
|
||||
.apps {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, 380px);
|
||||
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
|
||||
grid-gap: 20px 40px;
|
||||
justify-content: start;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import { getContext } from "svelte"
|
||||
import { fade } from "svelte/transition"
|
||||
import { post } from "builderStore/api"
|
||||
import analytics from "../../analytics"
|
||||
|
||||
const { open, close } = getContext("simple-modal")
|
||||
|
||||
|
@ -38,6 +39,11 @@
|
|||
|
||||
const res = await response.json()
|
||||
|
||||
analytics.captureEvent("web_app_created", {
|
||||
name,
|
||||
description,
|
||||
appId: res._id,
|
||||
})
|
||||
$goto(`./${res._id}`)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
@ -63,7 +69,7 @@
|
|||
<span class="icon">
|
||||
<AppsIcon />
|
||||
</span>
|
||||
<h3>Create new web app</h3>
|
||||
<h3 class="header">Create new web app</h3>
|
||||
</div>
|
||||
<Input
|
||||
name="name"
|
||||
|
@ -90,7 +96,7 @@
|
|||
<InfoIcon />
|
||||
How to get started
|
||||
</a>
|
||||
<Button outline thin on:click={_onCancel}>Cancel</Button>
|
||||
<Button secondary thin on:click={_onCancel}>Cancel</Button>
|
||||
<Button primary thin on:click={_onOkay}>Save</Button>
|
||||
</div>
|
||||
<div class="close-button" on:click={_onCancel}>
|
||||
|
@ -125,10 +131,11 @@
|
|||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
h3 {
|
||||
.header {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
font-weight: 600;
|
||||
font-family: inter;
|
||||
}
|
||||
.icon {
|
||||
display: grid;
|
||||
|
|
|
@ -25,38 +25,105 @@
|
|||
name: "Screen Placeholder",
|
||||
route: "*",
|
||||
props: {
|
||||
_id: "screenslot-placeholder",
|
||||
_component: "@budibase/standard-components/container",
|
||||
_styles: {
|
||||
normal: {},
|
||||
hover: {},
|
||||
active: {},
|
||||
selected: {},
|
||||
},
|
||||
_code: "",
|
||||
className: "",
|
||||
onLoad: [],
|
||||
type: "div",
|
||||
_children: [
|
||||
{
|
||||
_id: "51a1b494-0fa4-49c3-90cc-c2a6c7a3f888",
|
||||
_component: "@budibase/standard-components/container",
|
||||
_styles: { normal: {}, hover: {}, active: {}, selected: {} },
|
||||
_id: "__screenslot__text",
|
||||
_styles: {
|
||||
normal: {
|
||||
display: "flex",
|
||||
"flex-direction": "column",
|
||||
"align-items": "center",
|
||||
},
|
||||
hover: {},
|
||||
active: {},
|
||||
selected: {},
|
||||
},
|
||||
_code: "",
|
||||
className: "",
|
||||
onLoad: [],
|
||||
type: "div",
|
||||
_instanceId: "inst_40d9036_4c81114e2bf145ab8721978c66e09a10",
|
||||
_instanceName: "Container",
|
||||
_children: [
|
||||
{
|
||||
_component: "@budibase/standard-components/text",
|
||||
_id: "90a52cd0-f215-46c1-b29b-e28f9e7edf72",
|
||||
_component: "@budibase/standard-components/heading",
|
||||
_styles: {
|
||||
normal: {},
|
||||
normal: {
|
||||
width: "500px",
|
||||
padding: "8px",
|
||||
},
|
||||
hover: {},
|
||||
active: {},
|
||||
selected: {},
|
||||
},
|
||||
_id: "__screenslot__text_2",
|
||||
_code: "",
|
||||
text: "content",
|
||||
font: "",
|
||||
color: "",
|
||||
textAlign: "inline",
|
||||
verticalAlign: "inline",
|
||||
formattingTag: "none",
|
||||
className: "",
|
||||
text: "Screen Slot",
|
||||
type: "h1",
|
||||
_instanceId: "inst_40d9036_4c81114e2bf145ab8721978c66e09a10",
|
||||
_instanceName: "Heading",
|
||||
_children: [],
|
||||
},
|
||||
{
|
||||
_id: "71a3da65-72c6-4c43-8c6a-49871c07b77d",
|
||||
_component: "@budibase/standard-components/text",
|
||||
_styles: {
|
||||
normal: {
|
||||
"max-width": "",
|
||||
"text-align": "left",
|
||||
width: "500px",
|
||||
padding: "8px",
|
||||
},
|
||||
hover: {},
|
||||
active: {},
|
||||
selected: {},
|
||||
},
|
||||
_code: "",
|
||||
text:
|
||||
"The screens that you create will be displayed inside this box.",
|
||||
type: "none",
|
||||
_instanceId: "inst_40d9036_4c81114e2bf145ab8721978c66e09a10",
|
||||
_instanceName: "Text",
|
||||
},
|
||||
{
|
||||
_id: "8af80374-460d-497b-a5d8-7dd2ec4a7bbc",
|
||||
_component: "@budibase/standard-components/text",
|
||||
_styles: {
|
||||
normal: {
|
||||
"max-width": "",
|
||||
"text-align": "left",
|
||||
width: "500px",
|
||||
padding: "8px",
|
||||
},
|
||||
hover: {},
|
||||
active: {},
|
||||
selected: {},
|
||||
},
|
||||
_code: "",
|
||||
text:
|
||||
"This box is just a placeholder, to show you the position of screens.",
|
||||
type: "none",
|
||||
_instanceId: "inst_40d9036_4c81114e2bf145ab8721978c66e09a10",
|
||||
_instanceName: "Text",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
_instanceName: "Content Placeholder",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,11 @@ export default `<html>
|
|||
<style>
|
||||
body, html {
|
||||
height: 100%!important;
|
||||
font-family: Roboto !important;
|
||||
font-family: Inter !important;
|
||||
margin: 0px!important;
|
||||
}
|
||||
*, *:before, *:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.lay-__screenslot__text {
|
||||
width: 100%;
|
||||
|
@ -21,6 +25,24 @@ export default `<html>
|
|||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.container-screenslot-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
border-style: dashed !important;
|
||||
border-width: 1px;
|
||||
color: #000000;
|
||||
background: #fafafa;
|
||||
height: 94%;
|
||||
}
|
||||
|
||||
.container-screenslot-placeholder span {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
<script src='/assets/budibase-client.js'></script>
|
||||
<script>
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
<script>
|
||||
import FlatButton from "./FlatButton.svelte"
|
||||
|
||||
export let format = "hex"
|
||||
export let onclick = format => {}
|
||||
|
||||
let colorFormats = ["hex", "rgb", "hsl"]
|
||||
</script>
|
||||
|
||||
<div class="flatbutton-group">
|
||||
{#each colorFormats as text}
|
||||
<FlatButton
|
||||
selected={format === text}
|
||||
{text}
|
||||
on:click={() => onclick(text)} />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.flatbutton-group {
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: center;
|
||||
width: 170px;
|
||||
height: 30px;
|
||||
align-self: center;
|
||||
}
|
||||
</style>
|
|
@ -1,24 +0,0 @@
|
|||
<script>
|
||||
import { buildStyle } from "./helpers.js"
|
||||
import { fade } from "svelte/transition"
|
||||
|
||||
export let backgroundSize = "10px"
|
||||
export let borderRadius = ""
|
||||
export let height = ""
|
||||
export let width = ""
|
||||
export let margin = ""
|
||||
|
||||
$: style = buildStyle({ backgroundSize, borderRadius, height, width, margin })
|
||||
</script>
|
||||
|
||||
<div in:fade {style}>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
background-image: url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="white" d="M1,0H2V1H1V0ZM0,1H1V2H0V1Z"/><path fill="gray" d="M0,0H1V1H0V0ZM1,1H2V2H1V1Z"/></svg>');
|
||||
height: fit-content;
|
||||
width: fit-content;
|
||||
}
|
||||
</style>
|
|
@ -1,29 +0,0 @@
|
|||
<script>
|
||||
export let text = ""
|
||||
export let selected = false
|
||||
</script>
|
||||
|
||||
<div class="flatbutton" class:selected on:click>{text}</div>
|
||||
|
||||
<style>
|
||||
.flatbutton {
|
||||
cursor: pointer;
|
||||
border: 1px solid #d4d4d4;
|
||||
border-radius: 8px;
|
||||
text-transform: uppercase;
|
||||
margin: 5px;
|
||||
transition: all 0.3s;
|
||||
font-size: 10px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: #f1f3f4;
|
||||
}
|
||||
|
||||
.selected {
|
||||
color: #ffffff;
|
||||
background-color: #003cb0;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
|
@ -1,28 +0,0 @@
|
|||
<script>
|
||||
export let value = ""
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<input on:input on:change type="text" {value} maxlength="25" />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 5px 0px;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 175px;
|
||||
font-size: 13px;
|
||||
background: #f1f3f4;
|
||||
border-radius: 8px;
|
||||
height: 20px;
|
||||
outline-color: #003cb0;
|
||||
color: inherit;
|
||||
text-align: center;
|
||||
border: 1px solid #dadada;
|
||||
font-weight: 550;
|
||||
}
|
||||
</style>
|
|
@ -1,73 +0,0 @@
|
|||
<script>
|
||||
import { onMount, createEventDispatcher } from "svelte"
|
||||
import CheckedBackground from "./CheckedBackground.svelte"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let h = 0
|
||||
export let s = 0
|
||||
export let v = 0
|
||||
export let a = 1
|
||||
|
||||
let palette
|
||||
|
||||
let paletteHeight,
|
||||
paletteWidth = 0
|
||||
|
||||
function handleClick(event) {
|
||||
const { left, top } = palette.getBoundingClientRect()
|
||||
let clickX = event.clientX - left
|
||||
let clickY = event.clientY - top
|
||||
if (
|
||||
clickX > 0 &&
|
||||
clickY > 0 &&
|
||||
clickX < paletteWidth &&
|
||||
clickY < paletteHeight
|
||||
) {
|
||||
let s = (clickX / paletteWidth) * 100
|
||||
let v = 100 - (clickY / paletteHeight) * 100
|
||||
dispatch("change", { s, v })
|
||||
}
|
||||
}
|
||||
|
||||
$: pickerX = (s * paletteWidth) / 100
|
||||
$: pickerY = paletteHeight * ((100 - v) / 100)
|
||||
|
||||
$: paletteGradient = `linear-gradient(to top, rgba(0, 0, 0, 1), transparent),
|
||||
linear-gradient(to left, hsla(${h}, 100%, 50%, ${a}), rgba(255, 255, 255, ${a}))
|
||||
`
|
||||
$: style = `background: ${paletteGradient};`
|
||||
|
||||
$: pickerStyle = `transform: translate(${pickerX - 8}px, ${pickerY - 8}px);`
|
||||
</script>
|
||||
|
||||
<CheckedBackground width="100%">
|
||||
<div
|
||||
bind:this={palette}
|
||||
bind:clientHeight={paletteHeight}
|
||||
bind:clientWidth={paletteWidth}
|
||||
on:click={handleClick}
|
||||
class="palette"
|
||||
{style}>
|
||||
<div class="picker" style={pickerStyle} />
|
||||
</div>
|
||||
</CheckedBackground>
|
||||
|
||||
<style>
|
||||
.palette {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 140px;
|
||||
cursor: crosshair;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.picker {
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: transparent;
|
||||
border: 2px solid white;
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
|
@ -1,86 +0,0 @@
|
|||
<script>
|
||||
import { onMount, createEventDispatcher } from "svelte"
|
||||
import dragable from "./drag.js"
|
||||
|
||||
export let value = 1
|
||||
export let type = "hue"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let slider
|
||||
let sliderWidth = 0
|
||||
|
||||
function onSliderChange(mouseX, isDrag = false) {
|
||||
const { left, width } = slider.getBoundingClientRect()
|
||||
let clickPosition = mouseX - left
|
||||
|
||||
let percentageClick = (clickPosition / sliderWidth).toFixed(2)
|
||||
|
||||
if (percentageClick >= 0 && percentageClick <= 1) {
|
||||
let value = type === "hue" ? 360 * percentageClick : percentageClick
|
||||
|
||||
dispatch("change", { color: value, isDrag })
|
||||
}
|
||||
}
|
||||
|
||||
$: thumbPosition =
|
||||
type === "hue" ? sliderWidth * (value / 360) : sliderWidth * value
|
||||
|
||||
$: style = `transform: translateX(${thumbPosition - 6}px);`
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={slider}
|
||||
bind:clientWidth={sliderWidth}
|
||||
on:click={event => onSliderChange(event.clientX)}
|
||||
class="color-format-slider"
|
||||
class:hue={type === 'hue'}
|
||||
class:alpha={type === 'alpha'}>
|
||||
<div
|
||||
use:dragable
|
||||
on:drag={e => onSliderChange(e.detail, true)}
|
||||
on:dragend
|
||||
class="slider-thumb"
|
||||
{style} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.color-format-slider {
|
||||
position: relative;
|
||||
align-self: center;
|
||||
height: 8px;
|
||||
width: 150px;
|
||||
border-radius: 10px;
|
||||
margin: 10px 0px;
|
||||
border: 1px solid #e8e8ef;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hue {
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
hsl(0, 100%, 50%),
|
||||
hsl(60, 100%, 50%),
|
||||
hsl(120, 100%, 50%),
|
||||
hsl(180, 100%, 50%),
|
||||
hsl(240, 100%, 50%),
|
||||
hsl(300, 100%, 50%),
|
||||
hsl(360, 100%, 50%)
|
||||
);
|
||||
}
|
||||
|
||||
.alpha {
|
||||
background: linear-gradient(to right, transparent, rgb(0 0 0));
|
||||
}
|
||||
|
||||
.slider-thumb {
|
||||
position: absolute;
|
||||
bottom: -3px;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
border: 1px solid #777676;
|
||||
border-radius: 50%;
|
||||
background-color: #ffffff;
|
||||
cursor: grab;
|
||||
}
|
||||
</style>
|
|
@ -1,61 +0,0 @@
|
|||
<script>
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { fade } from "svelte/transition"
|
||||
import CheckedBackground from "./CheckedBackground.svelte"
|
||||
|
||||
export let hovered = false
|
||||
export let color = "#fff"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<div class="space">
|
||||
<CheckedBackground borderRadius="6px">
|
||||
<div
|
||||
in:fade
|
||||
class="swatch"
|
||||
style={`background: ${color};`}
|
||||
on:click|self
|
||||
on:mouseover={() => (hovered = true)}
|
||||
on:mouseleave={() => (hovered = false)}>
|
||||
{#if hovered}
|
||||
<div
|
||||
in:fade
|
||||
class="remove-icon"
|
||||
on:click|self={() => dispatch('removeswatch')}>
|
||||
<span on:click|self={() => dispatch('removeswatch')}>×</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</CheckedBackground>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.swatch {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #dedada;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.space {
|
||||
padding: 3px 5px;
|
||||
}
|
||||
|
||||
.remove-icon {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -5px;
|
||||
right: -4px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background-color: #800000;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
|
@ -1,23 +0,0 @@
|
|||
export default function(node) {
|
||||
function handleMouseDown() {
|
||||
window.addEventListener("mousemove", handleMouseMove)
|
||||
window.addEventListener("mouseup", handleMouseUp)
|
||||
}
|
||||
|
||||
function handleMouseMove(event) {
|
||||
let mouseX = event.clientX
|
||||
node.dispatchEvent(
|
||||
new CustomEvent("drag", {
|
||||
detail: mouseX,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
function handleMouseUp() {
|
||||
window.removeEventListener("mousedown", handleMouseDown)
|
||||
window.removeEventListener("mousemove", handleMouseMove)
|
||||
node.dispatchEvent(new CustomEvent("dragend"))
|
||||
}
|
||||
|
||||
node.addEventListener("mousedown", handleMouseDown)
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
export const buildStyle = styles => {
|
||||
let str = ""
|
||||
for (let s in styles) {
|
||||
if (styles[s]) {
|
||||
let key = convertCamel(s)
|
||||
str += `${key}: ${styles[s]}; `
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
export const convertCamel = str => {
|
||||
return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`)
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
import Colorpreview from "./Colorpreview.svelte"
|
||||
export default Colorpreview
|
|
@ -1,279 +0,0 @@
|
|||
export const isValidHex = str =>
|
||||
/^#(?:[A-F0-9]{3}$|[A-F0-9]{4}$|[A-F0-9]{6}$|[A-F0-9]{8})$/gi.test(str)
|
||||
|
||||
const getHexaValues = hexString => {
|
||||
if (hexString.length <= 5) {
|
||||
let hexArr = hexString.match(/[A-F0-9]/gi)
|
||||
let t = hexArr.map(c => (c += c))
|
||||
return t
|
||||
} else {
|
||||
return hexString.match(/[A-F0-9]{2}/gi)
|
||||
}
|
||||
}
|
||||
|
||||
export const isValidRgb = str => {
|
||||
const hasValidStructure = /^(?:rgba\(|rgb\()(?:[0-9,\s]|\.(?=\d))*\)$/gi.test(
|
||||
str
|
||||
)
|
||||
if (hasValidStructure) {
|
||||
return testRgbaValues(str.toLowerCase())
|
||||
}
|
||||
}
|
||||
|
||||
const findNonNumericChars = /[a-z()\s]/gi
|
||||
|
||||
export const getNumericValues = str =>
|
||||
str
|
||||
.replace(findNonNumericChars, "")
|
||||
.split(",")
|
||||
.map(v => (v !== "" ? v : undefined))
|
||||
|
||||
export const testRgbaValues = str => {
|
||||
const rgba = getNumericValues(str)
|
||||
const [r, g, b, a] = rgba
|
||||
|
||||
let isValidLengthRange =
|
||||
(str.startsWith("rgb(") && rgba.length === 3) ||
|
||||
(str.startsWith("rgba(") && rgba.length === 4)
|
||||
let isValidColorRange = [r, g, b].every(v => v >= 0 && v <= 255)
|
||||
let isValidAlphaRange = str.startsWith("rgba(")
|
||||
? `${a}`.length <= 4 && a >= 0 && a <= 1
|
||||
: true
|
||||
|
||||
return isValidLengthRange && isValidColorRange && isValidAlphaRange
|
||||
}
|
||||
|
||||
export const isValidHsl = str => {
|
||||
const hasValidStructure = /^(?:hsl\(|hsla\()(?:[0-9,%\s]|\.(?=\d))*\)$/gi.test(
|
||||
str
|
||||
)
|
||||
if (hasValidStructure) {
|
||||
return testHslaValues(str.toLowerCase())
|
||||
}
|
||||
}
|
||||
|
||||
export const testHslaValues = str => {
|
||||
const hsla = getNumericValues(str)
|
||||
const [h, s, l, a] = hsla
|
||||
const isUndefined = [h, s, l].some(v => v === undefined)
|
||||
|
||||
if (isUndefined) return false
|
||||
|
||||
let isValidLengthRange =
|
||||
(str.startsWith("hsl(") && hsla.length === 3) ||
|
||||
(str.startsWith("hsla(") && hsla.length === 4)
|
||||
let isValidHue = h >= 0 && h <= 360
|
||||
let isValidSatLum = [s, l].every(
|
||||
v => v.endsWith("%") && parseInt(v) >= 0 && parseInt(v) <= 100
|
||||
)
|
||||
let isValidAlphaRange = str.startsWith("hsla(")
|
||||
? `${a}`.length <= 4 && a >= 0 && a <= 1
|
||||
: true
|
||||
|
||||
return isValidLengthRange && isValidHue && isValidSatLum && isValidAlphaRange
|
||||
}
|
||||
|
||||
export const getColorFormat = color => {
|
||||
if (typeof color === "string") {
|
||||
if (isValidHex(color)) {
|
||||
return "hex"
|
||||
} else if (isValidRgb(color)) {
|
||||
return "rgb"
|
||||
} else if (isValidHsl(color)) {
|
||||
return "hsl"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const convertToHSVA = (value, format) => {
|
||||
switch (format) {
|
||||
case "hex":
|
||||
return getAndConvertHexa(value)
|
||||
case "rgb":
|
||||
return getAndConvertRgba(value)
|
||||
case "hsl":
|
||||
return getAndConvertHsla(value)
|
||||
}
|
||||
}
|
||||
|
||||
export const convertHsvaToFormat = (hsva, format) => {
|
||||
switch (format) {
|
||||
case "hex":
|
||||
return hsvaToHexa(hsva, true)
|
||||
case "rgb":
|
||||
return hsvaToRgba(hsva, true)
|
||||
case "hsl":
|
||||
return hsvaToHsla(hsva)
|
||||
}
|
||||
}
|
||||
|
||||
export const getAndConvertHexa = color => {
|
||||
let [rHex, gHex, bHex, aHex] = getHexaValues(color)
|
||||
return hexaToHSVA([rHex, gHex, bHex], aHex)
|
||||
}
|
||||
|
||||
export const getAndConvertRgba = color => {
|
||||
let rgba = getNumericValues(color)
|
||||
return rgbaToHSVA(rgba)
|
||||
}
|
||||
|
||||
export const getAndConvertHsla = color => {
|
||||
let hsla = getNumericValues(color)
|
||||
return hslaToHSVA(hsla)
|
||||
}
|
||||
|
||||
export const hexaToHSVA = (hex, alpha = "FF") => {
|
||||
const rgba = hex
|
||||
.map(v => parseInt(v, 16))
|
||||
.concat(Number((parseInt(alpha, 16) / 255).toFixed(2)))
|
||||
return rgbaToHSVA(rgba)
|
||||
}
|
||||
|
||||
export const rgbaToHSVA = rgba => {
|
||||
const [r, g, b, a = 1] = rgba
|
||||
let hsv = _rgbToHSV([r, g, b])
|
||||
return [...hsv, a].map(x => parseFloat(x))
|
||||
}
|
||||
|
||||
export const hslaToHSVA = ([h, s, l, a = 1]) => {
|
||||
let sat = s.replace(/%/, "")
|
||||
let lum = l.replace(/%/, "")
|
||||
let hsv = _hslToHSV([h, sat, lum])
|
||||
return [...hsv, a].map(x => parseFloat(x))
|
||||
}
|
||||
|
||||
export const hsvaToHexa = (hsva, asString = false) => {
|
||||
const [r, g, b, a] = hsvaToRgba(hsva)
|
||||
|
||||
const hexa = [r, g, b]
|
||||
.map(v => {
|
||||
let hex = Math.round(v).toString(16)
|
||||
return hex.length === 1 ? `0${hex}` : hex
|
||||
})
|
||||
.concat(Math.round(a * 255).toString(16))
|
||||
return asString ? `#${hexa.join("")}` : hexa
|
||||
}
|
||||
|
||||
export const hsvaToRgba = ([h, s, v, a = 1], asString = false) => {
|
||||
let rgb = _hsvToRgb([h, s, v]).map(x => Math.round(x))
|
||||
let rgba = [...rgb, a < 1 ? _fixNum(a, 2) : a]
|
||||
return asString ? `rgba(${rgba.join(",")})` : rgba
|
||||
}
|
||||
|
||||
export const hsvaToHsla = ([h, s, v, a = 1]) => {
|
||||
let [hue, sat, lum] = _hsvToHSL([h, s, v])
|
||||
let hsla = [hue, sat + "%", lum + "%", a < 1 ? _fixNum(a, 2) : a]
|
||||
return `hsla(${hsla.join(",")})`
|
||||
}
|
||||
|
||||
export const _hslToHSV = hsl => {
|
||||
const h = hsl[0]
|
||||
let s = hsl[1] / 100
|
||||
let l = hsl[2] / 100
|
||||
let smin = s
|
||||
const lmin = Math.max(l, 0.01)
|
||||
|
||||
l *= 2
|
||||
s *= l <= 1 ? l : 2 - l
|
||||
smin *= lmin <= 1 ? lmin : 2 - lmin
|
||||
const v = (l + s) / 2
|
||||
const sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s)
|
||||
|
||||
return [h, sv * 100, v * 100]
|
||||
}
|
||||
|
||||
//Credit : https://github.com/Qix-/color-convert
|
||||
export const _rgbToHSV = rgb => {
|
||||
let rdif
|
||||
let gdif
|
||||
let bdif
|
||||
let h
|
||||
let s
|
||||
|
||||
const r = rgb[0] / 255
|
||||
const g = rgb[1] / 255
|
||||
const b = rgb[2] / 255
|
||||
const v = Math.max(r, g, b)
|
||||
const diff = v - Math.min(r, g, b)
|
||||
const diffc = function(c) {
|
||||
return (v - c) / 6 / diff + 1 / 2
|
||||
}
|
||||
|
||||
if (diff === 0) {
|
||||
h = 0
|
||||
s = 0
|
||||
} else {
|
||||
s = diff / v
|
||||
rdif = diffc(r)
|
||||
gdif = diffc(g)
|
||||
bdif = diffc(b)
|
||||
|
||||
if (r === v) {
|
||||
h = bdif - gdif
|
||||
} else if (g === v) {
|
||||
h = 1 / 3 + rdif - bdif
|
||||
} else if (b === v) {
|
||||
h = 2 / 3 + gdif - rdif
|
||||
}
|
||||
|
||||
if (h < 0) {
|
||||
h += 1
|
||||
} else if (h > 1) {
|
||||
h -= 1
|
||||
}
|
||||
}
|
||||
|
||||
const hsvResult = [h * 360, s * 100, v * 100].map(v => Math.round(v))
|
||||
return hsvResult
|
||||
}
|
||||
|
||||
//Credit : https://github.com/Qix-/color-convert
|
||||
export const _hsvToRgb = hsv => {
|
||||
const h = hsv[0] / 60
|
||||
const s = hsv[1] / 100
|
||||
let v = hsv[2] / 100
|
||||
const hi = Math.floor(h) % 6
|
||||
|
||||
const f = h - Math.floor(h)
|
||||
const p = 255 * v * (1 - s)
|
||||
const q = 255 * v * (1 - s * f)
|
||||
const t = 255 * v * (1 - s * (1 - f))
|
||||
v *= 255
|
||||
|
||||
switch (hi) {
|
||||
case 0:
|
||||
return [v, t, p]
|
||||
case 1:
|
||||
return [q, v, p]
|
||||
case 2:
|
||||
return [p, v, t]
|
||||
case 3:
|
||||
return [p, q, v]
|
||||
case 4:
|
||||
return [t, p, v]
|
||||
case 5:
|
||||
return [v, p, q]
|
||||
}
|
||||
}
|
||||
|
||||
//Credit : https://github.com/Qix-/color-convert
|
||||
export const _hsvToHSL = hsv => {
|
||||
const h = hsv[0]
|
||||
const s = hsv[1] / 100
|
||||
const v = hsv[2] / 100
|
||||
const vmin = Math.max(v, 0.01)
|
||||
let sl
|
||||
let l
|
||||
|
||||
l = (2 - s) * v
|
||||
const lmin = (2 - s) * vmin
|
||||
sl = s * vmin
|
||||
sl /= lmin <= 1 ? lmin : 2 - lmin
|
||||
sl = sl || 0
|
||||
l /= 2
|
||||
|
||||
return [_fixNum(h, 0), _fixNum(sl * 100, 0), _fixNum(l * 100, 0)]
|
||||
}
|
||||
|
||||
export const _fixNum = (value, decimalPlaces) =>
|
||||
Number(parseFloat(value).toFixed(decimalPlaces))
|
|
@ -1,106 +0,0 @@
|
|||
import { getColorFormat, convertToHSVA, convertHsvaToFormat } from "./utils"
|
||||
|
||||
describe("convertToHSVA - convert to hsva from format", () => {
|
||||
test("convert from hexa", () => {
|
||||
expect(convertToHSVA("#f222d382", "hex")).toEqual([309, 86, 95, 0.51])
|
||||
})
|
||||
|
||||
test("convert from hex", () => {
|
||||
expect(convertToHSVA("#f222d3", "hex")).toEqual([309, 86, 95, 1])
|
||||
})
|
||||
|
||||
test("convert from rgba", () => {
|
||||
expect(convertToHSVA("rgba(242, 34, 211, 1)", "rgb")).toEqual([
|
||||
309,
|
||||
86,
|
||||
95,
|
||||
1,
|
||||
])
|
||||
})
|
||||
|
||||
test("convert from rgb", () => {
|
||||
expect(convertToHSVA("rgb(150, 80, 255)", "rgb")).toEqual([264, 69, 100, 1])
|
||||
})
|
||||
|
||||
test("convert from from hsl", () => {
|
||||
expect(convertToHSVA("hsl(264, 100%, 65.7%)", "hsl")).toEqual([
|
||||
264,
|
||||
68.6,
|
||||
100,
|
||||
1,
|
||||
])
|
||||
})
|
||||
|
||||
test("convert from from hsla", () => {
|
||||
expect(convertToHSVA("hsla(264, 100%, 65.7%, 0.51)", "hsl")).toEqual([
|
||||
264,
|
||||
68.6,
|
||||
100,
|
||||
0.51,
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("convertHsvaToFormat - convert from hsva to format", () => {
|
||||
test("Convert to hexa", () => {
|
||||
expect(convertHsvaToFormat([264, 68.63, 100, 0.5], "hex")).toBe("#9650ff80")
|
||||
})
|
||||
|
||||
test("Convert to rgba", () => {
|
||||
expect(convertHsvaToFormat([264, 68.63, 100, 0.75], "rgb")).toBe(
|
||||
"rgba(150,80,255,0.75)"
|
||||
)
|
||||
})
|
||||
|
||||
test("Convert to hsla", () => {
|
||||
expect(convertHsvaToFormat([264, 68.63, 100, 1], "hsl")).toBe(
|
||||
"hsla(264,100%,66%,1)"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Get Color Format", () => {
|
||||
test("Testing valid hex string", () => {
|
||||
expect(getColorFormat("#FFF")).toBe("hex")
|
||||
})
|
||||
|
||||
test("Testing invalid hex string", () => {
|
||||
expect(getColorFormat("#FFZ")).toBeUndefined()
|
||||
})
|
||||
|
||||
test("Testing valid hex with alpha", () => {
|
||||
expect(getColorFormat("#FF00BB80")).toBe("hex")
|
||||
})
|
||||
|
||||
test("Test valid rgb value", () => {
|
||||
expect(getColorFormat("RGB(255, 20, 50)")).toBe("rgb")
|
||||
})
|
||||
|
||||
test("Testing invalid rgb value", () => {
|
||||
expect(getColorFormat("rgb(255, 0)")).toBeUndefined()
|
||||
})
|
||||
|
||||
test("Testing rgb value with alpha", () => {
|
||||
expect(getColorFormat("rgba(255, 0, 50, 0.5)")).toBe("rgb")
|
||||
})
|
||||
|
||||
test("Testing rgb value with incorrectly provided alpha", () => {
|
||||
expect(getColorFormat("rgb(255, 0, 50, 0.5)")).toBeUndefined()
|
||||
})
|
||||
|
||||
test("Testing invalid hsl value", () => {
|
||||
expect(getColorFormat("hsla(255, 0)")).toBeUndefined()
|
||||
})
|
||||
|
||||
test("Testing hsla value with alpha", () => {
|
||||
expect(getColorFormat("hsla(150, 60%, 50%, 0.5)")).toBe("hsl")
|
||||
})
|
||||
|
||||
test("Testing hsl value with incorrectly provided alpha", () => {
|
||||
expect(getColorFormat("hsl(150, 0, 50, 0.5)")).toBeUndefined()
|
||||
})
|
||||
|
||||
test("Testing out of bounds hsl", () => {
|
||||
expect(getColorFormat("hsl(375, 0, 50)")).toBeUndefined()
|
||||
})
|
||||
})
|
|
@ -23,8 +23,8 @@
|
|||
let codeEditor
|
||||
let flattenedPanel = flattenComponents(panelStructure.categories)
|
||||
let categories = [
|
||||
{ value: "design", name: "Design" },
|
||||
{ value: "settings", name: "Settings" },
|
||||
{ value: "design", name: "Design" },
|
||||
{ value: "events", name: "Events" },
|
||||
]
|
||||
let selectedCategory = categories[0]
|
||||
|
|
|
@ -61,8 +61,9 @@
|
|||
|
||||
<style>
|
||||
.panel {
|
||||
padding: 20px 0px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 20px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: 20px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import { pipe } from "components/common/core"
|
||||
import { store } from "builderStore"
|
||||
import { ArrowDownIcon, ShapeIcon } from "components/common/Icons/"
|
||||
import ScreenDropdownMenu from "./ScreenDropdownMenu.svelte"
|
||||
|
||||
export let screens = []
|
||||
|
||||
|
@ -31,7 +32,7 @@
|
|||
<div class="root">
|
||||
{#each screens as screen}
|
||||
<div
|
||||
class="budibase__nav-item component"
|
||||
class="budibase__nav-item screen-header-row"
|
||||
class:selected={$store.currentComponentInfo._id === screen.props._id}
|
||||
on:click|stopPropagation={() => changeScreen(screen)}>
|
||||
|
||||
|
@ -46,6 +47,10 @@
|
|||
<i class="ri-artboard-2-fill icon" />
|
||||
|
||||
<span class="title">{screen.props._instanceName}</span>
|
||||
|
||||
<div class="dropdown-menu">
|
||||
<ScreenDropdownMenu {screen} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if $store.currentPreviewItem.props._instanceName && $store.currentPreviewItem.props._instanceName === screen.props._instanceName && screen.props._children}
|
||||
|
@ -63,10 +68,16 @@
|
|||
color: var(--ink);
|
||||
}
|
||||
|
||||
.screen-header-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-left: 14px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.icon {
|
||||
|
@ -89,4 +100,20 @@
|
|||
.rotate :global(svg) {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
display: none;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
color: var(--ink);
|
||||
padding: 0px 5px;
|
||||
border-style: none;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.budibase__nav-item:hover .dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
class:selected={currentComponent === component}
|
||||
style="padding-left: {level * 20 + 40}px">
|
||||
<div class="nav-item">
|
||||
<i class="icon ri-arrow-right-circle-fill" />
|
||||
<i class="icon ri-arrow-right-circle-line" />
|
||||
{isScreenslot(component._component) ? 'Screenslot' : component._instanceName}
|
||||
</div>
|
||||
<div class="actions">
|
||||
|
@ -73,7 +73,7 @@
|
|||
grid-template-columns: 1fr auto auto auto;
|
||||
padding: 0px 5px 0px 15px;
|
||||
margin: auto 0px;
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
height: 36px;
|
||||
align-items: center;
|
||||
}
|
||||
|
|
|
@ -21,21 +21,17 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
cursor: pointer;
|
||||
margin-bottom: 8px;
|
||||
padding: 8px 0px 16px 0px;
|
||||
width: 110px;
|
||||
padding: 12px 16px 16px 16px;
|
||||
height: 80px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 8px;
|
||||
background-color: var(--grey-1);
|
||||
border-radius: 3px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.item-item:hover {
|
||||
background: var(--grey-2);
|
||||
border-radius: 3px;
|
||||
transition: all 0.2s;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
|
@ -51,6 +47,7 @@
|
|||
.item-name {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
i {
|
||||
|
|
|
@ -23,21 +23,26 @@
|
|||
{#if !list.isCategory}
|
||||
<button class="back-button" on:click={() => (list = category)}>Back</button>
|
||||
{/if}
|
||||
|
||||
{#each list.children as item}
|
||||
<Item {item} on:click={() => handleClick(item)} />
|
||||
{/each}
|
||||
|
||||
<style>
|
||||
.back-button {
|
||||
font-size: 16px;
|
||||
width: 100%;
|
||||
grid-column: 1 / span 2;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
height: 40px;
|
||||
border-radius: 3px;
|
||||
border: solid 1px #e8e8ef;
|
||||
height: 36px;
|
||||
border-radius: 5px;
|
||||
border: solid 1px var(--grey-3);
|
||||
background: white;
|
||||
margin-bottom: 20px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
font-family: Inter;
|
||||
transition: all 0.3ms;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background: var(--grey-1);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -82,6 +82,7 @@
|
|||
function handleClick(val) {
|
||||
value = val
|
||||
onChange(value)
|
||||
toggleSelect(false)
|
||||
}
|
||||
|
||||
$: menuStyle = buildStyle({
|
||||
|
@ -110,7 +111,7 @@
|
|||
bind:this={select}
|
||||
class="bb-select-container"
|
||||
on:click={() => toggleSelect(!open)}>
|
||||
<div bind:this={selectAnchor} class="bb-select-anchor selected">
|
||||
<div bind:this={selectAnchor} title={value} class="bb-select-anchor selected">
|
||||
<span>{displayLabel}</span>
|
||||
<i bind:this={icon} class="ri-arrow-down-s-fill" />
|
||||
</div>
|
||||
|
@ -176,6 +177,7 @@
|
|||
background-color: var(--grey-2);
|
||||
border-radius: 5px;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bb-select-anchor > span {
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
<script>
|
||||
import { MoreIcon } from "components/common/Icons"
|
||||
import { store } from "builderStore"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import UIkit from "uikit"
|
||||
import api from "builderStore/api"
|
||||
import Portal from "svelte-portal"
|
||||
import { DropdownMenu } from "@budibase/bbui"
|
||||
|
||||
export let screen
|
||||
|
||||
let confirmDeleteDialog
|
||||
let dropdown
|
||||
let buttonForDropdown
|
||||
|
||||
const hideDropdown = () => {
|
||||
dropdown.hide()
|
||||
}
|
||||
|
||||
const deleteScreen = () => {
|
||||
store.update(s => {
|
||||
const screens = s.screens.filter(c => c.name !== screen.name)
|
||||
s.screens = screens
|
||||
if (s.currentPreviewItem.name === screen.name) {
|
||||
s.currentPreviewItem = s.pages[s.currentPageName]
|
||||
s.currentFrontEndType = "page"
|
||||
}
|
||||
|
||||
api.delete(
|
||||
`/_builder/api/pages/${s.currentPageName}/screens/${screen.name}`
|
||||
)
|
||||
|
||||
return s
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="root boundary" on:click|stopPropagation={() => {}}>
|
||||
<button on:click={() => dropdown.show()} bind:this={buttonForDropdown}>
|
||||
<MoreIcon />
|
||||
</button>
|
||||
<DropdownMenu bind:this={dropdown} anchor={buttonForDropdown}>
|
||||
<ul class="menu" on:click={hideDropdown}>
|
||||
<li class="item" on:click={() => confirmDeleteDialog.show()}>
|
||||
<i class="icon ri-delete-bin-2-line" />
|
||||
Delete
|
||||
</li>
|
||||
</ul>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:this={confirmDeleteDialog}
|
||||
title="Confirm Delete"
|
||||
body={`Are you sure you wish to delete the screen '${screen.props._instanceName}' ?`}
|
||||
okText="Delete Screen"
|
||||
onOk={deleteScreen} />
|
||||
|
||||
<style>
|
||||
.root {
|
||||
overflow: hidden;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
.root button {
|
||||
border-style: none;
|
||||
border-radius: 2px;
|
||||
padding: 5px;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
color: var(--ink);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.menu {
|
||||
z-index: 100000;
|
||||
overflow: visible;
|
||||
padding: 12px 0px;
|
||||
border-radius: 5px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.menu li {
|
||||
border-style: none;
|
||||
background-color: transparent;
|
||||
list-style-type: none;
|
||||
padding: 4px 16px;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.menu li:not(.disabled) {
|
||||
cursor: pointer;
|
||||
color: var(--grey-7);
|
||||
}
|
||||
|
||||
.menu li:not(.disabled):hover {
|
||||
color: var(--ink);
|
||||
background-color: var(--grey-1);
|
||||
}
|
||||
</style>
|
|
@ -2,7 +2,6 @@
|
|||
import PropertyControl from "./PropertyControl.svelte"
|
||||
import InputGroup from "../common/Inputs/InputGroup.svelte"
|
||||
import Input from "../common/Input.svelte"
|
||||
import Colorpicker from "../common/Colorpicker.svelte"
|
||||
import { goto } from "@sveltech/routify"
|
||||
import { excludeProps } from "./propertyCategories.js"
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import { backendUiStore } from "builderStore"
|
||||
import IconButton from "../common/IconButton.svelte"
|
||||
import Input from "../common/Input.svelte"
|
||||
import Colorpicker from "../common/Colorpicker.svelte"
|
||||
|
||||
export let value = ""
|
||||
export let onChanged = () => {}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Input from "../common/Input.svelte"
|
||||
import OptionSelect from "./OptionSelect.svelte"
|
||||
import FlatButtonGroup from "./FlatButtonGroup.svelte"
|
||||
import Colorpicker from "./Colorpicker"
|
||||
import Colorpicker from "@budibase/colorpicker"
|
||||
/*
|
||||
TODO: Allow for default values for all properties
|
||||
*/
|
||||
|
|
|
@ -11,23 +11,11 @@ export default {
|
|||
name: "Basic",
|
||||
isCategory: true,
|
||||
children: [
|
||||
{
|
||||
_component: "@budibase/standard-components/embed",
|
||||
icon: "ri-code-line",
|
||||
name: "Embed",
|
||||
description: "Embed content from 3rd party sources",
|
||||
properties: {
|
||||
design: {
|
||||
...all,
|
||||
},
|
||||
settings: [{ label: "Embed", key: "embed", control: Input }],
|
||||
},
|
||||
},
|
||||
{
|
||||
_component: "@budibase/standard-components/container",
|
||||
name: "Container",
|
||||
description: "This component contains things within itself",
|
||||
icon: "ri-layout-row-fill",
|
||||
icon: "ri-layout-row-line",
|
||||
commonProps: {},
|
||||
children: [],
|
||||
properties: {
|
||||
|
@ -56,10 +44,22 @@ export default {
|
|||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
_component: "@budibase/standard-components/embed",
|
||||
icon: "ri-code-line",
|
||||
name: "Embed",
|
||||
description: "Embed content from 3rd party sources",
|
||||
properties: {
|
||||
design: {
|
||||
...all,
|
||||
},
|
||||
settings: [{ label: "Embed", key: "embed", control: Input }],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Text",
|
||||
description: "This is a simple text component",
|
||||
icon: "ri-t-box-fill",
|
||||
icon: "ri-t-box-line",
|
||||
commonProps: {},
|
||||
children: [
|
||||
{
|
||||
|
@ -123,7 +123,7 @@ export default {
|
|||
{
|
||||
name: "Input",
|
||||
description: "These components handle user input.",
|
||||
icon: "ri-edit-box-fill",
|
||||
icon: "ri-edit-box-line",
|
||||
commonProps: {},
|
||||
children: [
|
||||
{
|
||||
|
@ -131,7 +131,7 @@ export default {
|
|||
name: "Textfield",
|
||||
description:
|
||||
"A textfield component that allows the user to input text.",
|
||||
icon: "ri-edit-box-fill",
|
||||
icon: "ri-edit-box-line",
|
||||
properties: {
|
||||
design: { ...all },
|
||||
settings: [
|
||||
|
@ -149,7 +149,7 @@ export default {
|
|||
_component: "@budibase/standard-components/checkbox",
|
||||
name: "Checkbox",
|
||||
description: "A selectable checkbox component",
|
||||
icon: "ri-checkbox-fill",
|
||||
icon: "ri-checkbox-line",
|
||||
properties: {
|
||||
design: { ...all },
|
||||
settings: [{ label: "Label", key: "label", control: Input }],
|
||||
|
@ -170,7 +170,7 @@ export default {
|
|||
name: "Select",
|
||||
description:
|
||||
"A select component for choosing from different options",
|
||||
icon: "ri-file-list-fill",
|
||||
icon: "ri-file-list-line",
|
||||
properties: {
|
||||
design: { ...all },
|
||||
settings: [],
|
||||
|
@ -182,7 +182,7 @@ export default {
|
|||
_component: "@budibase/standard-components/button",
|
||||
name: "Button",
|
||||
description: "A basic html button that is ready for styling",
|
||||
icon: "ri-radio-button-fill",
|
||||
icon: "ri-share-box-line",
|
||||
children: [],
|
||||
properties: {
|
||||
design: {
|
||||
|
@ -203,23 +203,23 @@ export default {
|
|||
_component: "@budibase/standard-components/image",
|
||||
name: "Image",
|
||||
description: "A basic component for displaying images",
|
||||
icon: "ri-image-fill",
|
||||
icon: "ri-image-line",
|
||||
children: [],
|
||||
properties: {
|
||||
design: { ...all },
|
||||
settings: [{ label: "URL", key: "url", control: Input }],
|
||||
},
|
||||
},
|
||||
{
|
||||
_component: "@budibase/standard-components/icon",
|
||||
name: "Icon",
|
||||
description: "A basic component for displaying icons",
|
||||
icon: "ri-sun-fill",
|
||||
children: [],
|
||||
properties: {
|
||||
design: { ...all },
|
||||
},
|
||||
},
|
||||
// {
|
||||
// _component: "@budibase/standard-components/icon",
|
||||
// name: "Icon",
|
||||
// description: "A basic component for displaying icons",
|
||||
// icon: "ri-sun-fill",
|
||||
// children: [],
|
||||
// properties: {
|
||||
// design: { ...all },
|
||||
// },
|
||||
// },
|
||||
{
|
||||
_component: "@budibase/standard-components/link",
|
||||
name: "Link",
|
||||
|
@ -251,7 +251,7 @@ export default {
|
|||
name: "Card",
|
||||
description:
|
||||
"A basic card component that can contain content and actions.",
|
||||
icon: "ri-layout-bottom-fill",
|
||||
icon: "ri-layout-bottom-line",
|
||||
children: [],
|
||||
properties: {
|
||||
design: { ...all },
|
||||
|
@ -283,34 +283,11 @@ export default {
|
|||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Login",
|
||||
_component: "@budibase/standard-components/login",
|
||||
description:
|
||||
"A component that automatically generates a login screen for your app.",
|
||||
icon: "ri-login-box-fill",
|
||||
children: [],
|
||||
properties: {
|
||||
design: { ...all },
|
||||
settings: [
|
||||
{
|
||||
label: "Name",
|
||||
key: "name",
|
||||
control: Input,
|
||||
},
|
||||
{
|
||||
label: "Logo",
|
||||
key: "logo",
|
||||
control: Input,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Table",
|
||||
_component: "@budibase/standard-components/datatable",
|
||||
description: "A component that generates a table from your data.",
|
||||
icon: "ri-archive-drawer-fill",
|
||||
icon: "ri-archive-drawer-line",
|
||||
properties: {
|
||||
design: { ...all },
|
||||
settings: [
|
||||
|
@ -319,6 +296,7 @@ export default {
|
|||
{ label: "Border Color", key: "borderColor", control: Input },
|
||||
{ label: "TH Color", key: "backgroundColor", control: Input },
|
||||
{ label: "TH Font Color", key: "color", control: Input },
|
||||
{ label: "Table", key: "model", control: ModelSelect }
|
||||
],
|
||||
},
|
||||
children: [],
|
||||
|
@ -326,17 +304,31 @@ export default {
|
|||
{
|
||||
name: "Form",
|
||||
description: "A component that generates a form from your data.",
|
||||
icon: "ri-file-edit-fill",
|
||||
icon: "ri-file-edit-line",
|
||||
commonProps: {},
|
||||
children: [
|
||||
{
|
||||
_component: "@budibase/standard-components/dataform",
|
||||
name: "Form Basic",
|
||||
icon: "ri-file-edit-fill",
|
||||
icon: "ri-file-edit-line",
|
||||
properties: {
|
||||
design: { ...all },
|
||||
settings: [
|
||||
{ label: "Model", key: "model", control: ModelSelect },
|
||||
{
|
||||
label: "Table",
|
||||
key: "model",
|
||||
control: ModelSelect,
|
||||
},
|
||||
{
|
||||
label: "Title",
|
||||
key: "title",
|
||||
control: Input,
|
||||
},
|
||||
{
|
||||
label: "Button Text",
|
||||
key: "buttonText",
|
||||
control: Input,
|
||||
},
|
||||
],
|
||||
},
|
||||
template: {
|
||||
|
@ -348,15 +340,25 @@ export default {
|
|||
{
|
||||
_component: "@budibase/standard-components/dataformwide",
|
||||
name: "Form Wide",
|
||||
icon: "ri-file-edit-fill",
|
||||
icon: "ri-file-edit-line",
|
||||
properties: {
|
||||
design: { ...all },
|
||||
settings: [
|
||||
{
|
||||
label: "Model",
|
||||
label: "Table",
|
||||
key: "model",
|
||||
control: ModelSelect,
|
||||
},
|
||||
{
|
||||
label: "Title",
|
||||
key: "title",
|
||||
control: Input,
|
||||
},
|
||||
{
|
||||
label: "Button Text",
|
||||
key: "buttonText",
|
||||
control: Input,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -366,11 +368,11 @@ export default {
|
|||
name: "Chart",
|
||||
_component: "@budibase/standard-components/datachart",
|
||||
description: "Shiny chart",
|
||||
icon: "ri-bar-chart-fill",
|
||||
icon: "ri-bar-chart-line",
|
||||
properties: {
|
||||
design: { ...all },
|
||||
settings: [
|
||||
{ label: "Model", key: "model", control: ModelSelect },
|
||||
{ label: "Table", key: "model", control: ModelSelect },
|
||||
{
|
||||
label: "Chart Type",
|
||||
key: "type",
|
||||
|
@ -394,36 +396,48 @@ export default {
|
|||
},
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
name: "Data List",
|
||||
_component: "@budibase/standard-components/datalist",
|
||||
description: "Shiny list",
|
||||
icon: "ri-file-list-fill",
|
||||
properties: {
|
||||
design: { ...all },
|
||||
settings: [{ label: "Model", key: "model", control: ModelSelect }],
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
// {
|
||||
// name: "Data List",
|
||||
// _component: "@budibase/standard-components/datalist",
|
||||
// description: "Shiny list",
|
||||
// icon: "ri-file-list-line",
|
||||
// properties: {
|
||||
// design: { ...all },
|
||||
// settings: [{ label: "Table", key: "model", control: ModelSelect }],
|
||||
// },
|
||||
// children: [],
|
||||
// },
|
||||
{
|
||||
name: "List",
|
||||
_component: "@budibase/standard-components/list",
|
||||
description: "Shiny list",
|
||||
icon: "ri-file-list-fill",
|
||||
description: "Renders all children once per record, of a given table",
|
||||
icon: "ri-file-list-line",
|
||||
properties: {
|
||||
design: { ...all },
|
||||
settings: [{ label: "Model", key: "model", control: ModelSelect }],
|
||||
settings: [{ label: "Table", key: "model", control: ModelSelect }],
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
name: "Map",
|
||||
_component: "@budibase/standard-components/datamap",
|
||||
description: "Shiny map",
|
||||
icon: "ri-map-pin-fill",
|
||||
properties: { design: { ...all } },
|
||||
name: "Record Detail",
|
||||
_component: "@budibase/standard-components/recorddetail",
|
||||
description:
|
||||
"Loads a record, using an id from the URL, which can be used with {{ context }}, in children",
|
||||
icon: "ri-profile-line",
|
||||
properties: {
|
||||
design: { ...all },
|
||||
settings: [{ label: "Table", key: "model", control: ModelSelect }],
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
// {
|
||||
// name: "Map",
|
||||
// _component: "@budibase/standard-components/datamap",
|
||||
// description: "Shiny map",
|
||||
// icon: "ri-map-pin-line",
|
||||
// properties: { design: { ...all } },
|
||||
// children: [],
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -432,10 +446,10 @@ export default {
|
|||
children: [
|
||||
{
|
||||
_component: "##builtin/screenslot",
|
||||
name: "Screenslot",
|
||||
name: "Screen Slot",
|
||||
description:
|
||||
"This component is a placeholder for the rendering of a screen within a page.",
|
||||
icon: "ri-crop-2-fill",
|
||||
icon: "ri-crop-2-line",
|
||||
properties: { design: { ...all } },
|
||||
commonProps: {},
|
||||
children: [],
|
||||
|
@ -445,7 +459,7 @@ export default {
|
|||
_component: "@budibase/standard-components/Navigation",
|
||||
description:
|
||||
"A component for handling the navigation within your app.",
|
||||
icon: "ri-navigation-fill",
|
||||
icon: "ri-navigation-line",
|
||||
children: [],
|
||||
properties: {
|
||||
design: { ...all },
|
||||
|
@ -457,6 +471,39 @@ export default {
|
|||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Login",
|
||||
_component: "@budibase/standard-components/login",
|
||||
description:
|
||||
"A component that automatically generates a login screen for your app.",
|
||||
icon: "ri-login-box-line",
|
||||
children: [],
|
||||
properties: {
|
||||
design: { ...all },
|
||||
settings: [
|
||||
{
|
||||
label: "Name",
|
||||
key: "name",
|
||||
control: Input,
|
||||
},
|
||||
{
|
||||
label: "Logo",
|
||||
key: "logo",
|
||||
control: Input,
|
||||
},
|
||||
{
|
||||
label: "Title",
|
||||
key: "title",
|
||||
control: Input,
|
||||
},
|
||||
{
|
||||
label: "Button Text",
|
||||
key: "buttonText",
|
||||
control: Input,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
</script>
|
||||
|
||||
<section>
|
||||
<Button purple wide on:click{newWorkflow}>Create New Workflow</Button>
|
||||
<Button purple wide on:click={newWorkflow}>Create New Workflow</Button>
|
||||
<ul>
|
||||
{#each $workflowStore.workflows as workflow}
|
||||
<li
|
||||
|
@ -91,7 +91,7 @@
|
|||
}
|
||||
|
||||
.workflow-item.selected {
|
||||
background: var(--blue-light);
|
||||
background: var(--grey-2);
|
||||
}
|
||||
|
||||
.new-workflow-button {
|
||||
|
|
|
@ -6,7 +6,7 @@ export const FIELDS = {
|
|||
constraints: {
|
||||
type: "string",
|
||||
length: {},
|
||||
presence: false,
|
||||
presence: { allowEmpty: true },
|
||||
},
|
||||
},
|
||||
NUMBER: {
|
||||
|
@ -15,7 +15,7 @@ export const FIELDS = {
|
|||
type: "number",
|
||||
constraints: {
|
||||
type: "number",
|
||||
presence: false,
|
||||
presence: { allowEmpty: true },
|
||||
numericality: {},
|
||||
},
|
||||
},
|
||||
|
@ -25,7 +25,7 @@ export const FIELDS = {
|
|||
type: "boolean",
|
||||
constraints: {
|
||||
type: "boolean",
|
||||
presence: false,
|
||||
presence: { allowEmpty: true },
|
||||
},
|
||||
},
|
||||
// OPTIONS: {
|
||||
|
@ -34,7 +34,7 @@ export const FIELDS = {
|
|||
// type: "options",
|
||||
// constraints: {
|
||||
// type: "string",
|
||||
// presence: false,
|
||||
// presence: { allowEmpty: true },
|
||||
// },
|
||||
// },
|
||||
DATETIME: {
|
||||
|
@ -44,7 +44,7 @@ export const FIELDS = {
|
|||
constraints: {
|
||||
type: "string",
|
||||
length: {},
|
||||
presence: false,
|
||||
presence: { allowEmpty: true },
|
||||
},
|
||||
},
|
||||
// IMAGE: {
|
||||
|
@ -53,7 +53,7 @@ export const FIELDS = {
|
|||
// type: "file",
|
||||
// constraints: {
|
||||
// type: "string",
|
||||
// presence: false,
|
||||
// presence: { allowEmpty: true },
|
||||
// },
|
||||
// },
|
||||
// FILE: {
|
||||
|
@ -62,11 +62,11 @@ export const FIELDS = {
|
|||
// type: "file",
|
||||
// constraints: {
|
||||
// type: "string",
|
||||
// presence: false,
|
||||
// presence: { allowEmpty: true },
|
||||
// },
|
||||
// },
|
||||
DATA_LINK: {
|
||||
name: "Data Links",
|
||||
LINKED_FIELDS: {
|
||||
name: "Linked Fields",
|
||||
icon: "ri-link",
|
||||
type: "link",
|
||||
modelId: null,
|
||||
|
@ -84,16 +84,46 @@ export const BLOCKS = {
|
|||
constraints: {
|
||||
type: "string",
|
||||
length: {},
|
||||
presence: false,
|
||||
presence: { allowEmpty: true },
|
||||
},
|
||||
},
|
||||
COMPANY: {
|
||||
name: "Company",
|
||||
icon: "ri-store-line",
|
||||
type: "string",
|
||||
constraints: {
|
||||
type: "string",
|
||||
length: {},
|
||||
presence: { allowEmpty: true },
|
||||
},
|
||||
},
|
||||
EMAIL: {
|
||||
name: "Email",
|
||||
icon: "ri-mail-line",
|
||||
type: "string",
|
||||
constraints: {
|
||||
type: "string",
|
||||
length: {},
|
||||
presence: { allowEmpty: true },
|
||||
},
|
||||
},
|
||||
PHONE_NUMBER: {
|
||||
name: "Phone Number",
|
||||
icon: "ri-number-1",
|
||||
name: "Phone No.",
|
||||
icon: "ri-phone-line",
|
||||
type: "number",
|
||||
constraints: {
|
||||
type: "number",
|
||||
presence: false,
|
||||
presence: { allowEmpty: true },
|
||||
numericality: {},
|
||||
},
|
||||
},
|
||||
VALUE: {
|
||||
name: "Value",
|
||||
icon: "ri-number-5",
|
||||
type: "number",
|
||||
constraints: {
|
||||
type: "number",
|
||||
presence: { allowEmpty: true },
|
||||
numericality: {},
|
||||
},
|
||||
},
|
||||
|
@ -103,7 +133,27 @@ export const BLOCKS = {
|
|||
type: "boolean",
|
||||
constraints: {
|
||||
type: "boolean",
|
||||
presence: false,
|
||||
presence: { allowEmpty: true },
|
||||
},
|
||||
},
|
||||
URL: {
|
||||
name: "URL",
|
||||
icon: "ri-link",
|
||||
type: "string",
|
||||
constraints: {
|
||||
type: "string",
|
||||
length: {},
|
||||
presence: { allowEmpty: true },
|
||||
},
|
||||
},
|
||||
IMAGE: {
|
||||
name: "Image URL",
|
||||
icon: "ri-image-line",
|
||||
type: "string",
|
||||
constraints: {
|
||||
type: "string",
|
||||
length: {},
|
||||
presence: { allowEmpty: true },
|
||||
},
|
||||
},
|
||||
// PRIORITY: {
|
||||
|
@ -112,7 +162,7 @@ export const BLOCKS = {
|
|||
// type: "options",
|
||||
// constraints: {
|
||||
// type: "string",
|
||||
// presence: false,
|
||||
// presence: { allowEmpty: true },
|
||||
// inclusion: ["low", "medium", "high"],
|
||||
// },
|
||||
// },
|
||||
|
@ -123,7 +173,7 @@ export const BLOCKS = {
|
|||
constraints: {
|
||||
type: "string",
|
||||
length: {},
|
||||
presence: false,
|
||||
presence: { allowEmpty: true },
|
||||
},
|
||||
},
|
||||
// AVATAR: {
|
||||
|
@ -132,7 +182,7 @@ export const BLOCKS = {
|
|||
// type: "image",
|
||||
// constraints: {
|
||||
// type: "string",
|
||||
// presence: false,
|
||||
// presence: { allowEmpty: true },
|
||||
// },
|
||||
// },
|
||||
// PDF: {
|
||||
|
@ -141,7 +191,7 @@ export const BLOCKS = {
|
|||
// type: "file",
|
||||
// constraints: {
|
||||
// type: "string",
|
||||
// presence: false,
|
||||
// presence: { allowEmpty: true },
|
||||
// },
|
||||
// },
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import Modal from "svelte-simple-modal"
|
||||
import { store, workflowStore } from "builderStore"
|
||||
import { store, workflowStore, backendUiStore } from "builderStore"
|
||||
import SettingsLink from "components/settings/Link.svelte"
|
||||
import { get } from "builderStore/api"
|
||||
|
||||
|
@ -20,6 +20,7 @@
|
|||
const pkg = await res.json()
|
||||
|
||||
if (res.ok) {
|
||||
backendUiStore.actions.reset()
|
||||
await store.setPackage(pkg)
|
||||
workflowStore.actions.fetch()
|
||||
return pkg
|
||||
|
|
|
@ -22,7 +22,8 @@
|
|||
<style>
|
||||
.root {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
display: grid;
|
||||
grid-template-columns: 300px minmax(0, 1fr) 300px;
|
||||
background: var(--grey-1);
|
||||
line-height: 1;
|
||||
}
|
||||
|
|
|
@ -23,12 +23,10 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
{#if selectedModel.schema && Object.keys(selectedModel.schema).length === 0}
|
||||
<EmptyModel />
|
||||
{:else if $backendUiStore.selectedDatabase._id && selectedModel.name}
|
||||
{#if $backendUiStore.selectedDatabase._id && selectedModel.name}
|
||||
<ModelDataTable />
|
||||
{:else}
|
||||
<i style="color: var(--grey-4)">create your first model to start building</i>
|
||||
<i style="color: var(--grey-4)">create your first table to start building</i>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
|
|
|
@ -22,5 +22,5 @@
|
|||
</script>
|
||||
|
||||
{#if $backendUiStore.models.length === 0}
|
||||
Please create a model
|
||||
{:else}Please select a model{/if}
|
||||
Please create a table
|
||||
{:else}Please select a table{/if}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
<!-- routify:options index=4 -->
|
||||
<slot />
|
|
@ -0,0 +1,90 @@
|
|||
<script>
|
||||
import { Button } from "@budibase/bbui"
|
||||
import { store } from "builderStore"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import api from "builderStore/api"
|
||||
import Spinner from "components/common/Spinner.svelte"
|
||||
import analytics from "../../../analytics"
|
||||
|
||||
let deployed = false
|
||||
let loading = false
|
||||
|
||||
$: appId = $store.appId
|
||||
|
||||
async function deployApp() {
|
||||
loading = true
|
||||
const DEPLOY_URL = `/deploy`
|
||||
|
||||
try {
|
||||
notifier.info("Starting Deployment..")
|
||||
const response = await api.post(DEPLOY_URL)
|
||||
const json = await response.json()
|
||||
if (response.status !== 200) {
|
||||
throw new Error()
|
||||
}
|
||||
|
||||
notifier.success(`Your Deployment is Complete.`)
|
||||
deployed = true
|
||||
loading = false
|
||||
analytics.captureEvent("web_app_deployment", {
|
||||
appId,
|
||||
})
|
||||
} catch (err) {
|
||||
analytics.captureException(err)
|
||||
notifier.danger("Deployment unsuccessful. Please try again later.")
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<section>
|
||||
<div>
|
||||
<h4>It's time to shine!</h4>
|
||||
{#if deployed}
|
||||
<a target="_blank" href={`https://${appId}.app.budi.live/${appId}`}>
|
||||
View App
|
||||
</a>
|
||||
{:else}
|
||||
<Button secondary medium on:click={deployApp}>
|
||||
Deploy App
|
||||
{#if loading}
|
||||
<Spinner ratio={'0.5'} />
|
||||
{/if}
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
<img src="/_builder/assets/deploy-rocket.jpg" />
|
||||
</section>
|
||||
|
||||
<style>
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
h4 {
|
||||
color: var(--white);
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
section {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
text-align: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 20%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 50%;
|
||||
}
|
||||
</style>
|
|
@ -14,7 +14,9 @@
|
|||
if ($leftover) {
|
||||
// Get the correct screen children.
|
||||
const screenChildren = $store.pages[$params.page]._screens.find(
|
||||
screen => screen.props._instanceName === $params.screen
|
||||
screen =>
|
||||
screen.props._instanceName === $params.screen ||
|
||||
screen.props._instanceName === decodeURIComponent($params.screen)
|
||||
).props._children
|
||||
findComponent(componentIds, screenChildren)
|
||||
}
|
||||
|
@ -36,15 +38,20 @@
|
|||
|
||||
// Loop through each ID
|
||||
ids.forEach(id => {
|
||||
// Find ID and select it
|
||||
componentToSelect = currentChildren.find(child => child._id === id)
|
||||
// Find ID
|
||||
const component = currentChildren.find(child => child._id === id)
|
||||
|
||||
// If it does not exist, ignore (use last valid route)
|
||||
if (!component) return
|
||||
|
||||
componentToSelect = component
|
||||
|
||||
// Update childrens array to selected components children
|
||||
currentChildren = componentToSelect._children
|
||||
})
|
||||
|
||||
// Select Component!
|
||||
store.selectComponent(componentToSelect)
|
||||
if (componentToSelect) store.selectComponent(componentToSelect)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -27,7 +27,8 @@
|
|||
|
||||
.root {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
display: grid;
|
||||
grid-template-columns: 300px minmax(0, 1fr) 300px;
|
||||
background: var(--grey-1);
|
||||
line-height: 1;
|
||||
}
|
||||
|
|
|
@ -26,27 +26,39 @@
|
|||
<div class="nav-section">
|
||||
<div class="nav-section-title">Build</div>
|
||||
<Link icon={AppsIcon} title="Apps" href="/" active />
|
||||
<Link icon={SettingsIcon} title="Settings" href="/" />
|
||||
<Link icon={UpdatesIcon} title="Updates" href="/" />
|
||||
<Link icon={HostingIcon} title="Hosting" href="/" />
|
||||
<Link
|
||||
icon={HostingIcon}
|
||||
title="Hosting"
|
||||
href="https://portal.budi.live/" />
|
||||
</div>
|
||||
|
||||
<div class="nav-section">
|
||||
<div class="nav-section-title">Learn</div>
|
||||
<Link icon={DocumentationIcon} title="Documentation" href="/" />
|
||||
<Link icon={TutorialsIcon} title="Tutorials" href="/" />
|
||||
<Link icon={CommunityIcon} title="Community" href="/" />
|
||||
<Link
|
||||
icon={DocumentationIcon}
|
||||
title="Documentation"
|
||||
href="https://docs.budibase.com/" />
|
||||
<Link
|
||||
icon={CommunityIcon}
|
||||
title="Community"
|
||||
href="https://forum.budibase.com/" />
|
||||
</div>
|
||||
|
||||
<div class="nav-section">
|
||||
<div class="nav-section-title">Contact</div>
|
||||
<Link
|
||||
icon={ContributionIcon}
|
||||
title="Contribute to our product"
|
||||
href="/" />
|
||||
<Link icon={BugIcon} title="Report bug" href="/" />
|
||||
<Link icon={EmailIcon} title="Email" href="/" />
|
||||
<Link icon={TwitterIcon} title="Twitter" href="/" />
|
||||
title="Contribute"
|
||||
href="https://github.com/Budibase/budibase" />
|
||||
<Link
|
||||
icon={BugIcon}
|
||||
title="Report bug"
|
||||
href="https://github.com/Budibase/budibase/issues" />
|
||||
<Link icon={EmailIcon} title="Email" href="mailto:hi@budibase.com" />
|
||||
<Link
|
||||
icon={TwitterIcon}
|
||||
title="Twitter"
|
||||
href="https://twitter.com/budibase" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -59,7 +71,7 @@
|
|||
<style>
|
||||
.root {
|
||||
display: grid;
|
||||
grid-template-columns: 300px 1fr;
|
||||
grid-template-columns: 260px 1fr;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: var(--grey-1);
|
||||
|
@ -67,6 +79,7 @@
|
|||
|
||||
.main {
|
||||
grid-column: 2;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.ui-nav {
|
||||
|
@ -75,7 +88,6 @@
|
|||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-right: 1px solid var(--grey-4);
|
||||
}
|
||||
|
||||
.home-logo {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { get } from "builderStore/api"
|
||||
import Spinner from "components/common/Spinner.svelte"
|
||||
import CreateAppModal from "components/start/CreateAppModal.svelte"
|
||||
import { Button } from "@budibase/bbui"
|
||||
|
||||
let promise = getApps()
|
||||
|
||||
|
@ -43,21 +44,17 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="welcome">Welcome to Budibase</div>
|
||||
<div class="header">
|
||||
<div class="welcome">Welcome to the Budibase Beta</div>
|
||||
<Button purple large on:click={showCreateAppModal}>Create New Web App</Button>
|
||||
</div>
|
||||
|
||||
<div class="banner">
|
||||
<img src="/_builder/assets/rocket.jpg" alt="rocket" />
|
||||
<img src="/_builder/assets/orange-landscape.png" alt="rocket" />
|
||||
<div class="banner-content">
|
||||
Every accomplishment starts with a decision to try.
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-section-header">
|
||||
<div class="app-section-title">Your Web Apps</div>
|
||||
<button class="banner-button" type="button" on:click={showCreateAppModal}>
|
||||
<i class="ri-add-circle-fill" />
|
||||
Create New Web App
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#await promise}
|
||||
<div class="spinner-container">
|
||||
|
@ -70,11 +67,17 @@
|
|||
{/await}
|
||||
|
||||
<style>
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 40px 80px 0px 80px;
|
||||
}
|
||||
|
||||
.welcome {
|
||||
font-size: 42px;
|
||||
color: var(--ink);
|
||||
font-weight: 700;
|
||||
margin: 40px 0px 0px 80px;
|
||||
}
|
||||
|
||||
.banner {
|
||||
|
@ -108,48 +111,4 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.banner-button {
|
||||
background-color: var(--ink);
|
||||
color: var(--white);
|
||||
padding: 12px 24px;
|
||||
border-radius: 5px;
|
||||
border: var(--ink) 1px solid;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease 0s;
|
||||
overflow: hidden;
|
||||
outline: none;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ri-add-circle-fill {
|
||||
margin-right: 4px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.banner-button:hover {
|
||||
background-color: var(--white);
|
||||
color: var(--ink);
|
||||
border: var(--grey-4) 1px solid;
|
||||
}
|
||||
|
||||
.app-section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 40px 80px 0px 80px;
|
||||
}
|
||||
|
||||
.app-section-title {
|
||||
font-size: 20px;
|
||||
color: var(--ink);
|
||||
font-weight: 600;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "budibase",
|
||||
"version": "0.0.32",
|
||||
"version": "0.1.12",
|
||||
"description": "Budibase CLI",
|
||||
"repository": "https://github.com/Budibase/Budibase",
|
||||
"homepage": "https://www.budibase.com",
|
||||
|
@ -17,7 +17,7 @@
|
|||
"author": "Budibase",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@budibase/server": "^0.0.32",
|
||||
"@budibase/server": "^0.1.12",
|
||||
"@inquirer/password": "^0.0.6-alpha.0",
|
||||
"chalk": "^2.4.2",
|
||||
"dotenv": "^8.2.0",
|
||||
|
@ -29,5 +29,5 @@
|
|||
"uuid": "^7.0.3",
|
||||
"yargs": "^14.2.0"
|
||||
},
|
||||
"gitHead": "b1f4f90927d9e494e513220ef060af28d2d42455"
|
||||
"gitHead": "eff4fa93ca1db11b97b5fdedc0c488413e277eb8"
|
||||
}
|
||||
|
|
|
@ -1,88 +1,7 @@
|
|||
const inquirer = require("inquirer")
|
||||
const { exists, readFile, writeFile, ensureDir } = require("fs-extra")
|
||||
const chalk = require("chalk")
|
||||
const { serverFileName, xPlatHomeDir } = require("../../common")
|
||||
const { join } = require("path")
|
||||
const Sqrl = require("squirrelly")
|
||||
const uuid = require("uuid")
|
||||
const { xPlatHomeDir } = require("../../common")
|
||||
const initialiseBudibase = require("@budibase/server/src/utilities/initialiseBudibase")
|
||||
|
||||
module.exports = opts => {
|
||||
return run(opts)
|
||||
}
|
||||
|
||||
const run = async opts => {
|
||||
try {
|
||||
await ensureAppDir(opts)
|
||||
await setEnvironmentVariables(opts)
|
||||
await createClientDatabase(opts)
|
||||
await createDevEnvFile(opts)
|
||||
console.log(chalk.green("Budibase successfully initialised."))
|
||||
} catch (error) {
|
||||
console.error(`Error initialising Budibase: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
const setEnvironmentVariables = async opts => {
|
||||
if (opts.couchDbUrl) {
|
||||
process.env.COUCH_DB_URL = opts.couchDbUrl
|
||||
} else {
|
||||
const dataDir = join(opts.dir, ".data")
|
||||
await ensureDir(dataDir)
|
||||
process.env.COUCH_DB_URL =
|
||||
dataDir + (dataDir.endsWith("/") || dataDir.endsWith("\\") ? "" : "/")
|
||||
}
|
||||
}
|
||||
|
||||
const ensureAppDir = async opts => {
|
||||
opts.dir = xPlatHomeDir(opts.dir)
|
||||
await ensureDir(opts.dir)
|
||||
}
|
||||
|
||||
const createClientDatabase = async opts => {
|
||||
// cannot be a top level require as it
|
||||
// will cause environment module to be loaded prematurely
|
||||
const clientDb = require("@budibase/server/src/db/clientDb")
|
||||
|
||||
if (opts.clientId === "new") {
|
||||
// cannot be a top level require as it
|
||||
// will cause environment module to be loaded prematurely
|
||||
const CouchDB = require("@budibase/server/src/db/client")
|
||||
const existing = await CouchDB.allDbs()
|
||||
|
||||
let i = 0
|
||||
let isExisting = true
|
||||
while (isExisting) {
|
||||
i += 1
|
||||
opts.clientId = i.toString()
|
||||
isExisting = existing.includes(clientDb.name(opts.clientId))
|
||||
}
|
||||
}
|
||||
|
||||
await clientDb.create(opts.clientId)
|
||||
}
|
||||
|
||||
const createDevEnvFile = async opts => {
|
||||
const destConfigFile = join(opts.dir, "./.env")
|
||||
let createConfig = !(await exists(destConfigFile)) || opts.quiet
|
||||
if (!createConfig) {
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: "input",
|
||||
name: "overwrite",
|
||||
message: ".env already exists - overwrite? (N/y)",
|
||||
},
|
||||
])
|
||||
createConfig = ["Y", "y", "yes"].includes(answers.overwrite)
|
||||
}
|
||||
|
||||
if (createConfig) {
|
||||
const template = await readFile(serverFileName(".env.template"), {
|
||||
encoding: "utf8",
|
||||
})
|
||||
opts.adminSecret = uuid.v4()
|
||||
opts.cookieKey1 = uuid.v4()
|
||||
opts.cookieKey2 = uuid.v4()
|
||||
const config = Sqrl.Render(template, opts)
|
||||
await writeFile(destConfigFile, config, { flag: "w+" })
|
||||
}
|
||||
return initialiseBudibase(opts)
|
||||
}
|
||||
|
|
|
@ -16,8 +16,10 @@ const run = async opts => {
|
|||
await createEmptyAppPackage(opts)
|
||||
exec(`cd ${join(opts.dir, opts.applicationId)} && npm install`)
|
||||
console.log(chalk.green(`Budibase app ${opts.name} created!`))
|
||||
process.exit()
|
||||
} catch (error) {
|
||||
console.error(chalk.red("Error creating new app", error))
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
const { xPlatHomeDir } = require("../../common")
|
||||
const { resolve } = require("path")
|
||||
|
||||
module.exports = ({ dir }) => {
|
||||
module.exports = async ({ dir }) => {
|
||||
dir = xPlatHomeDir(dir)
|
||||
process.env.BUDIBASE_DIR = resolve(dir)
|
||||
require("dotenv").config({ path: resolve(dir, ".env") })
|
||||
|
||||
// dont make this a variable or top level require
|
||||
// ti will cause environment module to be loaded prematurely
|
||||
require("@budibase/server/src/app")().then(server => {
|
||||
// it will cause environment module to be loaded prematurely
|
||||
return require("@budibase/server/src/app")().then(server => {
|
||||
server.on("close", () => console.log("Server Closed"))
|
||||
console.log(`Budibase running on ${JSON.stringify(server.address())}`)
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/client",
|
||||
"version": "0.0.32",
|
||||
"version": "0.1.1",
|
||||
"license": "MPL-2.0",
|
||||
"main": "dist/budibase-client.js",
|
||||
"module": "dist/budibase-client.esm.mjs",
|
||||
|
@ -60,5 +60,5 @@
|
|||
"rollup-plugin-node-resolve": "^5.2.0",
|
||||
"rollup-plugin-terser": "^4.0.4"
|
||||
},
|
||||
"gitHead": "b1f4f90927d9e494e513220ef060af28d2d42455"
|
||||
"gitHead": "e4e053cb6ff9a0ddc7115b44ccaa24b8ec41fb9a"
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { attachChildren } from "./render/attachChildren"
|
|||
import { createTreeNode } from "./render/prepareRenderComponent"
|
||||
import { screenRouter } from "./render/screenRouter"
|
||||
import { createStateManager } from "./state/stateManager"
|
||||
import { getAppId } from "./render/getAppId"
|
||||
import { parseAppIdFromCookie } from "./render/getAppId"
|
||||
|
||||
export const createApp = ({
|
||||
componentLibraries,
|
||||
|
@ -38,7 +38,7 @@ export const createApp = ({
|
|||
window,
|
||||
})
|
||||
const fallbackPath = window.location.pathname.replace(
|
||||
getAppId(window.document.cookie),
|
||||
parseAppIdFromCookie(window.document.cookie),
|
||||
""
|
||||
)
|
||||
routeTo(currentUrl || fallbackPath)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { createApp } from "./createApp"
|
||||
import { builtins, builtinLibName } from "./render/builtinComponents"
|
||||
import { getAppId } from "./render/getAppId"
|
||||
import { parseAppIdFromCookie } from "./render/getAppId"
|
||||
|
||||
/**
|
||||
* create a web application from static budibase definition files.
|
||||
|
@ -9,7 +9,7 @@ import { getAppId } from "./render/getAppId"
|
|||
export const loadBudibase = async opts => {
|
||||
const _window = (opts && opts.window) || window
|
||||
// const _localStorage = (opts && opts.localStorage) || localStorage
|
||||
const appId = getAppId(_window.document.cookie)
|
||||
const appId = parseAppIdFromCookie(_window.document.cookie)
|
||||
const frontendDefinition = _window["##BUDIBASE_FRONTEND_DEFINITION##"]
|
||||
|
||||
const user = {}
|
||||
|
|
|
@ -30,34 +30,33 @@ export const attachChildren = initialiseOpts => (htmlElement, options) => {
|
|||
}
|
||||
}
|
||||
|
||||
const contextArray = Array.isArray(context) ? context : [context]
|
||||
|
||||
const childNodes = []
|
||||
for (let childProps of treeNode.props._children) {
|
||||
const { componentName, libName } = splitName(childProps._component)
|
||||
|
||||
if (!componentName || !libName) return
|
||||
for (let context of contextArray) {
|
||||
for (let childProps of treeNode.props._children) {
|
||||
const { componentName, libName } = splitName(childProps._component)
|
||||
|
||||
const ComponentConstructor = componentLibraries[libName][componentName]
|
||||
if (!componentName || !libName) return
|
||||
|
||||
const prepareNodes = ctx => {
|
||||
const childNodesThisIteration = prepareRenderComponent({
|
||||
props: childProps,
|
||||
parentNode: treeNode,
|
||||
ComponentConstructor,
|
||||
htmlElement,
|
||||
anchor,
|
||||
context: ctx,
|
||||
})
|
||||
const ComponentConstructor = componentLibraries[libName][componentName]
|
||||
|
||||
for (let childNode of childNodesThisIteration) {
|
||||
childNodes.push(childNode)
|
||||
const prepareNodes = ctx => {
|
||||
const childNodesThisIteration = prepareRenderComponent({
|
||||
props: childProps,
|
||||
parentNode: treeNode,
|
||||
ComponentConstructor,
|
||||
htmlElement,
|
||||
anchor,
|
||||
context: ctx,
|
||||
})
|
||||
|
||||
for (let childNode of childNodesThisIteration) {
|
||||
childNodes.push(childNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(context)) {
|
||||
for (let singleCtx of context) {
|
||||
prepareNodes(singleCtx)
|
||||
}
|
||||
} else {
|
||||
prepareNodes(context)
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +99,10 @@ const areTreeNodesEqual = (children1, children2) => {
|
|||
|
||||
let isEqual = false
|
||||
for (let i = 0; i < children1.length; i++) {
|
||||
isEqual = deepEqual(children1[i].context, children2[i].context)
|
||||
// same context and same children, then nothing has changed
|
||||
isEqual =
|
||||
deepEqual(children1[i].context, children2[i].context) &&
|
||||
areTreeNodesEqual(children1[i].children, children2[i].children)
|
||||
if (!isEqual) return false
|
||||
if (isScreenSlot(children1[i].parentNode.props._component)) {
|
||||
isEqual = deepEqual(children1[i].props, children2[i].props)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export const getAppId = docCookie => {
|
||||
export const parseAppIdFromCookie = docCookie => {
|
||||
const cookie =
|
||||
docCookie.split(";").find(c => c.trim().startsWith("budibase:token")) ||
|
||||
docCookie.split(";").find(c => c.trim().startsWith("builder:token"))
|
||||
|
|
|
@ -78,13 +78,14 @@ export const createTreeNode = () => ({
|
|||
get destroy() {
|
||||
const node = this
|
||||
return () => {
|
||||
if (node.unsubscribe) node.unsubscribe()
|
||||
if (node.component && node.component.$destroy) node.component.$destroy()
|
||||
if (node.children) {
|
||||
// destroy children first - from leaf nodes up
|
||||
for (let child of node.children) {
|
||||
child.destroy()
|
||||
}
|
||||
}
|
||||
if (node.unsubscribe) node.unsubscribe()
|
||||
if (node.component && node.component.$destroy) node.component.$destroy()
|
||||
for (let onDestroyItem of node.onDestroy) {
|
||||
onDestroyItem()
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import regexparam from "regexparam"
|
||||
import { routerStore } from "../state/store"
|
||||
import { getAppId } from "./getAppId"
|
||||
import { appStore } from "../state/store"
|
||||
import { parseAppIdFromCookie } from "./getAppId"
|
||||
|
||||
export const screenRouter = ({ screens, onScreenSelected, window }) => {
|
||||
const makeRootedPath = url => {
|
||||
|
@ -9,7 +9,7 @@ export const screenRouter = ({ screens, onScreenSelected, window }) => {
|
|||
(window.location.hostname === "localhost" ||
|
||||
window.location.hostname === "127.0.0.1")
|
||||
) {
|
||||
const appId = getAppId(window.document.cookie)
|
||||
const appId = parseAppIdFromCookie(window.document.cookie)
|
||||
if (url) {
|
||||
if (url.startsWith(appId)) return url
|
||||
return `/${appId}${url.startsWith("/") ? "" : "/"}${url}`
|
||||
|
@ -49,20 +49,20 @@ export const screenRouter = ({ screens, onScreenSelected, window }) => {
|
|||
})
|
||||
}
|
||||
|
||||
routerStore.update(state => {
|
||||
appStore.update(state => {
|
||||
state["##routeParams"] = params
|
||||
return state
|
||||
})
|
||||
|
||||
const screenIndex = current !== -1 ? current : fallback
|
||||
|
||||
onScreenSelected(screens[screenIndex], _url)
|
||||
|
||||
try {
|
||||
!url.state && history.pushState(_url, null, _url)
|
||||
} catch (_) {
|
||||
// ignoring an exception here as the builder runs an iframe, which does not like this
|
||||
}
|
||||
|
||||
onScreenSelected(screens[screenIndex], _url)
|
||||
}
|
||||
|
||||
function click(e) {
|
||||
|
|
|
@ -8,6 +8,7 @@ export const bbFactory = ({
|
|||
store,
|
||||
componentLibraries,
|
||||
onScreenSlotRendered,
|
||||
getCurrentState,
|
||||
}) => {
|
||||
const apiCall = method => (url, body) => {
|
||||
return fetch(url, {
|
||||
|
@ -53,6 +54,8 @@ export const bbFactory = ({
|
|||
store: store,
|
||||
api,
|
||||
parent,
|
||||
// these parameters are populated by screenRouter
|
||||
routeParams: () => getCurrentState()["##routeParams"],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,8 +26,14 @@ export const createStateManager = ({
|
|||
routeTo,
|
||||
}) => {
|
||||
let handlerTypes = eventHandlers(routeTo)
|
||||
let currentState
|
||||
|
||||
// creating a reference to the current state
|
||||
// this avoids doing store.get() ... which is expensive on
|
||||
// hot paths, according to the svelte docs.
|
||||
// the state object reference never changes (although it's internals do)
|
||||
// so this should work fine for us
|
||||
let currentState
|
||||
appStore.subscribe(s => (currentState = s))
|
||||
const getCurrentState = () => currentState
|
||||
|
||||
const bb = bbFactory({
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
"dev:builder": "rollup -cw"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@budibase/client": "^0.0.32",
|
||||
"@budibase/standard-components": "^0.0.32",
|
||||
"@budibase/client": "^0.1.1",
|
||||
"@budibase/standard-components": "^0.1.10",
|
||||
"@material/button": "^4.0.0",
|
||||
"@material/checkbox": "^4.0.0",
|
||||
"@material/data-table": "4.0.0",
|
||||
|
@ -50,9 +50,9 @@
|
|||
"keywords": [
|
||||
"svelte"
|
||||
],
|
||||
"version": "0.0.32",
|
||||
"version": "0.1.10",
|
||||
"license": "MIT",
|
||||
"gitHead": "b1f4f90927d9e494e513220ef060af28d2d42455",
|
||||
"gitHead": "eff4fa93ca1db11b97b5fdedc0c488413e277eb8",
|
||||
"dependencies": {
|
||||
"@material/card": "4.0.0"
|
||||
}
|
||||
|
|
|
@ -12,4 +12,8 @@ JWT_SECRET={{cookieKey1}}
|
|||
PORT=4001
|
||||
|
||||
# error level for koa-pino
|
||||
LOG_LEVEL=error
|
||||
LOG_LEVEL=error
|
||||
|
||||
DEPLOYMENT_CREDENTIALS_URL="https://dt4mpwwap8.execute-api.eu-west-1.amazonaws.com/prod/"
|
||||
DEPLOYMENT_DB_URL="https://couchdb.budi.live:5984"
|
||||
SENTRY_DSN=https://a34ae347621946bf8acded18e5b7d4b8@o420233.ingest.sentry.io/5338131
|
|
@ -8,7 +8,7 @@
|
|||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Start Server",
|
||||
"program": "${workspaceFolder}/../cli/bin/budi"
|
||||
"program": "${workspaceFolder}/src/index.js"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
|
|
|
@ -2,6 +2,9 @@ FROM node:12-alpine
|
|||
|
||||
WORKDIR /app
|
||||
|
||||
ENV CLOUD=1
|
||||
ENV COUCH_DB_URL=https://couchdb.budi.live:5984
|
||||
|
||||
# copy files and install dependencies
|
||||
COPY . ./
|
||||
RUN yarn
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue