Merge branch 'develop' of github.com:Budibase/budibase into cheeks-fixes
This commit is contained in:
commit
6aef0f2134
|
@ -5,7 +5,7 @@
|
|||
"jest": true,
|
||||
"node": true
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"parser": "@babel/eslint-parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2019,
|
||||
"sourceType": "module",
|
||||
|
@ -18,17 +18,23 @@
|
|||
"*.spec.js",
|
||||
"bundle.js"
|
||||
],
|
||||
"plugins": ["svelte3"],
|
||||
"extends": ["eslint:recommended"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.svelte"],
|
||||
"processor": "svelte3/svelte3"
|
||||
"files": ["**/*.svelte"],
|
||||
"extends": "plugin:svelte/recommended",
|
||||
"parser": "svelte-eslint-parser",
|
||||
"parserOptions": {
|
||||
"parser": "@babel/eslint-parser",
|
||||
"ecmaVersion": 2019,
|
||||
"sourceType": "module",
|
||||
"allowImportExportEverywhere": true
|
||||
}
|
||||
|
||||
},
|
||||
{
|
||||
"files": ["**/*.ts"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": [],
|
||||
"extends": ["eslint:recommended"],
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
|
@ -41,7 +47,8 @@
|
|||
}
|
||||
],
|
||||
"rules": {
|
||||
"no-self-assign": "off"
|
||||
"no-self-assign": "off",
|
||||
"no-unused-vars": ["error", { "varsIgnorePattern": "^_", "argsIgnorePattern": "^_", "destructuredArrayIgnorePattern": "^_" }]
|
||||
},
|
||||
"globals": {
|
||||
"GeolocationPositionError": true
|
||||
|
|
|
@ -34,7 +34,6 @@ jobs:
|
|||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14.x
|
||||
|
@ -58,9 +57,12 @@ jobs:
|
|||
echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} >> .npmrc
|
||||
yarn release
|
||||
|
||||
- name: "Get Previous tag"
|
||||
id: previoustag
|
||||
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
||||
- name: "Get Current tag"
|
||||
id: currenttag
|
||||
run: |
|
||||
version=v$(./scripts/getCurrentVersion.sh)
|
||||
echo 'Using tag $version'
|
||||
echo "::set-output name=tag::$resversionult"
|
||||
|
||||
- name: Build/release Docker images
|
||||
run: |
|
||||
|
@ -69,7 +71,7 @@ jobs:
|
|||
env:
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}
|
||||
BUDIBASE_RELEASE_VERSION: ${{ steps.previoustag.outputs.tag }}
|
||||
BUDIBASE_RELEASE_VERSION: ${{ steps.currenttag.outputs.tag }}
|
||||
|
||||
release-helm-chart:
|
||||
needs: [release-images]
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"presets": [["@babel/preset-env", { "targets": { "node": "current" } }]]
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "2.7.37-alpha.15",
|
||||
"version": "2.8.2-alpha.4",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
|
|
18
package.json
18
package.json
|
@ -6,12 +6,10 @@
|
|||
"@nx/js": "16.2.1",
|
||||
"@rollup/plugin-json": "^4.0.2",
|
||||
"@typescript-eslint/parser": "5.45.0",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"esbuild": "^0.17.18",
|
||||
"esbuild-node-externals": "^1.7.0",
|
||||
"eslint": "^7.28.0",
|
||||
"eslint": "^8.44.0",
|
||||
"eslint-plugin-cypress": "^2.11.3",
|
||||
"eslint-plugin-svelte3": "^3.2.0",
|
||||
"husky": "^8.0.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"kill-port": "^1.6.1",
|
||||
|
@ -23,7 +21,12 @@
|
|||
"rimraf": "^3.0.2",
|
||||
"rollup-plugin-replace": "^2.2.0",
|
||||
"svelte": "^3.38.2",
|
||||
"typescript": "4.7.3"
|
||||
"typescript": "4.7.3",
|
||||
"@babel/core": "^7.22.5",
|
||||
"@babel/eslint-parser": "^7.22.5",
|
||||
"@babel/preset-env": "^7.22.5",
|
||||
"eslint-plugin-svelte": "^2.32.2",
|
||||
"svelte-eslint-parser": "^0.32.0"
|
||||
},
|
||||
"scripts": {
|
||||
"preinstall": "node scripts/syncProPackage.js",
|
||||
|
@ -52,10 +55,10 @@
|
|||
"dev:built": "yarn run kill-all && cd packages/server && yarn dev:stack:up && cd ../../ && lerna run --stream --parallel dev:built",
|
||||
"dev:docker": "yarn build:docker:pre && docker-compose -f hosting/docker-compose.build.yaml -f hosting/docker-compose.dev.yaml --env-file hosting/.env up --build --scale proxy-service=0",
|
||||
"test": "lerna run --stream test --stream",
|
||||
"lint:eslint": "eslint packages && eslint qa-core",
|
||||
"lint:eslint": "eslint packages qa-core --max-warnings=0",
|
||||
"lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --check \"qa-core/**/*.{js,ts,svelte}\"",
|
||||
"lint": "yarn run lint:eslint && yarn run lint:prettier",
|
||||
"lint:fix:eslint": "eslint --fix packages qa-core",
|
||||
"lint:fix:eslint": "eslint --fix --max-warnings=0 packages qa-core",
|
||||
"lint:fix:prettier": "prettier --write \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --write \"qa-core/**/*.{js,ts,svelte}\"",
|
||||
"lint:fix": "yarn run lint:fix:prettier && yarn run lint:fix:eslint",
|
||||
"build:specs": "lerna run --stream specs",
|
||||
|
@ -103,5 +106,6 @@
|
|||
"@budibase/string-templates": "0.0.0",
|
||||
"@budibase/types": "0.0.0"
|
||||
},
|
||||
"dependencies": {}
|
||||
"dependencies": {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
export let tooltip = undefined
|
||||
export let newStyles = true
|
||||
export let id
|
||||
|
||||
let showTooltip = false
|
||||
</script>
|
||||
|
||||
<button
|
||||
|
@ -35,9 +33,6 @@
|
|||
class="spectrum-Button spectrum-Button--size{size.toUpperCase()}"
|
||||
{disabled}
|
||||
on:click|preventDefault
|
||||
on:mouseover={() => (showTooltip = true)}
|
||||
on:focus={() => (showTooltip = true)}
|
||||
on:mouseleave={() => (showTooltip = false)}
|
||||
>
|
||||
{#if icon}
|
||||
<svg
|
||||
|
@ -52,19 +47,7 @@
|
|||
{#if $$slots}
|
||||
<span class="spectrum-Button-label"><slot /></span>
|
||||
{/if}
|
||||
{#if !disabled && tooltip}
|
||||
<div class="tooltip-icon">
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-Icon--size{size.toUpperCase()}"
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
aria-label="Info"
|
||||
>
|
||||
<use xlink:href="#spectrum-icon-18-InfoOutline" />
|
||||
</svg>
|
||||
</div>
|
||||
{/if}
|
||||
{#if showTooltip && tooltip}
|
||||
{#if tooltip}
|
||||
<div class="tooltip">
|
||||
<Tooltip textWrapping={true} direction={"bottom"} text={tooltip} />
|
||||
</div>
|
||||
|
@ -75,7 +58,6 @@
|
|||
button {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.spectrum-Button-label {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
@ -93,11 +75,13 @@
|
|||
text-align: center;
|
||||
transform: translateX(-50%);
|
||||
left: 50%;
|
||||
top: calc(100% - 3px);
|
||||
top: 100%;
|
||||
opacity: 0;
|
||||
transition: opacity 130ms ease-out;
|
||||
pointer-events: none;
|
||||
}
|
||||
.tooltip-icon {
|
||||
padding-left: var(--spacing-m);
|
||||
line-height: 0;
|
||||
button:hover .tooltip {
|
||||
opacity: 1;
|
||||
}
|
||||
.spectrum-Button--primary.new-styles {
|
||||
background: var(--spectrum-global-color-gray-800);
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: placeholder = !value
|
||||
|
||||
const extractProperty = (value, property) => {
|
||||
if (value && typeof value === "object") {
|
||||
return value[property]
|
||||
|
|
|
@ -150,7 +150,7 @@
|
|||
</div>
|
||||
{:else if variables.length}
|
||||
<div style="max-height: 100px">
|
||||
{#each variables as variable, idx}
|
||||
{#each variables as variable}
|
||||
<li
|
||||
class="spectrum-Menu-item"
|
||||
role="option"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import "@spectrum-css/link/dist/index-vars.css"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import Tooltip from "../Tooltip/Tooltip.svelte"
|
||||
|
||||
export let href = "#"
|
||||
export let size = "M"
|
||||
|
@ -10,18 +11,61 @@
|
|||
export let overBackground = false
|
||||
export let target
|
||||
export let download
|
||||
export let disabled = false
|
||||
export let tooltip = null
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const onClick = e => {
|
||||
if (!disabled) {
|
||||
dispatch("click")
|
||||
e.stopPropagation()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<a
|
||||
on:click={e => dispatch("click") && e.stopPropagation()}
|
||||
on:click={onClick}
|
||||
{href}
|
||||
{target}
|
||||
{download}
|
||||
class:disabled
|
||||
class:spectrum-Link--primary={primary}
|
||||
class:spectrum-Link--secondary={secondary}
|
||||
class:spectrum-Link--overBackground={overBackground}
|
||||
class:spectrum-Link--quiet={quiet}
|
||||
class="spectrum-Link spectrum-Link--size{size}"><slot /></a
|
||||
class="spectrum-Link spectrum-Link--size{size}"
|
||||
>
|
||||
<slot />
|
||||
{#if tooltip}
|
||||
<div class="tooltip">
|
||||
<Tooltip textWrapping direction="bottom" text={tooltip} />
|
||||
</div>
|
||||
{/if}
|
||||
</a>
|
||||
|
||||
<style>
|
||||
a {
|
||||
position: relative;
|
||||
}
|
||||
a.disabled {
|
||||
color: var(--spectrum-global-color-gray-500);
|
||||
}
|
||||
a.disabled:hover {
|
||||
text-decoration: none;
|
||||
cursor: default;
|
||||
}
|
||||
.tooltip {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 100%;
|
||||
transform: translateX(-50%);
|
||||
opacity: 0;
|
||||
transition: 130ms ease-out;
|
||||
pointer-events: none;
|
||||
z-index: 100;
|
||||
}
|
||||
a:hover .tooltip {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
<script context="module">
|
||||
export const keepOpen = Symbol("keepOpen")
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import "@spectrum-css/dialog/dist/index-vars.css"
|
||||
import { getContext } from "svelte"
|
||||
|
@ -30,7 +34,7 @@
|
|||
|
||||
async function secondary(e) {
|
||||
loading = true
|
||||
if (!secondaryAction || (await secondaryAction(e)) !== false) {
|
||||
if (!secondaryAction || (await secondaryAction(e)) !== keepOpen) {
|
||||
hide()
|
||||
}
|
||||
loading = false
|
||||
|
@ -38,7 +42,7 @@
|
|||
|
||||
async function confirm() {
|
||||
loading = true
|
||||
if (!onConfirm || (await onConfirm()) !== false) {
|
||||
if (!onConfirm || (await onConfirm()) !== keepOpen) {
|
||||
hide()
|
||||
}
|
||||
loading = false
|
||||
|
@ -46,7 +50,7 @@
|
|||
|
||||
async function close() {
|
||||
loading = true
|
||||
if (!onCancel || (await onCancel()) !== false) {
|
||||
if (!onCancel || (await onCancel()) !== keepOpen) {
|
||||
cancel()
|
||||
}
|
||||
loading = false
|
||||
|
|
|
@ -90,6 +90,6 @@
|
|||
.spectrum-Popover {
|
||||
min-width: var(--spectrum-global-dimension-size-2000);
|
||||
border-color: var(--spectrum-global-color-gray-300);
|
||||
overflow: auto;
|
||||
overflow: visible;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -29,7 +29,6 @@
|
|||
$: type = getType(schema)
|
||||
$: customRenderer = customRenderers?.find(x => x.column === schema?.name)
|
||||
$: renderer = customRenderer?.component ?? typeMap[type] ?? StringRenderer
|
||||
$: width = schema?.width || "150px"
|
||||
$: cellValue = getCellValue(value, schema.template)
|
||||
|
||||
const getType = schema => {
|
||||
|
|
|
@ -379,7 +379,7 @@
|
|||
</div>
|
||||
{/if}
|
||||
{#if sortedRows?.length}
|
||||
{#each sortedRows as row, idx}
|
||||
{#each sortedRows as row}
|
||||
<div class="spectrum-Table-row" class:clickable={allowClickRows}>
|
||||
{#if showEditColumn}
|
||||
<div
|
||||
|
|
|
@ -47,7 +47,7 @@ export { default as MenuSection } from "./Menu/Section.svelte"
|
|||
export { default as MenuSeparator } from "./Menu/Separator.svelte"
|
||||
export { default as MenuItem } from "./Menu/Item.svelte"
|
||||
export { default as Modal } from "./Modal/Modal.svelte"
|
||||
export { default as ModalContent } from "./Modal/ModalContent.svelte"
|
||||
export { default as ModalContent, keepOpen } from "./Modal/ModalContent.svelte"
|
||||
export { default as NotificationDisplay } from "./Notification/NotificationDisplay.svelte"
|
||||
export { default as Notification } from "./Notification/Notification.svelte"
|
||||
export { default as SideNavigation } from "./SideNavigation/Navigation.svelte"
|
||||
|
|
|
@ -3,6 +3,7 @@ import { getAutomationStore } from "./store/automation"
|
|||
import { getTemporalStore } from "./store/temporal"
|
||||
import { getThemeStore } from "./store/theme"
|
||||
import { getUserStore } from "./store/users"
|
||||
import { getDeploymentStore } from "./store/deployments"
|
||||
import { derived } from "svelte/store"
|
||||
import { findComponent, findComponentPath } from "./componentUtils"
|
||||
import { RoleUtils } from "@budibase/frontend-core"
|
||||
|
@ -14,6 +15,7 @@ export const automationStore = getAutomationStore()
|
|||
export const themeStore = getThemeStore()
|
||||
export const temporalStore = getTemporalStore()
|
||||
export const userStore = getUserStore()
|
||||
export const deploymentStore = getDeploymentStore()
|
||||
|
||||
// Setup history for screens
|
||||
export const screenHistoryStore = createHistoryStore({
|
||||
|
@ -118,3 +120,20 @@ export const selectedAutomation = derived(automationStore, $automationStore => {
|
|||
x => x._id === $automationStore.selectedAutomationId
|
||||
)
|
||||
})
|
||||
|
||||
// Derive map of resource IDs to other users.
|
||||
// We only ever care about a single user in each resource, so if multiple users
|
||||
// share the same datasource we can just overwrite them.
|
||||
export const userSelectedResourceMap = derived(userStore, $userStore => {
|
||||
let map = {}
|
||||
$userStore.forEach(user => {
|
||||
if (user.builderMetadata?.selectedResourceId) {
|
||||
map[user.builderMetadata?.selectedResourceId] = user
|
||||
}
|
||||
})
|
||||
return map
|
||||
})
|
||||
|
||||
export const isOnlyUser = derived(userStore, $userStore => {
|
||||
return $userStore.length < 2
|
||||
})
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import { writable } from "svelte/store"
|
||||
import { API } from "api"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
|
||||
export const getDeploymentStore = () => {
|
||||
let store = writable([])
|
||||
|
||||
const load = async () => {
|
||||
try {
|
||||
store.set(await API.getAppDeployments())
|
||||
} catch (err) {
|
||||
notifications.error("Error fetching deployments")
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
actions: {
|
||||
load,
|
||||
},
|
||||
}
|
||||
}
|
|
@ -38,6 +38,7 @@ import {
|
|||
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||
import { getComponentFieldOptions } from "helpers/formFields"
|
||||
import { createBuilderWebsocket } from "builderStore/websocket"
|
||||
import { BuilderSocketEvent } from "@budibase/shared-core"
|
||||
|
||||
const INITIAL_FRONTEND_STATE = {
|
||||
initialised: false,
|
||||
|
@ -353,6 +354,33 @@ export const getFrontendStore = () => {
|
|||
}
|
||||
return await sequentialScreenPatch(patchFn, screenId)
|
||||
},
|
||||
replace: async (screenId, screen) => {
|
||||
if (!screenId) {
|
||||
return
|
||||
}
|
||||
if (!screen) {
|
||||
// Screen deletion
|
||||
store.update(state => ({
|
||||
...state,
|
||||
screens: state.screens.filter(x => x._id !== screenId),
|
||||
}))
|
||||
} else {
|
||||
const index = get(store).screens.findIndex(x => x._id === screen._id)
|
||||
if (index === -1) {
|
||||
// Screen addition
|
||||
store.update(state => ({
|
||||
...state,
|
||||
screens: [...state.screens, screen],
|
||||
}))
|
||||
} else {
|
||||
// Screen update
|
||||
store.update(state => {
|
||||
state.screens[index] = screen
|
||||
return state
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
delete: async screens => {
|
||||
const screensToDelete = Array.isArray(screens) ? screens : [screens]
|
||||
|
||||
|
@ -1305,7 +1333,7 @@ export const getFrontendStore = () => {
|
|||
links: {
|
||||
save: async (url, title) => {
|
||||
const navigation = get(store).navigation
|
||||
let links = [...navigation?.links]
|
||||
let links = [...(navigation?.links ?? [])]
|
||||
|
||||
// Skip if we have an identical link
|
||||
if (links.find(link => link.url === url && link.text === title)) {
|
||||
|
@ -1365,6 +1393,21 @@ export const getFrontendStore = () => {
|
|||
})
|
||||
},
|
||||
},
|
||||
websocket: {
|
||||
selectResource: id => {
|
||||
websocket.emit(BuilderSocketEvent.SelectResource, {
|
||||
resourceId: id,
|
||||
})
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
replace: metadata => {
|
||||
store.update(state => ({
|
||||
...state,
|
||||
...metadata,
|
||||
}))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return store
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { createWebsocket } from "@budibase/frontend-core"
|
||||
import { userStore, store } from "builderStore"
|
||||
import { userStore, store, deploymentStore } from "builderStore"
|
||||
import { datasources, tables } from "stores/backend"
|
||||
import { get } from "svelte/store"
|
||||
import { auth } from "stores/portal"
|
||||
import { SocketEvent, BuilderSocketEvent } from "@budibase/shared-core"
|
||||
import { apps } from "stores/portal"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import { helpers } from "@budibase/shared-core"
|
||||
|
||||
export const createBuilderWebsocket = appId => {
|
||||
const socket = createWebsocket("/socket/builder")
|
||||
|
@ -31,7 +33,6 @@ export const createBuilderWebsocket = appId => {
|
|||
})
|
||||
socket.onOther(BuilderSocketEvent.LockTransfer, ({ userId }) => {
|
||||
if (userId === get(auth)?.user?._id) {
|
||||
notifications.success("You can now edit screens and automations")
|
||||
store.update(state => ({
|
||||
...state,
|
||||
hasLock: true,
|
||||
|
@ -39,15 +40,32 @@ export const createBuilderWebsocket = appId => {
|
|||
}
|
||||
})
|
||||
|
||||
// Table events
|
||||
// Data section events
|
||||
socket.onOther(BuilderSocketEvent.TableChange, ({ id, table }) => {
|
||||
tables.replaceTable(id, table)
|
||||
})
|
||||
|
||||
// Datasource events
|
||||
socket.onOther(BuilderSocketEvent.DatasourceChange, ({ id, datasource }) => {
|
||||
datasources.replaceDatasource(id, datasource)
|
||||
})
|
||||
|
||||
// Design section events
|
||||
socket.onOther(BuilderSocketEvent.ScreenChange, ({ id, screen }) => {
|
||||
store.actions.screens.replace(id, screen)
|
||||
})
|
||||
socket.onOther(BuilderSocketEvent.AppMetadataChange, ({ metadata }) => {
|
||||
store.actions.metadata.replace(metadata)
|
||||
})
|
||||
socket.onOther(
|
||||
BuilderSocketEvent.AppPublishChange,
|
||||
async ({ user, published }) => {
|
||||
await apps.load()
|
||||
if (published) {
|
||||
await deploymentStore.actions.load()
|
||||
}
|
||||
const verb = published ? "published" : "unpublished"
|
||||
notifications.success(`${helpers.getUserLabel(user)} ${verb} this app`)
|
||||
}
|
||||
)
|
||||
|
||||
return socket
|
||||
}
|
||||
|
|
|
@ -168,7 +168,7 @@
|
|||
<Layout noPadding gap="XS">
|
||||
<Detail size="S">Plugins</Detail>
|
||||
<div class="item-list">
|
||||
{#each Object.entries(plugins) as [idx, action]}
|
||||
{#each Object.entries(plugins) as [_, action]}
|
||||
<div
|
||||
class="item"
|
||||
class:selected={selectedAction === action.name}
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
</script>
|
||||
|
||||
<div>
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags-->
|
||||
{@html html}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
<Layout noPadding gap="XS">
|
||||
<Label size="S">Trigger</Label>
|
||||
<div class="item-list">
|
||||
{#each triggers as [idx, trigger]}
|
||||
{#each triggers as [_, trigger]}
|
||||
<div
|
||||
class="item"
|
||||
class:selected={selectedTrigger === trigger.name}
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
import { goto, params } from "@roxi/routify"
|
||||
import { Table, Heading, Layout } from "@budibase/bbui"
|
||||
import Spinner from "components/common/Spinner.svelte"
|
||||
import CreateEditRow from "./modals/CreateEditRow.svelte"
|
||||
import CreateEditUser from "./modals/CreateEditUser.svelte"
|
||||
import {
|
||||
TableNames,
|
||||
UNEDITABLE_USER_FIELDS,
|
||||
|
@ -33,7 +31,6 @@
|
|||
$: selectedRows, dispatch("selectionUpdated", selectedRows)
|
||||
$: isUsersTable = tableId === TableNames.USERS
|
||||
$: data && resetSelectedRows()
|
||||
$: editRowComponent = isUsersTable ? CreateEditUser : CreateEditRow
|
||||
$: {
|
||||
UNSORTABLE_TYPES.forEach(type => {
|
||||
Object.values(schema || {}).forEach(col => {
|
||||
|
|
|
@ -57,7 +57,6 @@
|
|||
|
||||
let table = $tables.selected
|
||||
let confirmDeleteDialog
|
||||
let deletion
|
||||
let savingColumn
|
||||
let deleteColName
|
||||
let jsonSchemaModal
|
||||
|
@ -215,7 +214,6 @@
|
|||
notifications.success(`Column ${editableColumn.name} deleted`)
|
||||
confirmDeleteDialog.hide()
|
||||
hide()
|
||||
deletion = false
|
||||
dispatch("updatecolumns")
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -266,13 +264,11 @@
|
|||
|
||||
function confirmDelete() {
|
||||
confirmDeleteDialog.show()
|
||||
deletion = true
|
||||
}
|
||||
|
||||
function hideDeleteDialog() {
|
||||
confirmDeleteDialog.hide()
|
||||
deleteColName = ""
|
||||
deletion = false
|
||||
}
|
||||
|
||||
function getRelationshipOptions(field) {
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
<script>
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { tables } from "stores/backend"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import { ModalContent, keepOpen, notifications } from "@budibase/bbui"
|
||||
import RowFieldControl from "../RowFieldControl.svelte"
|
||||
import { API } from "api"
|
||||
import { ModalContent } from "@budibase/bbui"
|
||||
import { FIELDS } from "constants/backend"
|
||||
|
||||
const FORMULA_TYPE = FIELDS.FORMULA.type
|
||||
|
@ -41,8 +40,8 @@
|
|||
} else {
|
||||
notifications.error(`Failed to save row - ${error.message}`)
|
||||
}
|
||||
// Prevent modal closing if there were errors
|
||||
return false
|
||||
|
||||
return keepOpen
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import { notifications } from "@budibase/bbui"
|
||||
import RowFieldControl from "../RowFieldControl.svelte"
|
||||
import { API } from "api"
|
||||
import { ModalContent, Select, Link } from "@budibase/bbui"
|
||||
import { keepOpen, ModalContent, Select, Link } from "@budibase/bbui"
|
||||
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
||||
import { goto } from "@roxi/routify"
|
||||
|
||||
|
@ -51,7 +51,7 @@
|
|||
errors = [...errors, { message: "Role is required" }]
|
||||
}
|
||||
if (errors.length) {
|
||||
return false
|
||||
return keepOpen
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -79,8 +79,8 @@
|
|||
} else {
|
||||
notifications.error("Error saving user")
|
||||
}
|
||||
// Prevent closing the modal on errors
|
||||
return false
|
||||
|
||||
return keepOpen
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { ModalContent, Select, Input, Button } from "@budibase/bbui"
|
||||
import { keepOpen, ModalContent, Select, Input, Button } from "@budibase/bbui"
|
||||
import { onMount } from "svelte"
|
||||
import { API } from "api"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
|
@ -76,7 +76,7 @@
|
|||
errors.push({ message: "Please choose permissions" })
|
||||
}
|
||||
if (errors.length) {
|
||||
return false
|
||||
return keepOpen
|
||||
}
|
||||
|
||||
// Save/create the role
|
||||
|
@ -85,7 +85,7 @@
|
|||
notifications.success("Role saved successfully")
|
||||
} catch (error) {
|
||||
notifications.error(`Error saving role - ${error.message}`)
|
||||
return false
|
||||
return keepOpen
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
} from "helpers/data/utils"
|
||||
import IntegrationIcon from "./IntegrationIcon.svelte"
|
||||
import { TableNames } from "constants"
|
||||
import { userSelectedResourceMap } from "builderStore"
|
||||
|
||||
let openDataSources = []
|
||||
|
||||
|
@ -166,8 +167,9 @@
|
|||
selected={$isActive("./table/:tableId") &&
|
||||
$tables.selected?._id === TableNames.USERS}
|
||||
on:click={() => selectTable(TableNames.USERS)}
|
||||
selectedBy={$userSelectedResourceMap[TableNames.USERS]}
|
||||
/>
|
||||
{#each enrichedDataSources as datasource, idx}
|
||||
{#each enrichedDataSources as datasource}
|
||||
<NavItem
|
||||
border
|
||||
text={datasource.name}
|
||||
|
@ -176,6 +178,7 @@
|
|||
withArrow={true}
|
||||
on:click={() => selectDatasource(datasource)}
|
||||
on:iconClick={() => toggleNode(datasource)}
|
||||
selectedBy={$userSelectedResourceMap[datasource._id]}
|
||||
>
|
||||
<div class="datasource-icon" slot="icon">
|
||||
<IntegrationIcon
|
||||
|
@ -201,6 +204,7 @@
|
|||
selected={$isActive("./query/:queryId") &&
|
||||
$queries.selectedQueryId === query._id}
|
||||
on:click={() => $goto(`./query/${query._id}`)}
|
||||
selectedBy={$userSelectedResourceMap[query._id]}
|
||||
>
|
||||
<EditQueryPopover {query} />
|
||||
</NavItem>
|
||||
|
@ -212,7 +216,7 @@
|
|||
|
||||
<style>
|
||||
.hierarchy-items-container {
|
||||
margin: 0 calc(-1 * var(--spacing-xl));
|
||||
margin: 0 calc(-1 * var(--spacing-l));
|
||||
}
|
||||
.datasource-icon {
|
||||
display: grid;
|
||||
|
|
|
@ -31,65 +31,65 @@
|
|||
<path
|
||||
class="st1"
|
||||
d="M-83.19,48h-41.79c-1.76,0-3.19-1.43-3.19-3.19V3.02c0-1.76,1.43-3.19,3.19-3.19h41.79
|
||||
c1.76,0,3.19,1.43,3.19,3.19v41.79C-80,46.57-81.43,48-83.19,48z"
|
||||
c1.76,0,3.19,1.43,3.19,3.19v41.79C-80,46.57-81.43,48-83.19,48z"
|
||||
/>
|
||||
<g>
|
||||
<g>
|
||||
<path
|
||||
class="st0"
|
||||
d="M-99.62,12.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58c0.86,0.39,1.59,0.91,2.19,1.57
|
||||
c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89c-0.35,0.9-0.84,1.68-1.47,2.35
|
||||
c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V35h-4.89V12.57H-99.62z
|
||||
M-93.46,28.11c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69
|
||||
c-0.38-0.17-0.79-0.26-1.24-0.26c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01
|
||||
c-0.17,0.39-0.26,0.8-0.26,1.23c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68
|
||||
c0.39,0.17,0.8,0.26,1.23,0.26c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1
|
||||
C-93.55,28.92-93.46,28.52-93.46,28.11z"
|
||||
c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89c-0.35,0.9-0.84,1.68-1.47,2.35
|
||||
c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V35h-4.89V12.57H-99.62z
|
||||
M-93.46,28.11c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69
|
||||
c-0.38-0.17-0.79-0.26-1.24-0.26c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01
|
||||
c-0.17,0.39-0.26,0.8-0.26,1.23c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68
|
||||
c0.39,0.17,0.8,0.26,1.23,0.26c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1
|
||||
C-93.55,28.92-93.46,28.52-93.46,28.11z"
|
||||
/>
|
||||
</g>
|
||||
<g>
|
||||
<path
|
||||
class="st0"
|
||||
d="M-114.76,12.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58
|
||||
c0.86,0.39,1.59,0.91,2.19,1.57c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89
|
||||
c-0.35,0.9-0.84,1.68-1.47,2.35c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V35
|
||||
h-4.89V12.57H-114.76z M-108.6,28.11c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69
|
||||
c-0.38-0.17-0.79-0.26-1.24-0.26c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01
|
||||
c-0.17,0.39-0.26,0.8-0.26,1.23c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68
|
||||
c0.39,0.17,0.8,0.26,1.23,0.26c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1
|
||||
C-108.68,28.92-108.6,28.52-108.6,28.11z"
|
||||
c0.86,0.39,1.59,0.91,2.19,1.57c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89
|
||||
c-0.35,0.9-0.84,1.68-1.47,2.35c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V35
|
||||
h-4.89V12.57H-114.76z M-108.6,28.11c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69
|
||||
c-0.38-0.17-0.79-0.26-1.24-0.26c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01
|
||||
c-0.17,0.39-0.26,0.8-0.26,1.23c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68
|
||||
c0.39,0.17,0.8,0.26,1.23,0.26c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1
|
||||
C-108.68,28.92-108.6,28.52-108.6,28.11z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<path
|
||||
class="st2"
|
||||
d="M44.81,159H3.02c-1.76,0-3.19-1.43-3.19-3.19v-41.79c0-1.76,1.43-3.19,3.19-3.19h41.79
|
||||
c1.76,0,3.19,1.43,3.19,3.19v41.79C48,157.57,46.57,159,44.81,159z"
|
||||
c1.76,0,3.19,1.43,3.19,3.19v41.79C48,157.57,46.57,159,44.81,159z"
|
||||
/>
|
||||
<g>
|
||||
<g>
|
||||
<path
|
||||
class="st1"
|
||||
d="M28.38,123.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58c0.86,0.39,1.59,0.91,2.19,1.57
|
||||
c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89c-0.35,0.9-0.84,1.68-1.47,2.35
|
||||
c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V146h-4.89v-22.43H28.38z
|
||||
M34.54,139.11c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69
|
||||
c-0.38-0.17-0.79-0.26-1.24-0.26c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01
|
||||
c-0.17,0.39-0.26,0.8-0.26,1.23c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68
|
||||
c0.39,0.17,0.8,0.26,1.23,0.26c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1
|
||||
C34.45,139.92,34.54,139.52,34.54,139.11z"
|
||||
c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89c-0.35,0.9-0.84,1.68-1.47,2.35
|
||||
c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V146h-4.89v-22.43H28.38z
|
||||
M34.54,139.11c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69
|
||||
c-0.38-0.17-0.79-0.26-1.24-0.26c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01
|
||||
c-0.17,0.39-0.26,0.8-0.26,1.23c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68
|
||||
c0.39,0.17,0.8,0.26,1.23,0.26c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1
|
||||
C34.45,139.92,34.54,139.52,34.54,139.11z"
|
||||
/>
|
||||
</g>
|
||||
<g>
|
||||
<path
|
||||
class="st1"
|
||||
d="M13.24,123.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58c0.86,0.39,1.59,0.91,2.19,1.57
|
||||
c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89c-0.35,0.9-0.84,1.68-1.47,2.35
|
||||
c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V146H8.35v-22.43H13.24z M19.4,139.11
|
||||
c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69c-0.38-0.17-0.79-0.26-1.24-0.26
|
||||
c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01c-0.17,0.39-0.26,0.8-0.26,1.23
|
||||
c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68c0.39,0.17,0.8,0.26,1.23,0.26
|
||||
c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1C19.32,139.92,19.4,139.52,19.4,139.11z"
|
||||
c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89c-0.35,0.9-0.84,1.68-1.47,2.35
|
||||
c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V146H8.35v-22.43H13.24z M19.4,139.11
|
||||
c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69c-0.38-0.17-0.79-0.26-1.24-0.26
|
||||
c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01c-0.17,0.39-0.26,0.8-0.26,1.23
|
||||
c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68c0.39,0.17,0.8,0.26,1.23,0.26
|
||||
c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1C19.32,139.92,19.4,139.52,19.4,139.11z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
|
@ -102,24 +102,24 @@
|
|||
<path
|
||||
class="st1"
|
||||
d="M28.48,12v10.44c1.18-1.27,2.65-1.9,4.42-1.9c1.05,0,2.01,0.2,2.89,0.61c0.87,0.41,1.62,0.96,2.24,1.65
|
||||
c0.62,0.69,1.1,1.5,1.45,2.44c0.35,0.94,0.52,1.93,0.52,2.99c0,1.08-0.18,2.09-0.54,3.04c-0.36,0.95-0.86,1.77-1.51,2.47
|
||||
c-0.64,0.7-1.4,1.25-2.28,1.66C34.8,35.8,33.86,36,32.84,36c-1.84,0-3.3-0.69-4.37-2.07v1.62h-5V12H28.48z M34.78,28.31
|
||||
c0-0.45-0.08-0.88-0.25-1.29c-0.17-0.41-0.4-0.76-0.69-1.06c-0.3-0.3-0.64-0.54-1.02-0.72c-0.39-0.18-0.81-0.27-1.27-0.27
|
||||
c-0.44,0-0.86,0.09-1.24,0.26c-0.39,0.17-0.72,0.41-1.01,0.71c-0.29,0.3-0.52,0.66-0.69,1.06c-0.18,0.41-0.26,0.84-0.26,1.29
|
||||
s0.08,0.88,0.25,1.28c0.17,0.4,0.4,0.74,0.69,1.04c0.29,0.29,0.64,0.53,1.04,0.71c0.4,0.18,0.82,0.27,1.26,0.27
|
||||
c0.44,0,0.86-0.09,1.24-0.26c0.39-0.17,0.72-0.41,1.01-0.71c0.29-0.3,0.52-0.65,0.69-1.05C34.69,29.16,34.78,28.75,34.78,28.31z"
|
||||
c0.62,0.69,1.1,1.5,1.45,2.44c0.35,0.94,0.52,1.93,0.52,2.99c0,1.08-0.18,2.09-0.54,3.04c-0.36,0.95-0.86,1.77-1.51,2.47
|
||||
c-0.64,0.7-1.4,1.25-2.28,1.66C34.8,35.8,33.86,36,32.84,36c-1.84,0-3.3-0.69-4.37-2.07v1.62h-5V12H28.48z M34.78,28.31
|
||||
c0-0.45-0.08-0.88-0.25-1.29c-0.17-0.41-0.4-0.76-0.69-1.06c-0.3-0.3-0.64-0.54-1.02-0.72c-0.39-0.18-0.81-0.27-1.27-0.27
|
||||
c-0.44,0-0.86,0.09-1.24,0.26c-0.39,0.17-0.72,0.41-1.01,0.71c-0.29,0.3-0.52,0.66-0.69,1.06c-0.18,0.41-0.26,0.84-0.26,1.29
|
||||
s0.08,0.88,0.25,1.28c0.17,0.4,0.4,0.74,0.69,1.04c0.29,0.29,0.64,0.53,1.04,0.71c0.4,0.18,0.82,0.27,1.26,0.27
|
||||
c0.44,0,0.86-0.09,1.24-0.26c0.39-0.17,0.72-0.41,1.01-0.71c0.29-0.3,0.52-0.65,0.69-1.05C34.69,29.16,34.78,28.75,34.78,28.31z"
|
||||
/>
|
||||
</g>
|
||||
<g>
|
||||
<path
|
||||
class="st1"
|
||||
d="M13,12v10.44c1.18-1.27,2.65-1.9,4.42-1.9c1.05,0,2.01,0.2,2.89,0.61c0.87,0.41,1.62,0.96,2.24,1.65
|
||||
c0.62,0.69,1.1,1.5,1.45,2.44c0.35,0.94,0.52,1.93,0.52,2.99c0,1.08-0.18,2.09-0.54,3.04c-0.36,0.95-0.86,1.77-1.51,2.47
|
||||
c-0.64,0.7-1.4,1.25-2.28,1.66C19.32,35.8,18.38,36,17.37,36c-1.84,0-3.3-0.69-4.37-2.07v1.62H8V12H13z M19.3,28.31
|
||||
c0-0.45-0.08-0.88-0.25-1.29c-0.17-0.41-0.4-0.76-0.69-1.06c-0.3-0.3-0.64-0.54-1.02-0.72c-0.39-0.18-0.81-0.27-1.27-0.27
|
||||
c-0.44,0-0.86,0.09-1.24,0.26c-0.39,0.17-0.72,0.41-1.01,0.71c-0.29,0.3-0.52,0.66-0.69,1.06c-0.18,0.41-0.26,0.84-0.26,1.29
|
||||
s0.08,0.88,0.25,1.28c0.17,0.4,0.4,0.74,0.69,1.04c0.29,0.29,0.64,0.53,1.04,0.71c0.4,0.18,0.82,0.27,1.26,0.27
|
||||
c0.44,0,0.86-0.09,1.24-0.26c0.39-0.17,0.72-0.41,1.01-0.71c0.29-0.3,0.52-0.65,0.69-1.05C19.21,29.16,19.3,28.75,19.3,28.31z"
|
||||
c0.62,0.69,1.1,1.5,1.45,2.44c0.35,0.94,0.52,1.93,0.52,2.99c0,1.08-0.18,2.09-0.54,3.04c-0.36,0.95-0.86,1.77-1.51,2.47
|
||||
c-0.64,0.7-1.4,1.25-2.28,1.66C19.32,35.8,18.38,36,17.37,36c-1.84,0-3.3-0.69-4.37-2.07v1.62H8V12H13z M19.3,28.31
|
||||
c0-0.45-0.08-0.88-0.25-1.29c-0.17-0.41-0.4-0.76-0.69-1.06c-0.3-0.3-0.64-0.54-1.02-0.72c-0.39-0.18-0.81-0.27-1.27-0.27
|
||||
c-0.44,0-0.86,0.09-1.24,0.26c-0.39,0.17-0.72,0.41-1.01,0.71c-0.29,0.3-0.52,0.66-0.69,1.06c-0.18,0.41-0.26,0.84-0.26,1.29
|
||||
s0.08,0.88,0.25,1.28c0.17,0.4,0.4,0.74,0.69,1.04c0.29,0.29,0.64,0.53,1.04,0.71c0.4,0.18,0.82,0.27,1.26,0.27
|
||||
c0.44,0,0.86-0.09,1.24-0.26c0.39-0.17,0.72-0.41,1.01-0.71c0.29-0.3,0.52-0.65,0.69-1.05C19.21,29.16,19.3,28.75,19.3,28.31z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { goto } from "@roxi/routify"
|
||||
import {
|
||||
keepOpen,
|
||||
ModalContent,
|
||||
notifications,
|
||||
Body,
|
||||
|
@ -70,10 +71,9 @@
|
|||
}
|
||||
|
||||
notifications.success(`Imported successfully.`)
|
||||
return true
|
||||
} catch (error) {
|
||||
notifications.error("Error importing queries")
|
||||
return false
|
||||
return keepOpen
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import {
|
||||
keepOpen,
|
||||
Modal,
|
||||
notifications,
|
||||
Body,
|
||||
|
@ -36,7 +37,7 @@
|
|||
})
|
||||
}
|
||||
|
||||
return false
|
||||
return keepOpen
|
||||
}
|
||||
|
||||
let createVariableModal
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { RelationshipTypes } from "constants/backend"
|
||||
import {
|
||||
keepOpen,
|
||||
Button,
|
||||
Input,
|
||||
ModalContent,
|
||||
|
@ -277,7 +278,7 @@
|
|||
|
||||
async function saveRelationship() {
|
||||
if (!validate()) {
|
||||
return false
|
||||
return keepOpen
|
||||
}
|
||||
buildRelationships()
|
||||
removeExistingRelationship()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { derived, writable, get } from "svelte/store"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import { datasources, ImportTableError } from "stores/backend"
|
||||
import { keepOpen, notifications } from "@budibase/bbui"
|
||||
import { datasources, ImportTableError, tables } from "stores/backend"
|
||||
|
||||
export const createTableSelectionStore = (integration, datasource) => {
|
||||
const tableNamesStore = writable([])
|
||||
|
@ -27,7 +27,7 @@ export const createTableSelectionStore = (integration, datasource) => {
|
|||
|
||||
try {
|
||||
await datasources.updateSchema(datasource, get(selectedTableNamesStore))
|
||||
|
||||
await tables.fetch()
|
||||
notifications.success(`Tables fetched successfully.`)
|
||||
await onComplete()
|
||||
} catch (err) {
|
||||
|
@ -37,8 +37,7 @@ export const createTableSelectionStore = (integration, datasource) => {
|
|||
notifications.error("Error fetching tables.")
|
||||
}
|
||||
|
||||
// Prevent modal closing
|
||||
return false
|
||||
return keepOpen
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
let error = null
|
||||
let fileName = null
|
||||
let fileType = null
|
||||
|
||||
let loading = false
|
||||
let updateExistingRows = false
|
||||
|
@ -74,7 +73,6 @@
|
|||
const response = await parseFile(e)
|
||||
rows = response.rows
|
||||
fileName = response.fileName
|
||||
fileType = response.fileType
|
||||
} catch (e) {
|
||||
loading = false
|
||||
error = e
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
let fileInput
|
||||
let error = null
|
||||
let fileName = null
|
||||
let fileType = null
|
||||
|
||||
let loading = false
|
||||
let validation = {}
|
||||
|
@ -60,7 +59,6 @@
|
|||
rows = response.rows
|
||||
schema = response.schema
|
||||
fileName = response.fileName
|
||||
fileType = response.fileType
|
||||
} catch (e) {
|
||||
loading = false
|
||||
error = e
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
import EditViewPopover from "./popovers/EditViewPopover.svelte"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
import { goto, isActive } from "@roxi/routify"
|
||||
import { userSelectedResourceMap } from "builderStore"
|
||||
|
||||
const alphabetical = (a, b) =>
|
||||
a.name?.toLowerCase() > b.name?.toLowerCase() ? 1 : -1
|
||||
|
@ -30,6 +31,7 @@
|
|||
selected={$isActive("./table/:tableId") &&
|
||||
$tables.selected?._id === table._id}
|
||||
on:click={() => selectTable(table._id)}
|
||||
selectedBy={$userSelectedResourceMap[table._id]}
|
||||
>
|
||||
{#if table._id !== TableNames.USERS}
|
||||
<EditTablePopover {table} />
|
||||
|
@ -42,6 +44,7 @@
|
|||
text={viewName}
|
||||
selected={$isActive("./view") && $views.selected?.name === viewName}
|
||||
on:click={() => $goto(`./view/${encodeURIComponent(viewName)}`)}
|
||||
selectedBy={$userSelectedResourceMap[viewName]}
|
||||
>
|
||||
<EditViewPopover
|
||||
view={{ name: viewName, ...table.views[viewName] }}
|
||||
|
|
|
@ -93,42 +93,42 @@
|
|||
`https://github.com/Budibase/budibase/issues/new?assignees=&labels=bug&template=bug_report.md&title=`
|
||||
),
|
||||
},
|
||||
...$datasources?.list.map(datasource => ({
|
||||
...($datasources?.list?.map(datasource => ({
|
||||
type: "Datasource",
|
||||
name: `${datasource.name}`,
|
||||
icon: "Data",
|
||||
action: () => $goto(`./data/datasource/${datasource._id}`),
|
||||
})),
|
||||
...$tables?.list.map(table => ({
|
||||
})) ?? []),
|
||||
...($tables?.list?.map(table => ({
|
||||
type: "Table",
|
||||
name: table.name,
|
||||
icon: "Table",
|
||||
action: () => $goto(`./data/table/${table._id}`),
|
||||
})),
|
||||
...$views?.list.map(view => ({
|
||||
})) ?? []),
|
||||
...($views?.list?.map(view => ({
|
||||
type: "View",
|
||||
name: view.name,
|
||||
icon: "Remove",
|
||||
action: () => $goto(`./data/view/${view.name}`),
|
||||
})),
|
||||
...$queries?.list.map(query => ({
|
||||
})) ?? []),
|
||||
...($queries?.list?.map(query => ({
|
||||
type: "Query",
|
||||
name: query.name,
|
||||
icon: "SQLQuery",
|
||||
action: () => $goto(`./data/query/${query._id}`),
|
||||
})),
|
||||
})) ?? []),
|
||||
...$sortedScreens.map(screen => ({
|
||||
type: "Screen",
|
||||
name: screen.routing.route,
|
||||
icon: "WebPage",
|
||||
action: () => $goto(`./design/${screen._id}/components`),
|
||||
})),
|
||||
...$automationStore?.automations.map(automation => ({
|
||||
...($automationStore?.automations?.map(automation => ({
|
||||
type: "Automation",
|
||||
name: automation.name,
|
||||
icon: "ShareAndroid",
|
||||
action: () => $goto(`./automation/${automation._id}`),
|
||||
})),
|
||||
})) ?? []),
|
||||
...Constants.Themes.map(theme => ({
|
||||
type: "Change Builder Theme",
|
||||
name: theme.name,
|
||||
|
@ -208,8 +208,8 @@
|
|||
|
||||
async function deployApp() {
|
||||
try {
|
||||
await API.deployAppChanges()
|
||||
notifications.success("Application published successfully")
|
||||
await API.publishAppChanges($store.appId)
|
||||
notifications.success("App published successfully")
|
||||
} catch (error) {
|
||||
notifications.error("Error publishing app")
|
||||
}
|
||||
|
@ -237,11 +237,11 @@
|
|||
<Input bind:value={search} quiet placeholder="Search for command" />
|
||||
</div>
|
||||
<div class="commands">
|
||||
{#each categories as [name, results], catIdx}
|
||||
{#each categories as [name, results]}
|
||||
<div class="category">
|
||||
<Detail>{name}</Detail>
|
||||
<div class="options">
|
||||
{#each results as command, cmdIdx}
|
||||
{#each results as command}
|
||||
<div
|
||||
class="command"
|
||||
on:click={() => runAction(command)}
|
||||
|
|
|
@ -20,4 +20,5 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags-->
|
||||
{@html substituteSize(svgHtml)}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { Icon } from "@budibase/bbui"
|
||||
import { createEventDispatcher, getContext } from "svelte"
|
||||
import { helpers } from "@budibase/shared-core"
|
||||
|
||||
export let icon
|
||||
export let withArrow = false
|
||||
|
@ -18,12 +19,15 @@
|
|||
export let rightAlignIcon = false
|
||||
export let id
|
||||
export let showTooltip = false
|
||||
export let selectedBy = null
|
||||
|
||||
const scrollApi = getContext("scroll")
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let contentRef
|
||||
|
||||
$: selected && contentRef && scrollToView()
|
||||
$: style = getStyle(indentLevel, selectedBy)
|
||||
|
||||
const onClick = () => {
|
||||
scrollToView()
|
||||
|
@ -42,6 +46,14 @@
|
|||
const bounds = contentRef.getBoundingClientRect()
|
||||
scrollApi.scrollTo(bounds)
|
||||
}
|
||||
|
||||
const getStyle = (indentLevel, selectedBy) => {
|
||||
let style = `padding-left:calc(${indentLevel * 14}px);`
|
||||
if (selectedBy) {
|
||||
style += `--selected-by-color:${helpers.getUserColor(selectedBy)};`
|
||||
}
|
||||
return style
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
|
@ -51,8 +63,7 @@
|
|||
class:withActions
|
||||
class:scrollable
|
||||
class:highlighted
|
||||
style={`padding-left: calc(${indentLevel * 14}px)`}
|
||||
{draggable}
|
||||
class:selectedBy
|
||||
on:dragend
|
||||
on:dragstart
|
||||
on:dragover
|
||||
|
@ -61,6 +72,8 @@
|
|||
ondragover="return false"
|
||||
ondragenter="return false"
|
||||
{id}
|
||||
{style}
|
||||
{draggable}
|
||||
>
|
||||
<div class="nav-item-content" bind:this={contentRef}>
|
||||
{#if withArrow}
|
||||
|
@ -97,6 +110,9 @@
|
|||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if selectedBy}
|
||||
<div class="selected-by-label">{helpers.getUserLabel(selectedBy)}</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
@ -111,6 +127,7 @@
|
|||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
position: relative;
|
||||
}
|
||||
.nav-item.scrollable {
|
||||
flex-direction: column;
|
||||
|
@ -142,6 +159,37 @@
|
|||
padding-left: var(--spacing-l);
|
||||
}
|
||||
|
||||
/* Selected user styles */
|
||||
.nav-item.selectedBy:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: calc(100% - 4px);
|
||||
height: 28px;
|
||||
border: 2px solid var(--selected-by-color);
|
||||
left: 0;
|
||||
top: 0;
|
||||
border-radius: 2px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.selected-by-label {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background: var(--selected-by-color);
|
||||
padding: 2px 4px;
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
transform: translateY(calc(1px - 100%));
|
||||
border-top-right-radius: 2px;
|
||||
border-top-left-radius: 2px;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 130ms ease-out;
|
||||
}
|
||||
.nav-item.selectedBy:hover .selected-by-label {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Needed to fully display the actions icon */
|
||||
.nav-item.scrollable .nav-item-content {
|
||||
padding-right: 1px;
|
||||
|
|
|
@ -88,6 +88,7 @@
|
|||
{/if}
|
||||
{#if hoverTarget.description}
|
||||
<div class="helper__description">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags-->
|
||||
{@html hoverTarget.description}
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -124,7 +125,6 @@
|
|||
/>
|
||||
</span>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<span
|
||||
class="search-input-icon"
|
||||
on:click={() => {
|
||||
|
@ -162,7 +162,6 @@
|
|||
</div>
|
||||
<ul>
|
||||
{#each category.bindings as binding}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<li
|
||||
class="binding"
|
||||
on:mouseenter={e => {
|
||||
|
|
|
@ -14,15 +14,12 @@
|
|||
import RevertModal from "components/deploy/RevertModal.svelte"
|
||||
import VersionModal from "components/deploy/VersionModal.svelte"
|
||||
import UpdateAppModal from "components/start/UpdateAppModal.svelte"
|
||||
|
||||
import { processStringSync } from "@budibase/string-templates"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import analytics, { Events, EventSource } from "analytics"
|
||||
import { checkIncomingDeploymentStatus } from "components/deploy/utils"
|
||||
import { API } from "api"
|
||||
import { onMount } from "svelte"
|
||||
import { apps } from "stores/portal"
|
||||
import { store } from "builderStore"
|
||||
import { deploymentStore, store, isOnlyUser } from "builderStore"
|
||||
import TourWrap from "components/portal/onboarding/TourWrap.svelte"
|
||||
import { TOUR_STEP_KEYS } from "components/portal/onboarding/tours.js"
|
||||
import { goto } from "@roxi/routify"
|
||||
|
@ -34,37 +31,31 @@
|
|||
let updateAppModal
|
||||
let revertModal
|
||||
let versionModal
|
||||
|
||||
let appActionPopover
|
||||
let appActionPopoverOpen = false
|
||||
let appActionPopoverAnchor
|
||||
|
||||
let publishing = false
|
||||
|
||||
$: filteredApps = $apps.filter(app => app.devId === application)
|
||||
$: selectedApp = filteredApps?.length ? filteredApps[0] : null
|
||||
|
||||
$: deployments = []
|
||||
$: latestDeployments = deployments
|
||||
$: latestDeployments = $deploymentStore
|
||||
.filter(deployment => deployment.status === "SUCCESS")
|
||||
.sort((a, b) => a.updatedAt > b.updatedAt)
|
||||
|
||||
$: isPublished =
|
||||
selectedApp?.status === "published" && latestDeployments?.length > 0
|
||||
|
||||
$: updateAvailable =
|
||||
$store.upgradableVersion &&
|
||||
$store.version &&
|
||||
$store.upgradableVersion !== $store.version
|
||||
|
||||
$: canPublish = !publishing && loaded
|
||||
$: lastDeployed = getLastDeployedString($deploymentStore)
|
||||
|
||||
const initialiseApp = async () => {
|
||||
const applicationPkg = await API.fetchAppPackage($store.devId)
|
||||
await store.actions.initialise(applicationPkg)
|
||||
}
|
||||
|
||||
const updateDeploymentString = () => {
|
||||
const getLastDeployedString = deployments => {
|
||||
return deployments?.length
|
||||
? processStringSync("Published {{ duration time 'millisecond' }} ago", {
|
||||
time:
|
||||
|
@ -73,27 +64,6 @@
|
|||
: ""
|
||||
}
|
||||
|
||||
const reviewPendingDeployments = (deployments, newDeployments) => {
|
||||
if (deployments.length > 0) {
|
||||
const pending = checkIncomingDeploymentStatus(deployments, newDeployments)
|
||||
if (pending.length) {
|
||||
notifications.warning(
|
||||
"Deployment has been queued and will be processed shortly"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchDeployments() {
|
||||
try {
|
||||
const newDeployments = await API.getAppDeployments()
|
||||
reviewPendingDeployments(deployments, newDeployments)
|
||||
return newDeployments
|
||||
} catch (err) {
|
||||
notifications.error("Error fetching deployment overview")
|
||||
}
|
||||
}
|
||||
|
||||
const previewApp = () => {
|
||||
store.update(state => ({
|
||||
...state,
|
||||
|
@ -116,14 +86,11 @@
|
|||
async function publishApp() {
|
||||
try {
|
||||
publishing = true
|
||||
|
||||
await API.publishAppChanges($store.appId)
|
||||
|
||||
notifications.send("App published", {
|
||||
notifications.send("App published successfully", {
|
||||
type: "success",
|
||||
icon: "GlobeCheck",
|
||||
})
|
||||
|
||||
await completePublish()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
@ -163,210 +130,196 @@
|
|||
const completePublish = async () => {
|
||||
try {
|
||||
await apps.load()
|
||||
deployments = await fetchDeployments()
|
||||
await deploymentStore.actions.load()
|
||||
} catch (err) {
|
||||
notifications.error("Error refreshing app")
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
if (!$apps.length) {
|
||||
await apps.load()
|
||||
}
|
||||
deployments = await fetchDeployments()
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if $store.hasLock}
|
||||
<div class="action-top-nav" class:has-lock={$store.hasLock}>
|
||||
<div class="action-buttons">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
{#if updateAvailable}
|
||||
<div class="app-action-button version" on:click={versionModal.show}>
|
||||
<div class="app-action">
|
||||
<ActionButton quiet>
|
||||
<StatusLight notice />
|
||||
Update
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<TourWrap
|
||||
tourStepKey={$store.onboarding
|
||||
? TOUR_STEP_KEYS.BUILDER_USER_MANAGEMENT
|
||||
: TOUR_STEP_KEYS.FEATURE_USER_MANAGEMENT}
|
||||
>
|
||||
<div class="app-action-button users">
|
||||
<div class="app-action" id="builder-app-users-button">
|
||||
<ActionButton
|
||||
quiet
|
||||
icon="UserGroup"
|
||||
on:click={() => {
|
||||
store.update(state => {
|
||||
state.builderSidePanel = true
|
||||
return state
|
||||
})
|
||||
}}
|
||||
>
|
||||
Users
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
</TourWrap>
|
||||
|
||||
<div class="app-action-button preview">
|
||||
<div class="action-top-nav">
|
||||
<div class="action-buttons">
|
||||
{#if updateAvailable && $isOnlyUser}
|
||||
<div class="app-action-button version" on:click={versionModal.show}>
|
||||
<div class="app-action">
|
||||
<ActionButton quiet icon="PlayCircle" on:click={previewApp}>
|
||||
Preview
|
||||
<ActionButton quiet>
|
||||
<StatusLight notice />
|
||||
Update
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="app-action-button publish app-action-popover"
|
||||
on:click={() => {
|
||||
if (!appActionPopoverOpen) {
|
||||
appActionPopover.show()
|
||||
} else {
|
||||
appActionPopover.hide()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div bind:this={appActionPopoverAnchor}>
|
||||
<div class="app-action">
|
||||
<Icon name={isPublished ? "GlobeCheck" : "GlobeStrike"} />
|
||||
<TourWrap tourStepKey={TOUR_STEP_KEYS.BUILDER_APP_PUBLISH}>
|
||||
<span class="publish-open" id="builder-app-publish-button">
|
||||
Publish
|
||||
<Icon
|
||||
name={appActionPopoverOpen ? "ChevronUp" : "ChevronDown"}
|
||||
size="M"
|
||||
/>
|
||||
</span>
|
||||
</TourWrap>
|
||||
</div>
|
||||
{/if}
|
||||
<TourWrap
|
||||
tourStepKey={$store.onboarding
|
||||
? TOUR_STEP_KEYS.BUILDER_USER_MANAGEMENT
|
||||
: TOUR_STEP_KEYS.FEATURE_USER_MANAGEMENT}
|
||||
>
|
||||
<div class="app-action-button users">
|
||||
<div class="app-action" id="builder-app-users-button">
|
||||
<ActionButton
|
||||
quiet
|
||||
icon="UserGroup"
|
||||
on:click={() => {
|
||||
store.update(state => {
|
||||
state.builderSidePanel = true
|
||||
return state
|
||||
})
|
||||
}}
|
||||
>
|
||||
Users
|
||||
</ActionButton>
|
||||
</div>
|
||||
<Popover
|
||||
bind:this={appActionPopover}
|
||||
align="right"
|
||||
disabled={!isPublished}
|
||||
anchor={appActionPopoverAnchor}
|
||||
offset={35}
|
||||
on:close={() => {
|
||||
appActionPopoverOpen = false
|
||||
}}
|
||||
on:open={() => {
|
||||
appActionPopoverOpen = true
|
||||
}}
|
||||
>
|
||||
<div class="app-action-popover-content">
|
||||
<Layout noPadding gap="M">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<Body size="M">
|
||||
<span
|
||||
class="app-link"
|
||||
on:click={() => {
|
||||
if (isPublished) {
|
||||
viewApp()
|
||||
} else {
|
||||
appActionPopover.hide()
|
||||
updateAppModal.show()
|
||||
}
|
||||
}}
|
||||
>
|
||||
{$store.url}
|
||||
{#if isPublished}
|
||||
<Icon size="S" name="LinkOut" />
|
||||
{:else}
|
||||
<Icon size="S" name="Edit" />
|
||||
{/if}
|
||||
</span>
|
||||
</Body>
|
||||
</div>
|
||||
</TourWrap>
|
||||
|
||||
<Body size="S">
|
||||
<span class="publish-popover-status">
|
||||
{#if isPublished}
|
||||
<span class="status-text">
|
||||
{updateDeploymentString(deployments)}
|
||||
</span>
|
||||
<span class="unpublish-link">
|
||||
<Link quiet on:click={unpublishApp}>Unpublish</Link>
|
||||
</span>
|
||||
<span class="revert-link">
|
||||
<Link quiet secondary on:click={revertApp}>Revert</Link>
|
||||
</span>
|
||||
{:else}
|
||||
<span class="status-text unpublished">Not published</span>
|
||||
{/if}
|
||||
</span>
|
||||
</Body>
|
||||
<div class="action-buttons">
|
||||
{#if $store.hasLock}
|
||||
{#if isPublished}
|
||||
<ActionButton
|
||||
quiet
|
||||
icon="Code"
|
||||
on:click={() => {
|
||||
$goto("./settings/embed")
|
||||
appActionPopover.hide()
|
||||
}}
|
||||
>
|
||||
Embed
|
||||
</ActionButton>
|
||||
{/if}
|
||||
<Button
|
||||
cta
|
||||
on:click={publishApp}
|
||||
id={"builder-app-publish-button"}
|
||||
disabled={!canPublish}
|
||||
>
|
||||
Publish
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</Layout>
|
||||
</div>
|
||||
</Popover>
|
||||
<div class="app-action-button preview">
|
||||
<div class="app-action">
|
||||
<ActionButton quiet icon="PlayCircle" on:click={previewApp}>
|
||||
Preview
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modals -->
|
||||
<ConfirmDialog
|
||||
bind:this={unpublishModal}
|
||||
title="Confirm unpublish"
|
||||
okText="Unpublish app"
|
||||
onOk={confirmUnpublishApp}
|
||||
>
|
||||
Are you sure you want to unpublish the app <b>{selectedApp?.name}</b>?
|
||||
</ConfirmDialog>
|
||||
|
||||
<Modal bind:this={updateAppModal} padding={false} width="600px">
|
||||
<UpdateAppModal
|
||||
app={{
|
||||
name: $store.name,
|
||||
url: $store.url,
|
||||
icon: $store.icon,
|
||||
appId: $store.appId,
|
||||
<div
|
||||
class="app-action-button publish app-action-popover"
|
||||
on:click={() => {
|
||||
if (!appActionPopoverOpen) {
|
||||
appActionPopover.show()
|
||||
} else {
|
||||
appActionPopover.hide()
|
||||
}
|
||||
}}
|
||||
onUpdateComplete={async () => {
|
||||
await initialiseApp()
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
>
|
||||
<div bind:this={appActionPopoverAnchor}>
|
||||
<div class="app-action">
|
||||
<Icon name={isPublished ? "GlobeCheck" : "GlobeStrike"} />
|
||||
<TourWrap tourStepKey={TOUR_STEP_KEYS.BUILDER_APP_PUBLISH}>
|
||||
<span class="publish-open" id="builder-app-publish-button">
|
||||
Publish
|
||||
<Icon
|
||||
name={appActionPopoverOpen ? "ChevronUp" : "ChevronDown"}
|
||||
size="M"
|
||||
/>
|
||||
</span>
|
||||
</TourWrap>
|
||||
</div>
|
||||
</div>
|
||||
<Popover
|
||||
bind:this={appActionPopover}
|
||||
align="right"
|
||||
disabled={!isPublished}
|
||||
anchor={appActionPopoverAnchor}
|
||||
offset={35}
|
||||
on:close={() => {
|
||||
appActionPopoverOpen = false
|
||||
}}
|
||||
on:open={() => {
|
||||
appActionPopoverOpen = true
|
||||
}}
|
||||
>
|
||||
<div class="app-action-popover-content">
|
||||
<Layout noPadding gap="M">
|
||||
<Body size="M">
|
||||
<span
|
||||
class="app-link"
|
||||
on:click={() => {
|
||||
if (isPublished) {
|
||||
viewApp()
|
||||
} else {
|
||||
appActionPopover.hide()
|
||||
updateAppModal.show()
|
||||
}
|
||||
}}
|
||||
>
|
||||
{$store.url}
|
||||
{#if isPublished}
|
||||
<Icon size="S" name="LinkOut" />
|
||||
{:else}
|
||||
<Icon size="S" name="Edit" />
|
||||
{/if}
|
||||
</span>
|
||||
</Body>
|
||||
|
||||
<RevertModal bind:this={revertModal} />
|
||||
<VersionModal hideIcon bind:this={versionModal} />
|
||||
{:else}
|
||||
<div class="app-action-button preview-locked">
|
||||
<div class="app-action">
|
||||
<ActionButton quiet icon="PlayCircle" on:click={previewApp}>
|
||||
Preview
|
||||
</ActionButton>
|
||||
<Body size="S">
|
||||
<span class="publish-popover-status">
|
||||
{#if isPublished}
|
||||
<span class="status-text">
|
||||
{lastDeployed}
|
||||
</span>
|
||||
<span class="unpublish-link">
|
||||
<Link quiet on:click={unpublishApp}>Unpublish</Link>
|
||||
</span>
|
||||
<span class="revert-link">
|
||||
<Link
|
||||
disabled={!$isOnlyUser}
|
||||
quiet
|
||||
secondary
|
||||
on:click={revertApp}
|
||||
tooltip="Unavailable - another user is editing this app"
|
||||
>
|
||||
Revert
|
||||
</Link>
|
||||
</span>
|
||||
{:else}
|
||||
<span class="status-text unpublished">Not published</span>
|
||||
{/if}
|
||||
</span>
|
||||
</Body>
|
||||
<div class="action-buttons">
|
||||
{#if isPublished}
|
||||
<ActionButton
|
||||
quiet
|
||||
icon="Code"
|
||||
on:click={() => {
|
||||
$goto("./settings/embed")
|
||||
appActionPopover.hide()
|
||||
}}
|
||||
>
|
||||
Embed
|
||||
</ActionButton>
|
||||
{/if}
|
||||
<Button
|
||||
cta
|
||||
on:click={publishApp}
|
||||
id={"builder-app-publish-button"}
|
||||
disabled={!canPublish}
|
||||
>
|
||||
Publish
|
||||
</Button>
|
||||
</div>
|
||||
</Layout>
|
||||
</div>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Modals -->
|
||||
<ConfirmDialog
|
||||
bind:this={unpublishModal}
|
||||
title="Confirm unpublish"
|
||||
okText="Unpublish app"
|
||||
onOk={confirmUnpublishApp}
|
||||
>
|
||||
Are you sure you want to unpublish the app <b>{selectedApp?.name}</b>?
|
||||
</ConfirmDialog>
|
||||
|
||||
<Modal bind:this={updateAppModal} padding={false} width="600px">
|
||||
<UpdateAppModal
|
||||
app={{
|
||||
name: $store.name,
|
||||
url: $store.url,
|
||||
icon: $store.icon,
|
||||
appId: $store.appId,
|
||||
}}
|
||||
onUpdateComplete={async () => {
|
||||
await initialiseApp()
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<RevertModal bind:this={revertModal} />
|
||||
<VersionModal hideIcon bind:this={versionModal} />
|
||||
|
||||
<style>
|
||||
.app-action-popover-content {
|
||||
|
@ -450,10 +403,6 @@
|
|||
gap: var(--spectrum-actionbutton-icon-gap);
|
||||
}
|
||||
|
||||
.app-action-button.preview-locked {
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.app-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
@ -1,118 +0,0 @@
|
|||
<script>
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
notifications,
|
||||
ModalContent,
|
||||
Layout,
|
||||
ProgressCircle,
|
||||
CopyInput,
|
||||
} from "@budibase/bbui"
|
||||
import { API } from "api"
|
||||
import analytics, { Events, EventSource } from "analytics"
|
||||
import { store } from "builderStore"
|
||||
import TourWrap from "../portal/onboarding/TourWrap.svelte"
|
||||
import { TOUR_STEP_KEYS } from "../portal/onboarding/tours.js"
|
||||
|
||||
let publishModal
|
||||
let asyncModal
|
||||
let publishCompleteModal
|
||||
|
||||
let published
|
||||
|
||||
$: publishedUrl = published ? `${window.origin}/app${published.appUrl}` : ""
|
||||
|
||||
export let onOk
|
||||
|
||||
async function publishApp() {
|
||||
try {
|
||||
//In Progress
|
||||
asyncModal.show()
|
||||
publishModal.hide()
|
||||
|
||||
published = await API.publishAppChanges($store.appId)
|
||||
|
||||
if (typeof onOk === "function") {
|
||||
await onOk()
|
||||
}
|
||||
|
||||
//Request completed
|
||||
asyncModal.hide()
|
||||
publishCompleteModal.show()
|
||||
} catch (error) {
|
||||
analytics.captureException(error)
|
||||
notifications.error("Error publishing app")
|
||||
}
|
||||
}
|
||||
|
||||
const viewApp = () => {
|
||||
if (published) {
|
||||
analytics.captureEvent(Events.APP_VIEW_PUBLISHED, {
|
||||
appId: $store.appId,
|
||||
eventSource: EventSource.PORTAL,
|
||||
})
|
||||
window.open(publishedUrl, "_blank")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<TourWrap tourStepKey={TOUR_STEP_KEYS.BUILDER_APP_PUBLISH}>
|
||||
<Button cta on:click={publishModal.show} id={"builder-app-publish-button"}>
|
||||
Publish
|
||||
</Button>
|
||||
</TourWrap>
|
||||
<Modal bind:this={publishModal}>
|
||||
<ModalContent
|
||||
title="Publish to production"
|
||||
confirmText="Publish"
|
||||
onConfirm={publishApp}
|
||||
>
|
||||
The changes you have made will be published to the production version of the
|
||||
application.
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
<!-- Publish in progress -->
|
||||
<Modal bind:this={asyncModal}>
|
||||
<ModalContent
|
||||
showCancelButton={false}
|
||||
showConfirmButton={false}
|
||||
showCloseIcon={false}
|
||||
>
|
||||
<Layout justifyItems="center">
|
||||
<ProgressCircle size="XL" />
|
||||
</Layout>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
<!-- Publish complete -->
|
||||
<Modal bind:this={publishCompleteModal}>
|
||||
<ModalContent confirmText="Done" cancelText="View App" onCancel={viewApp}>
|
||||
<div slot="header" class="app-published-header">
|
||||
<svg
|
||||
width="26px"
|
||||
height="26px"
|
||||
class="spectrum-Icon success-icon"
|
||||
focusable="false"
|
||||
>
|
||||
<use xlink:href="#spectrum-icon-18-GlobeCheck" />
|
||||
</svg>
|
||||
<span class="app-published-header-text">App Published!</span>
|
||||
</div>
|
||||
<CopyInput value={publishedUrl} label="You can view your app at:" />
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
.app-published-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
.success-icon {
|
||||
color: var(--spectrum-global-color-green-600);
|
||||
}
|
||||
.app-published-header .app-published-header-text {
|
||||
padding-left: var(--spacing-l);
|
||||
}
|
||||
</style>
|
|
@ -1,236 +0,0 @@
|
|||
<script>
|
||||
import { onMount, onDestroy } from "svelte"
|
||||
import Spinner from "components/common/Spinner.svelte"
|
||||
import { slide } from "svelte/transition"
|
||||
import { Heading, Button, Modal, ModalContent } from "@budibase/bbui"
|
||||
import { API } from "api"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import CreateWebhookDeploymentModal from "./CreateWebhookDeploymentModal.svelte"
|
||||
import { store } from "builderStore"
|
||||
import {
|
||||
checkIncomingDeploymentStatus,
|
||||
DeploymentStatus,
|
||||
} from "components/deploy/utils"
|
||||
|
||||
const DATE_OPTIONS = {
|
||||
fullDate: {
|
||||
weekday: "long",
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
},
|
||||
timeOnly: {
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
hourCycle: "h12",
|
||||
},
|
||||
}
|
||||
const POLL_INTERVAL = 5000
|
||||
|
||||
export let appId
|
||||
|
||||
let modal
|
||||
let errorReasonModal
|
||||
let errorReason
|
||||
let poll
|
||||
let deployments = []
|
||||
let urlComponent = $store.url || `/${appId}`
|
||||
let deploymentUrl = `${urlComponent}`
|
||||
|
||||
const formatDate = (date, format) =>
|
||||
Intl.DateTimeFormat("en-GB", DATE_OPTIONS[format]).format(date)
|
||||
|
||||
async function fetchDeployments() {
|
||||
try {
|
||||
const newDeployments = await API.getAppDeployments()
|
||||
if (deployments.length > 0) {
|
||||
const pendingDeployments = checkIncomingDeploymentStatus(
|
||||
deployments,
|
||||
newDeployments
|
||||
)
|
||||
if (pendingDeployments.length) {
|
||||
showErrorReasonModal(pendingDeployments[0].err)
|
||||
}
|
||||
}
|
||||
deployments = newDeployments
|
||||
} catch (err) {
|
||||
clearInterval(poll)
|
||||
notifications.error("Error fetching deployment overview")
|
||||
}
|
||||
}
|
||||
|
||||
function showErrorReasonModal(err) {
|
||||
if (!err) return
|
||||
errorReason = err
|
||||
errorReasonModal.show()
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
fetchDeployments()
|
||||
poll = setInterval(fetchDeployments, POLL_INTERVAL)
|
||||
})
|
||||
|
||||
onDestroy(() => clearInterval(poll))
|
||||
</script>
|
||||
|
||||
{#if deployments.length > 0}
|
||||
<section class="deployment-history" in:slide>
|
||||
<header>
|
||||
<Heading>Deployment History</Heading>
|
||||
<div class="deploy-div">
|
||||
{#if deployments.some(deployment => deployment.status === DeploymentStatus.SUCCESS)}
|
||||
<a target="_blank" href={deploymentUrl}> View Your Deployed App → </a>
|
||||
<Button primary on:click={() => modal.show()}>View webhooks</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</header>
|
||||
<div class="deployment-list">
|
||||
{#each deployments as deployment}
|
||||
<article class="deployment">
|
||||
<div class="deployment-info">
|
||||
<span class="deploy-date">
|
||||
{formatDate(deployment.updatedAt, "fullDate")}
|
||||
</span>
|
||||
<span class="deploy-time">
|
||||
{formatDate(deployment.updatedAt, "timeOnly")}
|
||||
</span>
|
||||
</div>
|
||||
<div class="deployment-right">
|
||||
{#if deployment.status.toLowerCase() === "pending"}
|
||||
<Spinner size="10" />
|
||||
{/if}
|
||||
<div
|
||||
on:click={() => showErrorReasonModal(deployment.err)}
|
||||
class={`deployment-status ${deployment.status}`}
|
||||
>
|
||||
<span>
|
||||
{deployment.status}
|
||||
{#if deployment.status === DeploymentStatus.FAILURE}
|
||||
<i class="ri-information-line" />
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
<Modal bind:this={modal} width="30%">
|
||||
<CreateWebhookDeploymentModal />
|
||||
</Modal>
|
||||
<Modal bind:this={errorReasonModal} width="30%">
|
||||
<ModalContent
|
||||
title="Deployment Error"
|
||||
confirmText="OK"
|
||||
showCancelButton={false}
|
||||
>
|
||||
{errorReason}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
section {
|
||||
padding: var(--spacing-xl) 0;
|
||||
}
|
||||
|
||||
.deployment-list {
|
||||
height: 40vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
header {
|
||||
padding-left: var(--spacing-l);
|
||||
padding-bottom: var(--spacing-xl);
|
||||
padding-right: var(--spacing-l);
|
||||
border-bottom: var(--border-light);
|
||||
}
|
||||
|
||||
.deploy-div {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.deployment-history {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
.deployment {
|
||||
padding: var(--spacing-l);
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: var(--border-light);
|
||||
}
|
||||
.deployment:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.deployment-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-right: var(--spacing-s);
|
||||
}
|
||||
|
||||
.deploy-date {
|
||||
font-size: var(--font-size-m);
|
||||
}
|
||||
|
||||
.deploy-time {
|
||||
color: var(--grey-7);
|
||||
font-weight: 600;
|
||||
font-size: var(--font-size-s);
|
||||
}
|
||||
|
||||
.deployment-right {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.deployment-status {
|
||||
font-size: var(--font-size-s);
|
||||
padding: var(--spacing-s);
|
||||
border-radius: var(--border-radius-s);
|
||||
font-weight: 600;
|
||||
text-transform: lowercase;
|
||||
width: 80px;
|
||||
text-align: center;
|
||||
}
|
||||
.deployment-status:first-letter {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--blue);
|
||||
font-weight: 600;
|
||||
font-size: var(--font-size-s);
|
||||
}
|
||||
|
||||
.SUCCESS {
|
||||
color: var(--green);
|
||||
background: var(--green-light);
|
||||
}
|
||||
|
||||
.PENDING {
|
||||
color: var(--yellow);
|
||||
background: var(--yellow-light);
|
||||
}
|
||||
|
||||
.FAILURE {
|
||||
color: var(--red);
|
||||
background: var(--red-light);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
i {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
</style>
|
|
@ -1,25 +0,0 @@
|
|||
export const DeploymentStatus = {
|
||||
SUCCESS: "SUCCESS",
|
||||
PENDING: "PENDING",
|
||||
FAILURE: "FAILURE",
|
||||
}
|
||||
|
||||
// Required to check any updated deployment statuses between polls
|
||||
export function checkIncomingDeploymentStatus(current, incoming) {
|
||||
return incoming.reduce((acc, incomingDeployment) => {
|
||||
if (incomingDeployment.status === DeploymentStatus.FAILURE) {
|
||||
const currentDeployment = current.find(
|
||||
deployment => deployment._id === incomingDeployment._id
|
||||
)
|
||||
|
||||
//We have just been notified of an ongoing deployments failure
|
||||
if (
|
||||
!currentDeployment ||
|
||||
currentDeployment.status === DeploymentStatus.PENDING
|
||||
) {
|
||||
acc.push(incomingDeployment)
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
}
|
|
@ -42,7 +42,6 @@
|
|||
}
|
||||
})
|
||||
|
||||
$: hasAutomations = automations && automations.length > 0
|
||||
$: selectedAutomation = automations?.find(
|
||||
a => a._id === parameters?.automationId
|
||||
)
|
||||
|
@ -145,12 +144,6 @@
|
|||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.params {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
.synchronous-info {
|
||||
display: flex;
|
||||
gap: var(--spacing-s);
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
getSchemaForDatasource,
|
||||
} from "builderStore/dataBinding"
|
||||
import { currentAsset } from "builderStore"
|
||||
import { getFields } from "helpers/searchFields"
|
||||
|
||||
export let componentInstance
|
||||
export let value = []
|
||||
|
@ -31,9 +30,6 @@
|
|||
$: options = Object.keys(schema || {})
|
||||
$: sanitisedValue = getValidColumns(value, options)
|
||||
$: updateBoundValue(sanitisedValue)
|
||||
$: enrichedSchemaFields = getFields(Object.values(schema || {}), {
|
||||
allowLinks: true,
|
||||
})
|
||||
|
||||
const getSchema = (asset, datasource) => {
|
||||
const schema = getSchemaForDatasource(asset, datasource).schema
|
||||
|
|
|
@ -192,7 +192,7 @@
|
|||
<Label>Filters</Label>
|
||||
</div>
|
||||
<div class="fields">
|
||||
{#each rawFilters as filter, idx}
|
||||
{#each rawFilters as filter}
|
||||
<Select
|
||||
bind:value={filter.field}
|
||||
options={fieldOptions}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
<i class={icon} />
|
||||
{:else}
|
||||
<span>
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html text}
|
||||
</span>
|
||||
{/if}
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
$: nullishValue = value == null || value === ""
|
||||
$: allBindings = getAllBindings(bindings, componentBindings, nested)
|
||||
$: safeValue = getSafeValue(value, defaultValue, allBindings)
|
||||
$: tempValue = safeValue
|
||||
$: replaceBindings = val => readableToRuntimeBinding(allBindings, val)
|
||||
|
||||
const getAllBindings = (bindings, componentBindings, nested) => {
|
||||
|
@ -104,6 +103,7 @@
|
|||
/>
|
||||
</div>
|
||||
{#if info}
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
<div class="text">{@html info}</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -1,32 +1,61 @@
|
|||
<script>
|
||||
import { Tooltip } from "@budibase/bbui"
|
||||
|
||||
export let text
|
||||
export let url
|
||||
export let active = false
|
||||
export let disabled = false
|
||||
export let tooltip = null
|
||||
</script>
|
||||
|
||||
{#if url}
|
||||
<a on:click href={url} class:active>
|
||||
{text || ""}
|
||||
</a>
|
||||
{:else}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<span on:click class:active>
|
||||
{text || ""}
|
||||
</span>
|
||||
{/if}
|
||||
<div class="side-nav-item">
|
||||
{#if url}
|
||||
<a class="text" on:click href={url} class:active class:disabled>
|
||||
{text || ""}
|
||||
</a>
|
||||
{:else}
|
||||
<div class="text" on:click class:active class:disabled>
|
||||
{text || ""}
|
||||
</div>
|
||||
{/if}
|
||||
{#if tooltip}
|
||||
<div class="tooltip">
|
||||
<Tooltip textWrapping direction="right" text={tooltip} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
a,
|
||||
span {
|
||||
.side-nav-item {
|
||||
position: relative;
|
||||
}
|
||||
.text {
|
||||
display: block;
|
||||
padding: var(--spacing-s) var(--spacing-m);
|
||||
color: var(--spectrum-global-color-gray-900);
|
||||
border-radius: 4px;
|
||||
transition: background 130ms ease-out;
|
||||
}
|
||||
.active,
|
||||
span:hover,
|
||||
a:hover {
|
||||
.text:hover {
|
||||
background-color: var(--spectrum-global-color-gray-200);
|
||||
cursor: pointer;
|
||||
}
|
||||
.disabled {
|
||||
pointer-events: none;
|
||||
color: var(--spectrum-global-color-gray-500) !important;
|
||||
}
|
||||
.tooltip {
|
||||
position: absolute;
|
||||
transform: translateY(-50%);
|
||||
left: 100%;
|
||||
top: 50%;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 130ms ease-out;
|
||||
z-index: 100;
|
||||
}
|
||||
.side-nav-item:hover .tooltip {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
<script>
|
||||
import { ModalContent, Body, notifications, CopyInput } from "@budibase/bbui"
|
||||
import {
|
||||
ModalContent,
|
||||
keepOpen,
|
||||
Body,
|
||||
notifications,
|
||||
CopyInput,
|
||||
} from "@budibase/bbui"
|
||||
import { auth } from "stores/portal"
|
||||
import { onMount } from "svelte"
|
||||
|
||||
|
@ -12,8 +18,8 @@
|
|||
} catch (err) {
|
||||
notifications.error("Unable to generate new API key")
|
||||
}
|
||||
// need to return false to keep modal open
|
||||
return false
|
||||
|
||||
return keepOpen
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
<script>
|
||||
import { writable, get as svelteGet } from "svelte/store"
|
||||
import { notifications, Input, ModalContent, Dropzone } from "@budibase/bbui"
|
||||
import {
|
||||
notifications,
|
||||
keepOpen,
|
||||
Input,
|
||||
ModalContent,
|
||||
Dropzone,
|
||||
} from "@budibase/bbui"
|
||||
import { store, automationStore } from "builderStore"
|
||||
import { API } from "api"
|
||||
import { apps, admin, auth } from "stores/portal"
|
||||
|
@ -167,7 +173,7 @@
|
|||
onConfirm: async () => {
|
||||
if (encryptedFile) {
|
||||
currentStep = Step.SET_PASSWORD
|
||||
return false
|
||||
return keepOpen
|
||||
} else {
|
||||
try {
|
||||
await createNewApp()
|
||||
|
@ -190,7 +196,7 @@
|
|||
message += `: ${lowercase(e.message)}`
|
||||
}
|
||||
notifications.error(message)
|
||||
return false
|
||||
return keepOpen
|
||||
}
|
||||
},
|
||||
isValid: $encryptionValidation.valid,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import {
|
||||
ModalContent,
|
||||
keepOpen,
|
||||
Toggle,
|
||||
Body,
|
||||
InlineAlert,
|
||||
|
@ -32,7 +33,7 @@
|
|||
exportApp()
|
||||
} else {
|
||||
currentStep = Step.SET_PASSWORD
|
||||
return false
|
||||
return keepOpen
|
||||
}
|
||||
},
|
||||
isValid: true,
|
||||
|
@ -43,7 +44,7 @@
|
|||
onConfirm: async () => {
|
||||
await validation.check({ password })
|
||||
if (!$validation.valid) {
|
||||
return false
|
||||
return keepOpen
|
||||
}
|
||||
exportApp(password)
|
||||
},
|
||||
|
|
|
@ -114,26 +114,24 @@ export const syncURLToState = options => {
|
|||
|
||||
// Updates the URL with new state values
|
||||
const mapStateToUrl = state => {
|
||||
let needsUpdate = false
|
||||
const urlValue = cachedParams?.[urlParam]
|
||||
const stateValue = state?.[stateKey]
|
||||
if (stateValue !== urlValue) {
|
||||
needsUpdate = true
|
||||
log(`url.${urlParam} (${urlValue}) <= state.${stateKey} (${stateValue})`)
|
||||
if (validate && fallbackUrl) {
|
||||
if (!validate(stateValue)) {
|
||||
log("Invalid state param!", stateValue)
|
||||
redirectUrl(fallbackUrl)
|
||||
return
|
||||
}
|
||||
|
||||
// As the store updated, validate that the current state value is valid
|
||||
if (validate && fallbackUrl) {
|
||||
if (!validate(stateValue)) {
|
||||
log("Invalid state param!", stateValue)
|
||||
redirectUrl(fallbackUrl)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid updating the URL if not necessary to prevent a wasted render
|
||||
// cycle
|
||||
if (!needsUpdate) {
|
||||
if (stateValue === urlValue) {
|
||||
return
|
||||
}
|
||||
log(`url.${urlParam} (${urlValue}) <= state.${stateKey} (${stateValue})`)
|
||||
|
||||
// Navigate to the new URL
|
||||
if (!get(isChangingPage)) {
|
||||
|
|
|
@ -14,17 +14,16 @@
|
|||
import { groups, licensing, apps, users, auth, admin } from "stores/portal"
|
||||
import { fetchData } from "@budibase/frontend-core"
|
||||
import { API } from "api"
|
||||
import { onMount } from "svelte"
|
||||
import GroupIcon from "../../../portal/users/groups/_components/GroupIcon.svelte"
|
||||
import RoleSelect from "components/common/RoleSelect.svelte"
|
||||
import UpgradeModal from "components/common/users/UpgradeModal.svelte"
|
||||
import { Constants, Utils } from "@budibase/frontend-core"
|
||||
import { emailValidator } from "helpers/validation"
|
||||
import { roles } from "stores/backend"
|
||||
import { fly } from "svelte/transition"
|
||||
|
||||
let query = null
|
||||
let loaded = false
|
||||
let rendered = false
|
||||
let inviting = false
|
||||
let searchFocus = false
|
||||
|
||||
|
@ -383,10 +382,6 @@
|
|||
|
||||
$: initSidePanel($store.builderSidePanel)
|
||||
|
||||
onMount(() => {
|
||||
rendered = true
|
||||
})
|
||||
|
||||
function handleKeyDown(evt) {
|
||||
if (evt.key === "Enter" && queryIsEmail && !inviting) {
|
||||
onInviteUser()
|
||||
|
@ -418,16 +413,14 @@
|
|||
<svelte:window on:keydown={handleKeyDown} />
|
||||
|
||||
<div
|
||||
transition:fly={{ x: 400, duration: 260 }}
|
||||
id="builder-side-panel-container"
|
||||
class:open={$store.builderSidePanel}
|
||||
use:clickOutside={$store.builderSidePanel
|
||||
? () => {
|
||||
store.update(state => {
|
||||
state.builderSidePanel = false
|
||||
return state
|
||||
})
|
||||
}
|
||||
: () => {}}
|
||||
use:clickOutside={() => {
|
||||
store.update(state => {
|
||||
state.builderSidePanel = false
|
||||
return state
|
||||
})
|
||||
}}
|
||||
>
|
||||
<div class="builder-side-panel-header">
|
||||
<Heading size="S">Users</Heading>
|
||||
|
@ -737,12 +730,11 @@
|
|||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
transition: transform 130ms ease-out;
|
||||
position: absolute;
|
||||
width: 400px;
|
||||
right: 0;
|
||||
transform: translateX(100%);
|
||||
height: 100%;
|
||||
box-shadow: 0 0 40px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.builder-side-panel-header,
|
||||
|
@ -792,11 +784,6 @@
|
|||
font-style: normal;
|
||||
}
|
||||
|
||||
#builder-side-panel-container.open {
|
||||
transform: translateX(0);
|
||||
box-shadow: 0 0 40px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.builder-side-panel-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
<script>
|
||||
import { store, automationStore, userStore } from "builderStore"
|
||||
import {
|
||||
store,
|
||||
automationStore,
|
||||
userStore,
|
||||
deploymentStore,
|
||||
} from "builderStore"
|
||||
import { roles, flags } from "stores/backend"
|
||||
import { auth } from "stores/portal"
|
||||
import { auth, apps } from "stores/portal"
|
||||
import { TENANT_FEATURE_FLAGS, isEnabled } from "helpers/featureFlags"
|
||||
import {
|
||||
Icon,
|
||||
|
@ -44,6 +49,8 @@
|
|||
await automationStore.actions.fetch()
|
||||
await roles.fetch()
|
||||
await flags.fetch()
|
||||
await apps.load()
|
||||
await deploymentStore.actions.load()
|
||||
loaded = true
|
||||
return pkg
|
||||
} catch (error) {
|
||||
|
@ -69,18 +76,13 @@
|
|||
|
||||
// Event handler for the command palette
|
||||
const handleKeyDown = e => {
|
||||
if (e.key === "k" && (e.ctrlKey || e.metaKey) && $store.hasLock) {
|
||||
if (e.key === "k" && (e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault()
|
||||
commandPaletteModal.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
const initTour = async () => {
|
||||
// Skip tour if we don't have the lock
|
||||
if (!$store.hasLock) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if onboarding is enabled.
|
||||
if (isEnabled(TENANT_FEATURE_FLAGS.ONBOARDING_TOUR)) {
|
||||
if (!$auth.user?.onboardedAt) {
|
||||
|
@ -140,7 +142,7 @@
|
|||
{/if}
|
||||
|
||||
<div class="root" class:blur={$store.showPreview}>
|
||||
<div class="top-nav" class:has-lock={$store.hasLock}>
|
||||
<div class="top-nav">
|
||||
{#if $store.initialised}
|
||||
<div class="topleftnav">
|
||||
<span class="back-to-apps">
|
||||
|
@ -151,37 +153,25 @@
|
|||
on:click={() => $goto("../../portal/apps")}
|
||||
/>
|
||||
</span>
|
||||
{#if $store.hasLock}
|
||||
<Tabs {selected} size="M">
|
||||
{#each $layout.children as { path, title }}
|
||||
<TourWrap tourStepKey={`builder-${title}-section`}>
|
||||
<Tab
|
||||
quiet
|
||||
selected={$isActive(path)}
|
||||
on:click={topItemNavigate(path)}
|
||||
title={capitalise(title)}
|
||||
id={`builder-${title}-tab`}
|
||||
/>
|
||||
</TourWrap>
|
||||
{/each}
|
||||
</Tabs>
|
||||
{:else}
|
||||
<div class="secondary-editor">
|
||||
<Icon name="LockClosed" />
|
||||
<div
|
||||
class="secondary-editor-body"
|
||||
title="Another user is currently editing your screens and automations"
|
||||
>
|
||||
Another user is currently editing your screens and automations
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<Tabs {selected} size="M">
|
||||
{#each $layout.children as { path, title }}
|
||||
<TourWrap tourStepKey={`builder-${title}-section`}>
|
||||
<Tab
|
||||
quiet
|
||||
selected={$isActive(path)}
|
||||
on:click={topItemNavigate(path)}
|
||||
title={capitalise(title)}
|
||||
id={`builder-${title}-tab`}
|
||||
/>
|
||||
</TourWrap>
|
||||
{/each}
|
||||
</Tabs>
|
||||
</div>
|
||||
<div class="topcenternav">
|
||||
<Heading size="XS">{$store.name}</Heading>
|
||||
</div>
|
||||
<div class="toprightnav">
|
||||
<span class:nav-lock={!$store.hasLock}>
|
||||
<span>
|
||||
<UserAvatars users={$userStore} />
|
||||
</span>
|
||||
<AppActions {application} {loaded} />
|
||||
|
@ -248,10 +238,6 @@
|
|||
z-index: 2;
|
||||
}
|
||||
|
||||
.top-nav.has-lock {
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.topcenternav {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -290,23 +276,6 @@
|
|||
margin-right: var(--spacing-l);
|
||||
}
|
||||
|
||||
.secondary-editor {
|
||||
align-self: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
margin-left: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.secondary-editor-body {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
min-width: 0px;
|
||||
}
|
||||
|
||||
.body {
|
||||
flex: 1 1 auto;
|
||||
z-index: 1;
|
||||
|
|
|
@ -8,15 +8,6 @@
|
|||
import { onDestroy, onMount } from "svelte"
|
||||
import { syncURLToState } from "helpers/urlStateSync"
|
||||
import * as routify from "@roxi/routify"
|
||||
import { store } from "builderStore"
|
||||
import { redirect } from "@roxi/routify"
|
||||
|
||||
// Prevent access for other users than the lock holder
|
||||
$: {
|
||||
if (!$store.hasLock) {
|
||||
$redirect("../data")
|
||||
}
|
||||
}
|
||||
|
||||
// Keep URL and state in sync for selected screen ID
|
||||
const stopSyncing = syncURLToState({
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { Modal } from "@budibase/bbui"
|
||||
import { Modal, keepOpen } from "@budibase/bbui"
|
||||
import { goto } from "@roxi/routify"
|
||||
import { IntegrationTypes } from "constants/backend"
|
||||
import GoogleAuthPrompt from "./GoogleAuthPrompt.svelte"
|
||||
|
@ -75,8 +75,7 @@
|
|||
notifications.error(`Error creating datasource: ${e.message}`)
|
||||
}
|
||||
|
||||
// Prevent modal closing
|
||||
return false
|
||||
return keepOpen
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { Modal, notifications } from "@budibase/bbui"
|
||||
import { keepOpen, Modal, notifications } from "@budibase/bbui"
|
||||
import { integrationForDatasource } from "stores/selectors"
|
||||
import { datasources, integrations } from "stores/backend"
|
||||
import DatasourceConfigEditor from "components/backend/Datasources/ConfigEditor/index.svelte"
|
||||
|
@ -23,8 +23,8 @@
|
|||
)
|
||||
} catch (err) {
|
||||
notifications.error(err?.message ?? "Error saving datasource")
|
||||
// prevent the modal from closing
|
||||
return false
|
||||
|
||||
return keepOpen
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -49,7 +49,6 @@
|
|||
return datasource.config.spreadsheetId
|
||||
}
|
||||
}
|
||||
$: subtitle = getSubtitle(datasource)
|
||||
</script>
|
||||
|
||||
<div class="button" on:click>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { goto } from "@roxi/routify"
|
||||
import {
|
||||
keepOpen,
|
||||
ModalContent,
|
||||
notifications,
|
||||
Body,
|
||||
|
@ -70,10 +71,10 @@
|
|||
}
|
||||
|
||||
notifications.success("Imported successfully")
|
||||
return true
|
||||
} catch (error) {
|
||||
notifications.error("Error importing queries")
|
||||
return false
|
||||
|
||||
return keepOpen
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -4,6 +4,10 @@
|
|||
import { syncURLToState } from "helpers/urlStateSync"
|
||||
import * as routify from "@roxi/routify"
|
||||
import { onDestroy } from "svelte"
|
||||
import { store } from "builderStore"
|
||||
|
||||
$: datasourceId = $datasources.selectedDatasourceId
|
||||
$: store.actions.websocket.selectResource(datasourceId)
|
||||
|
||||
const stopSyncing = syncURLToState({
|
||||
urlParam: "datasourceId",
|
||||
|
|
|
@ -7,9 +7,11 @@
|
|||
import { onMount } from "svelte"
|
||||
import { BUDIBASE_INTERNAL_DB_ID } from "constants/backend"
|
||||
import { TableNames } from "constants"
|
||||
import { store } from "builderStore"
|
||||
|
||||
let modal
|
||||
|
||||
$: store.actions.websocket.selectResource(BUDIBASE_INTERNAL_DB_ID)
|
||||
$: internalTablesBySourceId = $tables.list.filter(
|
||||
table =>
|
||||
table.type !== "external" &&
|
||||
|
|
|
@ -6,8 +6,11 @@
|
|||
import { goto } from "@roxi/routify"
|
||||
import { DEFAULT_BB_DATASOURCE_ID } from "constants/backend"
|
||||
import { onMount } from "svelte"
|
||||
import { store } from "builderStore"
|
||||
|
||||
let modal
|
||||
|
||||
$: store.actions.websocket.selectResource(DEFAULT_BB_DATASOURCE_ID)
|
||||
$: internalTablesBySourceId = $tables.list.filter(
|
||||
table =>
|
||||
table.type !== "external" && table.sourceId === DEFAULT_BB_DATASOURCE_ID
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
import { syncURLToState } from "helpers/urlStateSync"
|
||||
import * as routify from "@roxi/routify"
|
||||
import { onDestroy } from "svelte"
|
||||
import { store } from "builderStore"
|
||||
|
||||
$: queryId = $queries.selectedQueryId
|
||||
$: store.actions.websocket.selectResource(queryId)
|
||||
|
||||
const stopSyncing = syncURLToState({
|
||||
urlParam: "queryId",
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
import { tables } from "stores/backend"
|
||||
import * as routify from "@roxi/routify"
|
||||
import { onDestroy } from "svelte"
|
||||
import { store } from "builderStore"
|
||||
|
||||
$: tableId = $tables.selectedTableId
|
||||
$: store.actions.websocket.selectResource(tableId)
|
||||
|
||||
const stopSyncing = syncURLToState({
|
||||
urlParam: "tableId",
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
import { syncURLToState } from "helpers/urlStateSync"
|
||||
import * as routify from "@roxi/routify"
|
||||
import { onDestroy } from "svelte"
|
||||
import { store } from "builderStore"
|
||||
|
||||
$: viewName = $views.selectedViewName
|
||||
$: store.actions.websocket.selectResource(viewName)
|
||||
|
||||
const stopSyncing = syncURLToState({
|
||||
urlParam: "viewName",
|
||||
|
|
|
@ -272,6 +272,7 @@
|
|||
{:else if error}
|
||||
<div class="center error">
|
||||
<Layout justifyItems="center" gap="S">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html ErrorSVG}
|
||||
<Heading size="L">App preview failed to load</Heading>
|
||||
<Body size="S">{error}</Body>
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
import { onDestroy } from "svelte"
|
||||
const { isActive, goto } = routify
|
||||
|
||||
$: screenId = $store.selectedScreenId
|
||||
$: store.actions.websocket.selectResource(screenId)
|
||||
|
||||
// Keep URL and state in sync for selected screen ID
|
||||
const stopSyncing = syncURLToState({
|
||||
urlParam: "screenId",
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import ComponentTree from "./ComponentTree.svelte"
|
||||
import { dndStore } from "./dndStore.js"
|
||||
import { goto } from "@roxi/routify"
|
||||
import { store, selectedScreen } from "builderStore"
|
||||
import { store, selectedScreen, userSelectedResourceMap } from "builderStore"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
import ScreenslotDropdownMenu from "./ScreenslotDropdownMenu.svelte"
|
||||
import DNDPositionIndicator from "./DNDPositionIndicator.svelte"
|
||||
|
@ -41,6 +41,7 @@
|
|||
$store.selectedComponentId = $selectedScreen?.props._id
|
||||
}}
|
||||
id={`component-${$selectedScreen?.props._id}`}
|
||||
selectedBy={$userSelectedResourceMap[$selectedScreen?.props._id]}
|
||||
>
|
||||
<ScreenslotDropdownMenu component={$selectedScreen?.props} />
|
||||
</NavItem>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { store } from "builderStore"
|
||||
import { store, userSelectedResourceMap } from "builderStore"
|
||||
import ComponentDropdownMenu from "./ComponentDropdownMenu.svelte"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
import { capitalise } from "helpers"
|
||||
|
@ -18,8 +18,6 @@
|
|||
|
||||
let closedNodes = {}
|
||||
|
||||
$: currentScreen = get(selectedScreen)
|
||||
|
||||
$: filteredComponents = components?.filter(component => {
|
||||
return (
|
||||
!$store.componentToPaste?.isCut ||
|
||||
|
@ -123,6 +121,7 @@
|
|||
selected={$store.selectedComponentId === component._id}
|
||||
{opened}
|
||||
highlighted={isChildOfSelectedComponent(component)}
|
||||
selectedBy={$userSelectedResourceMap[component._id]}
|
||||
>
|
||||
<ComponentDropdownMenu {component} />
|
||||
</NavItem>
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
import ComponentListPanel from "./_components/navigation/ComponentListPanel.svelte"
|
||||
import ComponentSettingsPanel from "./_components/settings/ComponentSettingsPanel.svelte"
|
||||
|
||||
$: componentId = $store.selectedComponentId
|
||||
$: store.actions.websocket.selectResource(componentId)
|
||||
|
||||
const cleanUrl = url => {
|
||||
// Strip trailing slashes
|
||||
if (url?.endsWith("/index")) {
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
$: role = $roles.find(role => role._id === roleId)
|
||||
$: tooltip =
|
||||
roleId === Roles.PUBLIC
|
||||
? "This screen is open to the public"
|
||||
: `Requires at least ${role?.name} access`
|
||||
? "Open to the public"
|
||||
: `Requires ${role?.name} access`
|
||||
</script>
|
||||
|
||||
<div
|
||||
|
@ -44,14 +44,14 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
width: 130px;
|
||||
width: 200px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.tooltip :global(.spectrum-Tooltip) {
|
||||
background: var(--color);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
max-width: 130px;
|
||||
max-width: 200px;
|
||||
}
|
||||
.tooltip :global(.spectrum-Tooltip-tip) {
|
||||
border-top-color: var(--color);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { Search, Layout, Select, Body, Button } from "@budibase/bbui"
|
||||
import Panel from "components/design/Panel.svelte"
|
||||
import { roles } from "stores/backend"
|
||||
import { store, sortedScreens } from "builderStore"
|
||||
import { store, sortedScreens, userSelectedResourceMap } from "builderStore"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
import ScreenDropdownMenu from "./ScreenDropdownMenu.svelte"
|
||||
import ScreenWizard from "./ScreenWizard.svelte"
|
||||
|
@ -60,6 +60,7 @@
|
|||
on:click={() => store.actions.screens.select(screen._id)}
|
||||
rightAlignIcon
|
||||
showTooltip
|
||||
selectedBy={$userSelectedResourceMap[screen._id]}
|
||||
>
|
||||
<ScreenDropdownMenu screenId={screen._id} />
|
||||
<RoleIndicator slot="right" roleId={screen.routing.roleId} />
|
||||
|
|
|
@ -1,14 +1,2 @@
|
|||
<script>
|
||||
import { store } from "builderStore"
|
||||
import { redirect } from "@roxi/routify"
|
||||
|
||||
// Prevent access for other users than the lock holder
|
||||
$: {
|
||||
if (!$store.hasLock) {
|
||||
$redirect("../data")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- routify:options index=2 -->
|
||||
<slot />
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import { Page, Layout } from "@budibase/bbui"
|
||||
import { url, isActive } from "@roxi/routify"
|
||||
import DeleteModal from "components/deploy/DeleteModal.svelte"
|
||||
import { isOnlyUser } from "builderStore"
|
||||
|
||||
let deleteModal
|
||||
</script>
|
||||
|
@ -49,6 +50,10 @@
|
|||
on:click={() => {
|
||||
deleteModal.show()
|
||||
}}
|
||||
disabled={!$isOnlyUser}
|
||||
tooltip={$isOnlyUser
|
||||
? null
|
||||
: "Unavailable - another user is editing this app"}
|
||||
/>
|
||||
</div>
|
||||
</SideNav>
|
||||
|
@ -61,7 +66,7 @@
|
|||
<DeleteModal bind:this={deleteModal} />
|
||||
|
||||
<style>
|
||||
.delete-action :global(span) {
|
||||
.delete-action :global(.text) {
|
||||
color: var(--spectrum-global-color-red-400);
|
||||
}
|
||||
.delete-action {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import CreateRestoreModal from "./CreateRestoreModal.svelte"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { isOnlyUser } from "builderStore"
|
||||
|
||||
export let row
|
||||
|
||||
|
@ -45,7 +46,16 @@
|
|||
</div>
|
||||
|
||||
{#if row.type !== "restore"}
|
||||
<MenuItem on:click={restoreDialog.show} icon="Revert">Restore</MenuItem>
|
||||
<MenuItem
|
||||
on:click={restoreDialog.show}
|
||||
icon="Revert"
|
||||
disabled={!$isOnlyUser}
|
||||
tooltip={$isOnlyUser
|
||||
? null
|
||||
: "Unavailable - another user is editing this app"}
|
||||
>
|
||||
Restore
|
||||
</MenuItem>
|
||||
<MenuItem on:click={deleteDialog.show} icon="Delete">Delete</MenuItem>
|
||||
<MenuItem on:click={downloadExport} icon="Download">Download</MenuItem>
|
||||
{/if}
|
||||
|
|
|
@ -63,9 +63,8 @@
|
|||
}}
|
||||
disabled={appDeployed}
|
||||
tooltip={appDeployed
|
||||
? "You must unpublish your app to make changes to these settings"
|
||||
? "You must unpublish your app to make changes"
|
||||
: null}
|
||||
icon={appDeployed ? "HelpOutline" : null}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { Layout, Heading, Body, Divider, Button } from "@budibase/bbui"
|
||||
import { store } from "builderStore"
|
||||
import { store, isOnlyUser } from "builderStore"
|
||||
import VersionModal from "components/deploy/VersionModal.svelte"
|
||||
|
||||
let versionModal
|
||||
|
@ -22,7 +22,16 @@
|
|||
Updates can contain new features, performance improvements and bug fixes.
|
||||
</Body>
|
||||
<div>
|
||||
<Button cta on:click={versionModal.show}>Update app</Button>
|
||||
<Button
|
||||
cta
|
||||
on:click={versionModal.show}
|
||||
disabled={!$isOnlyUser}
|
||||
tooltip={$isOnlyUser
|
||||
? null
|
||||
: "Unavailable - another user is editing this app"}
|
||||
>
|
||||
Update app
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<Body>
|
||||
|
@ -31,7 +40,15 @@
|
|||
You're running the latest!
|
||||
</Body>
|
||||
<div>
|
||||
<Button secondary on:click={versionModal.show}>Revert app</Button>
|
||||
<Button
|
||||
secondary
|
||||
on:click={versionModal.show}
|
||||
tooltip={$isOnlyUser
|
||||
? null
|
||||
: "Unavailable - another user is editing this app"}
|
||||
>
|
||||
Revert app
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</Layout>
|
||||
|
|
|
@ -144,7 +144,7 @@
|
|||
<Heading>Apps</Heading>
|
||||
<div class="group">
|
||||
<Layout gap="S" noPadding>
|
||||
{#each userApps as app, idx (app.appId)}
|
||||
{#each userApps as app (app.appId)}
|
||||
<a class="app" target="_blank" href={getUrl(app)}>
|
||||
<div class="preview" use:gradient={{ seed: app.name }} />
|
||||
<div class="app-info">
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
{#each $menu as { title, href, subPages }}
|
||||
{#each $menu as { title, subPages }}
|
||||
{#if subPages?.length}
|
||||
<div class="category">{title}</div>
|
||||
{#each subPages as { title, href }}
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
let template
|
||||
let creationModal = false
|
||||
let appLimitModal
|
||||
let creatingApp = false
|
||||
|
||||
const initiateAppCreation = () => {
|
||||
if ($licensing?.usageMetrics?.apps >= 100) {
|
||||
|
@ -19,13 +18,11 @@
|
|||
} else {
|
||||
template = null
|
||||
creationModal.show()
|
||||
creatingApp = true
|
||||
}
|
||||
}
|
||||
|
||||
const stopAppCreation = () => {
|
||||
template = null
|
||||
creatingApp = false
|
||||
}
|
||||
|
||||
const initiateAppImport = () => {
|
||||
|
@ -34,7 +31,6 @@
|
|||
} else {
|
||||
template = { fromFile: true }
|
||||
creationModal.show()
|
||||
creatingApp = true
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
let creationModal
|
||||
let appLimitModal
|
||||
let accountLockedModal
|
||||
let creatingApp = false
|
||||
let searchTerm = ""
|
||||
let creatingFromTemplate = false
|
||||
let automationErrors
|
||||
|
@ -123,14 +122,12 @@
|
|||
} else {
|
||||
template = null
|
||||
creationModal.show()
|
||||
creatingApp = true
|
||||
}
|
||||
}
|
||||
|
||||
const initiateAppImport = () => {
|
||||
template = { fromFile: true }
|
||||
creationModal.show()
|
||||
creatingApp = true
|
||||
}
|
||||
|
||||
const autoCreateApp = async () => {
|
||||
|
@ -173,7 +170,6 @@
|
|||
|
||||
const stopAppCreation = () => {
|
||||
template = null
|
||||
creatingApp = false
|
||||
}
|
||||
|
||||
function createAppFromTemplateUrl(templateKey) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { Body, ModalContent, Select } from "@budibase/bbui"
|
||||
import { keepOpen, Body, ModalContent, Select } from "@budibase/bbui"
|
||||
import { apps, groups } from "stores/portal"
|
||||
import { roles } from "stores/backend"
|
||||
import RoleSelect from "components/common/RoleSelect.svelte"
|
||||
|
@ -20,8 +20,8 @@
|
|||
if (!selectingRole) {
|
||||
selectingRole = true
|
||||
await roles.fetchByAppId(prodAppId)
|
||||
// return false to stop closing modal
|
||||
return false
|
||||
|
||||
return keepOpen
|
||||
} else {
|
||||
await groups.actions.addApp(group._id, prodAppId, selectedRoleId)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import {
|
||||
keepOpen,
|
||||
ColorPicker,
|
||||
Body,
|
||||
ModalContent,
|
||||
|
@ -17,7 +18,7 @@
|
|||
onConfirm={() => {
|
||||
if (!group.name?.trim()) {
|
||||
nameError = "Group name cannot be empty"
|
||||
return false
|
||||
return keepOpen
|
||||
}
|
||||
saveGroup(group)
|
||||
}}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import {
|
||||
keepOpen,
|
||||
Label,
|
||||
ActionButton,
|
||||
ModalContent,
|
||||
|
@ -73,7 +74,7 @@
|
|||
valid = validateInput(input, index) && valid
|
||||
})
|
||||
if (!valid) {
|
||||
return false
|
||||
return keepOpen
|
||||
}
|
||||
showOnboardingTypeModal({ users: userData, groups: userGroups })
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
export let deleteUsersResponse
|
||||
|
||||
let successCount
|
||||
let failureCount
|
||||
let title
|
||||
let unsuccessfulUsers
|
||||
let message
|
||||
|
@ -37,7 +36,6 @@
|
|||
|
||||
onMount(() => {
|
||||
successCount = deleteUsersResponse.successful.length
|
||||
failureCount = deleteUsersResponse.unsuccessful.length
|
||||
setTitle()
|
||||
setMessage()
|
||||
setUsers()
|
||||
|
|
|
@ -183,7 +183,7 @@
|
|||
const currentUserEmails = (await users.fetch())?.map(x => x.email) || []
|
||||
const newUsers = []
|
||||
|
||||
for (const user of userData?.users) {
|
||||
for (const user of userData?.users ?? []) {
|
||||
const { email } = user
|
||||
|
||||
if (
|
||||
|
|
|
@ -142,6 +142,7 @@
|
|||
{#if permissionError}
|
||||
<div class="error">
|
||||
<Layout justifyItems="center" gap="S">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html ErrorSVG}
|
||||
<Heading size="L">
|
||||
You don't have permission to use this app
|
||||
|
@ -154,6 +155,7 @@
|
|||
{:else if !$screenStore.activeLayout}
|
||||
<div class="error">
|
||||
<Layout justifyItems="center" gap="S">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html ErrorSVG}
|
||||
<Heading size="L">
|
||||
Something went wrong rendering your app
|
||||
|
@ -166,6 +168,7 @@
|
|||
{:else if embedNoScreens}
|
||||
<div class="error">
|
||||
<Layout justifyItems="center" gap="S">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html ErrorSVG}
|
||||
<Heading size="L">
|
||||
This Budibase app is not publicly accessible
|
||||
|
|
|
@ -203,7 +203,7 @@
|
|||
type: instance._component,
|
||||
errorState,
|
||||
parent: id,
|
||||
ancestors: [...$component?.ancestors, instance._component],
|
||||
ancestors: [...($component?.ancestors ?? []), instance._component],
|
||||
})
|
||||
|
||||
const initialise = (instance, force = false) => {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
{#if embed}
|
||||
<div class="embed" use:styleable={$component.styles}>
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html embed}
|
||||
</div>
|
||||
{:else if $builderStore.inBuilder}
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
>
|
||||
{#if $builderStore.inBuilder}
|
||||
<div class="underlay">
|
||||
{#each coords as coord}
|
||||
{#each coords as _}
|
||||
<div class="placeholder" />
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
@ -115,7 +115,7 @@
|
|||
</Body>
|
||||
{#if filters?.length}
|
||||
<div class="fields">
|
||||
{#each filters as filter, idx}
|
||||
{#each filters as filter}
|
||||
<Select
|
||||
bind:value={filter.field}
|
||||
options={fieldOptions}
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
<Tooltip
|
||||
direction={tooltipDirection}
|
||||
textWrapping
|
||||
text={user.email}
|
||||
text={helpers.getUserLabel(user)}
|
||||
size="S"
|
||||
/>
|
||||
</div>
|
||||
|
@ -46,13 +46,15 @@
|
|||
position: relative;
|
||||
}
|
||||
.tooltip {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 130ms ease-out;
|
||||
}
|
||||
.user-avatar:hover .tooltip {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -258,7 +258,7 @@
|
|||
class:wrap={editable || contentLines > 1}
|
||||
on:wheel={e => (focused ? e.stopPropagation() : null)}
|
||||
>
|
||||
{#each value || [] as relationship, idx}
|
||||
{#each value || [] as relationship}
|
||||
{#if relationship.primaryDisplay}
|
||||
<div class="badge">
|
||||
<span
|
||||
|
|
|
@ -2,16 +2,9 @@
|
|||
import { getContext } from "svelte"
|
||||
import { GutterWidth } from "../lib/constants"
|
||||
|
||||
const {
|
||||
columns,
|
||||
resize,
|
||||
renderedColumns,
|
||||
stickyColumn,
|
||||
isReordering,
|
||||
scrollLeft,
|
||||
} = getContext("grid")
|
||||
const { resize, renderedColumns, stickyColumn, isReordering, scrollLeft } =
|
||||
getContext("grid")
|
||||
|
||||
$: cutoff = $scrollLeft + GutterWidth + ($columns[0]?.width || 0)
|
||||
$: offset = GutterWidth + ($stickyColumn?.width || 0)
|
||||
$: activeColumn = $resize.column
|
||||
|
||||
|
|
|
@ -30,8 +30,9 @@ export const deriveStores = context => {
|
|||
([$users, $focusedCellId]) => {
|
||||
let map = {}
|
||||
$users.forEach(user => {
|
||||
if (user.focusedCellId && user.focusedCellId !== $focusedCellId) {
|
||||
map[user.focusedCellId] = user
|
||||
const cellId = user.gridMetadata?.focusedCellId
|
||||
if (cellId && cellId !== $focusedCellId) {
|
||||
map[cellId] = user
|
||||
}
|
||||
})
|
||||
return map
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 1a5207d91fb9e0835562c708dd9c421973026543
|
||||
Subproject commit 544c7e067de69832469cde673e59501480d6d98a
|
|
@ -53,6 +53,7 @@ import {
|
|||
} from "@budibase/types"
|
||||
import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts"
|
||||
import sdk from "../../sdk"
|
||||
import { builderSocket } from "../../websockets"
|
||||
|
||||
// utility function, need to do away with this
|
||||
async function getLayouts() {
|
||||
|
@ -439,6 +440,14 @@ export async function update(ctx: UserCtx) {
|
|||
await events.app.updated(app)
|
||||
ctx.status = 200
|
||||
ctx.body = app
|
||||
builderSocket?.emitAppMetadataUpdate(ctx, {
|
||||
theme: app.theme,
|
||||
customTheme: app.customTheme,
|
||||
navigation: app.navigation,
|
||||
name: app.name,
|
||||
url: app.url,
|
||||
icon: app.icon,
|
||||
})
|
||||
}
|
||||
|
||||
export async function updateClient(ctx: UserCtx) {
|
||||
|
@ -569,6 +578,7 @@ export async function unpublish(ctx: UserCtx) {
|
|||
await unpublishApp(ctx)
|
||||
await postDestroyApp(ctx)
|
||||
ctx.status = 204
|
||||
builderSocket?.emitAppUnpublish(ctx)
|
||||
}
|
||||
|
||||
export async function sync(ctx: UserCtx) {
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
import { backups } from "@budibase/pro"
|
||||
import { AppBackupTrigger } from "@budibase/types"
|
||||
import sdk from "../../../sdk"
|
||||
import { builderSocket } from "../../../websockets"
|
||||
|
||||
// the max time we can wait for an invalidation to complete before considering it failed
|
||||
const MAX_PENDING_TIME_MS = 30 * 60000
|
||||
|
@ -201,4 +202,5 @@ export const publishApp = async function (ctx: any) {
|
|||
|
||||
await events.app.published(app)
|
||||
ctx.body = deployment
|
||||
builderSocket?.emitAppPublish(ctx)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
} from "@budibase/backend-core"
|
||||
import { updateAppPackage } from "./application"
|
||||
import { Plugin, ScreenProps, BBContext } from "@budibase/types"
|
||||
import { builderSocket } from "../../websockets"
|
||||
|
||||
export async function fetch(ctx: BBContext) {
|
||||
const db = context.getAppDB()
|
||||
|
@ -87,13 +88,17 @@ export async function save(ctx: BBContext) {
|
|||
if (eventFn) {
|
||||
await eventFn(screen)
|
||||
}
|
||||
ctx.message = `Screen ${screen.name} saved.`
|
||||
ctx.body = {
|
||||
const savedScreen = {
|
||||
...screen,
|
||||
_id: response.id,
|
||||
_rev: response.rev,
|
||||
}
|
||||
ctx.message = `Screen ${screen.name} saved.`
|
||||
ctx.body = {
|
||||
...savedScreen,
|
||||
pluginAdded,
|
||||
}
|
||||
builderSocket?.emitScreenUpdate(ctx, savedScreen)
|
||||
}
|
||||
|
||||
export async function destroy(ctx: BBContext) {
|
||||
|
@ -108,6 +113,7 @@ export async function destroy(ctx: BBContext) {
|
|||
message: "Screen deleted successfully",
|
||||
}
|
||||
ctx.status = 200
|
||||
builderSocket?.emitScreenDeletion(ctx, id)
|
||||
}
|
||||
|
||||
function findPlugins(component: ScreenProps, foundPlugins: string[]) {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue