forked from tsai/budibase
committed by
GitHub
230 changed files with 5947 additions and 3221 deletions
@ -1,5 +1,5 @@ |
|||||
; CouchDB Configuration Settings |
; CouchDB Configuration Settings |
||||
|
|
||||
[couchdb] |
[couchdb] |
||||
database_dir = /data/couch/dbs |
database_dir = DATA_DIR/couchdb/dbs |
||||
view_index_dir = /data/couch/views |
view_index_dir = DATA_DIR/couchdb/views |
||||
|
|||||
@ -0,0 +1,7 @@ |
|||||
|
export const getAccount = jest.fn() |
||||
|
export const getAccountByTenantId = jest.fn() |
||||
|
|
||||
|
jest.mock("../../../src/cloud/accounts", () => ({ |
||||
|
getAccount, |
||||
|
getAccountByTenantId, |
||||
|
})) |
||||
@ -1,2 +0,0 @@ |
|||||
exports.MOCK_DATE = new Date("2020-01-01T00:00:00.000Z") |
|
||||
exports.MOCK_DATE_TIMESTAMP = 1577836800000 |
|
||||
@ -0,0 +1,2 @@ |
|||||
|
export const MOCK_DATE = new Date("2020-01-01T00:00:00.000Z") |
||||
|
export const MOCK_DATE_TIMESTAMP = 1577836800000 |
||||
@ -1,9 +0,0 @@ |
|||||
const posthog = require("./posthog") |
|
||||
const events = require("./events") |
|
||||
const date = require("./date") |
|
||||
|
|
||||
module.exports = { |
|
||||
posthog, |
|
||||
date, |
|
||||
events, |
|
||||
} |
|
||||
@ -0,0 +1,4 @@ |
|||||
|
import "./posthog" |
||||
|
import "./events" |
||||
|
export * as accounts from "./accounts" |
||||
|
export * as date from "./date" |
||||
@ -0,0 +1,178 @@ |
|||||
|
import filterTests from "../../support/filterTests" |
||||
|
// const interact = require("../support/interact")
|
||||
|
|
||||
|
filterTests(["smoke", "all"], () => { |
||||
|
context("Auth Configuration", () => { |
||||
|
before(() => { |
||||
|
cy.login() |
||||
|
}) |
||||
|
|
||||
|
after(() => { |
||||
|
cy.get(".spectrum-SideNav li").contains("Auth").click() |
||||
|
cy.location().should(loc => { |
||||
|
expect(loc.pathname).to.eq("/builder/portal/manage/auth") |
||||
|
}) |
||||
|
|
||||
|
cy.get("[data-cy=new-scope-input]").clear() |
||||
|
|
||||
|
cy.get("div.content").scrollTo("bottom") |
||||
|
cy.get("[data-cy=oidc-active]").click() |
||||
|
|
||||
|
cy.get("[data-cy=oidc-active]").should('not.be.checked') |
||||
|
|
||||
|
cy.intercept("POST", "/api/global/configs").as("updateAuth") |
||||
|
cy.get("button[data-cy=oidc-save]").contains("Save").click({force: true}) |
||||
|
cy.wait("@updateAuth") |
||||
|
cy.get("@updateAuth").its("response.statusCode").should("eq", 200) |
||||
|
|
||||
|
cy.get(".spectrum-Toast-content") |
||||
|
.contains("Settings saved") |
||||
|
.should("be.visible") |
||||
|
}) |
||||
|
|
||||
|
it("Should allow updating of the OIDC config", () => { |
||||
|
cy.get(".spectrum-SideNav li").contains("Auth").click() |
||||
|
cy.location().should(loc => { |
||||
|
expect(loc.pathname).to.eq("/builder/portal/manage/auth") |
||||
|
}) |
||||
|
cy.get("div.content").scrollTo("bottom") |
||||
|
cy.get(".spectrum-Toast .spectrum-ClearButton").click() |
||||
|
|
||||
|
cy.get("input[data-cy=configUrl]").type("http://budi-auth.com/v2") |
||||
|
cy.get("input[data-cy=clientID]").type("34ac6a13-f24a-4b52-c70d-fa544ffd11b2") |
||||
|
cy.get("input[data-cy=clientSecret]").type("12A8Q~4nS_DWhOOJ2vWIRsNyDVsdtXPD.Zxa9df_") |
||||
|
|
||||
|
cy.get("button[data-cy=oidc-save]").should("not.be.disabled"); |
||||
|
|
||||
|
cy.intercept("POST", "/api/global/configs").as("updateAuth") |
||||
|
cy.get("button[data-cy=oidc-save]").contains("Save").click({force: true}) |
||||
|
cy.wait("@updateAuth") |
||||
|
cy.get("@updateAuth").its("response.statusCode").should("eq", 200) |
||||
|
|
||||
|
cy.get(".spectrum-Toast-content") |
||||
|
.contains("Settings saved") |
||||
|
.should("be.visible") |
||||
|
}) |
||||
|
|
||||
|
it("Should display default scopes in advanced config.", () => { |
||||
|
cy.get(".spectrum-SideNav li").contains("Auth").click() |
||||
|
cy.location().should(loc => { |
||||
|
expect(loc.pathname).to.eq("/builder/portal/manage/auth") |
||||
|
}) |
||||
|
cy.get("div.content").scrollTo("bottom") |
||||
|
|
||||
|
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4) |
||||
|
|
||||
|
cy.get(".spectrum-Tags-item").contains("openid") |
||||
|
cy.get(".spectrum-Tags-item").contains("openid").find(".spectrum-ClearButton").should("not.exist") |
||||
|
|
||||
|
cy.get(".spectrum-Tags-item").contains("offline_access") |
||||
|
cy.get(".spectrum-Tags-item").contains("email") |
||||
|
cy.get(".spectrum-Tags-item").contains("profile") |
||||
|
}) |
||||
|
|
||||
|
it("Add a new scopes", () => { |
||||
|
cy.get(".spectrum-SideNav li").contains("Auth").click() |
||||
|
cy.location().should(loc => { |
||||
|
expect(loc.pathname).to.eq("/builder/portal/manage/auth") |
||||
|
}) |
||||
|
cy.get("div.content").scrollTo("bottom") |
||||
|
|
||||
|
cy.get("[data-cy=new-scope-input]").type("Sample{enter}") |
||||
|
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 5) |
||||
|
cy.get(".spectrum-Tags-item").contains("Sample") |
||||
|
|
||||
|
cy.get(".auth-form input.spectrum-Textfield-input").type("Another ") |
||||
|
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 6) |
||||
|
cy.get(".spectrum-Tags-item").contains("Another") |
||||
|
|
||||
|
cy.get("button[data-cy=oidc-save]").should("not.be.disabled"); |
||||
|
|
||||
|
cy.intercept("POST", "/api/global/configs").as("updateAuth") |
||||
|
cy.get("button[data-cy=oidc-save]").contains("Save").click({force: true}) |
||||
|
cy.wait("@updateAuth") |
||||
|
cy.get("@updateAuth").its("response.statusCode").should("eq", 200) |
||||
|
|
||||
|
cy.reload() |
||||
|
|
||||
|
cy.get("div.content").scrollTo("bottom") |
||||
|
|
||||
|
cy.get(".spectrum-Tags-item").contains("openid") |
||||
|
cy.get(".spectrum-Tags-item").contains("offline_access") |
||||
|
cy.get(".spectrum-Tags-item").contains("email") |
||||
|
cy.get(".spectrum-Tags-item").contains("profile") |
||||
|
cy.get(".spectrum-Tags-item").contains("Sample") |
||||
|
cy.get(".spectrum-Tags-item").contains("Another") |
||||
|
}) |
||||
|
|
||||
|
it("Should allow the removal of auth scopes", () => { |
||||
|
cy.get(".spectrum-SideNav li").contains("Auth").click() |
||||
|
cy.location().should(loc => { |
||||
|
expect(loc.pathname).to.eq("/builder/portal/manage/auth") |
||||
|
}) |
||||
|
cy.get("div.content").scrollTo("bottom") |
||||
|
|
||||
|
cy.get(".spectrum-Tags-item").contains("offline_access").parent().find(".spectrum-ClearButton").click() |
||||
|
cy.get(".spectrum-Tags-item").contains("profile").parent().find(".spectrum-ClearButton").click() |
||||
|
|
||||
|
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4) |
||||
|
|
||||
|
cy.get(".spectrum-Tags-item").contains("offline_access").should("not.exist") |
||||
|
cy.get(".spectrum-Tags-item").contains("profile").should("not.exist") |
||||
|
|
||||
|
cy.get("button[data-cy=oidc-save]").should("not.be.disabled"); |
||||
|
|
||||
|
cy.intercept("POST", "/api/global/configs").as("updateAuth") |
||||
|
cy.get("button[data-cy=oidc-save]").contains("Save").click({force: true}) |
||||
|
cy.wait("@updateAuth") |
||||
|
cy.get("@updateAuth").its("response.statusCode").should("eq", 200) |
||||
|
|
||||
|
cy.get(".spectrum-Toast-content") |
||||
|
.contains("Settings saved") |
||||
|
.should("be.visible") |
||||
|
|
||||
|
cy.reload() |
||||
|
|
||||
|
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4) |
||||
|
|
||||
|
cy.get(".spectrum-Tags-item").contains("offline_access").should("not.exist") |
||||
|
cy.get(".spectrum-Tags-item").contains("profile").should("not.exist") |
||||
|
}) |
||||
|
|
||||
|
it("Should allow auth scopes to be reset to the core defaults.", () => { |
||||
|
cy.get(".spectrum-SideNav li").contains("Auth").click() |
||||
|
|
||||
|
cy.get("div.content").scrollTo("bottom") |
||||
|
|
||||
|
cy.get("[data-cy=restore-oidc-default-scopes]").click({force: true}) |
||||
|
|
||||
|
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4) |
||||
|
|
||||
|
cy.get(".spectrum-Tags-item").contains("openid") |
||||
|
cy.get(".spectrum-Tags-item").contains("offline_access") |
||||
|
cy.get(".spectrum-Tags-item").contains("email") |
||||
|
cy.get(".spectrum-Tags-item").contains("profile") |
||||
|
}) |
||||
|
|
||||
|
it("Should not allow invalid characters in the auth scopes", () => { |
||||
|
cy.get("[data-cy=new-scope-input]").type("thisIsInvalid\\{enter}") |
||||
|
cy.get(".spectrum-Form-itemField .error").contains("Auth scopes cannot contain spaces, double quotes or backslashes") |
||||
|
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4) |
||||
|
|
||||
|
cy.get("[data-cy=new-scope-input]").clear() |
||||
|
|
||||
|
cy.get("[data-cy=new-scope-input]").type("alsoInvalid\"{enter}") |
||||
|
cy.get(".spectrum-Form-itemField .error").contains("Auth scopes cannot contain spaces, double quotes or backslashes") |
||||
|
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4) |
||||
|
|
||||
|
cy.get("[data-cy=new-scope-input]").clear() |
||||
|
}) |
||||
|
|
||||
|
it("Should not allow duplicate auth scopes", () => { |
||||
|
cy.get("[data-cy=new-scope-input]").type("offline_access{enter}") |
||||
|
cy.get(".spectrum-Form-itemField .error").contains("Auth scope already exists") |
||||
|
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4) |
||||
|
}) |
||||
|
|
||||
|
}) |
||||
|
}) |
||||
@ -0,0 +1,61 @@ |
|||||
|
<script> |
||||
|
import { Select, Label, Checkbox } from "@budibase/bbui" |
||||
|
import { onMount } from "svelte" |
||||
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte" |
||||
|
|
||||
|
export let parameters |
||||
|
export let bindings = [] |
||||
|
|
||||
|
const types = [ |
||||
|
{ |
||||
|
label: "Success", |
||||
|
value: "success", |
||||
|
}, |
||||
|
{ |
||||
|
label: "Warning", |
||||
|
value: "warning", |
||||
|
}, |
||||
|
{ |
||||
|
label: "Error", |
||||
|
value: "error", |
||||
|
}, |
||||
|
{ |
||||
|
label: "Info", |
||||
|
value: "info", |
||||
|
}, |
||||
|
] |
||||
|
|
||||
|
onMount(() => { |
||||
|
if (!parameters.type) { |
||||
|
parameters.type = "success" |
||||
|
} |
||||
|
if (parameters.autoDismiss == null) { |
||||
|
parameters.autoDismiss = true |
||||
|
} |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
<div class="root"> |
||||
|
<Label>Type</Label> |
||||
|
<Select bind:value={parameters.type} options={types} placeholder={null} /> |
||||
|
<Label>Message</Label> |
||||
|
<DrawerBindableInput |
||||
|
{bindings} |
||||
|
value={parameters.message} |
||||
|
on:change={e => (parameters.message = e.detail)} |
||||
|
/> |
||||
|
<Label /> |
||||
|
<Checkbox text="Auto dismiss" bind:value={parameters.autoDismiss} /> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.root { |
||||
|
display: grid; |
||||
|
column-gap: var(--spacing-l); |
||||
|
row-gap: var(--spacing-s); |
||||
|
grid-template-columns: 60px 1fr; |
||||
|
align-items: center; |
||||
|
max-width: 400px; |
||||
|
margin: 0 auto; |
||||
|
} |
||||
|
</style> |
||||
@ -1,104 +0,0 @@ |
|||||
export default ` |
|
||||
<html> |
|
||||
<head> |
|
||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" /> |
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" /> |
|
||||
<link |
|
||||
href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600;700&display=swap" |
|
||||
rel="stylesheet" |
|
||||
/> |
|
||||
<link |
|
||||
href="https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css" |
|
||||
rel="stylesheet" |
|
||||
/> |
|
||||
<style> |
|
||||
html, body { |
|
||||
padding: 0; |
|
||||
margin: 0; |
|
||||
} |
|
||||
html { |
|
||||
height: 100%; |
|
||||
width: 100%; |
|
||||
overflow: hidden; |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
justify-content: flex-start; |
|
||||
align-items: stretch; |
|
||||
} |
|
||||
body { |
|
||||
flex: 1 1 auto; |
|
||||
overflow: hidden; |
|
||||
} |
|
||||
|
|
||||
*, |
|
||||
*:before, |
|
||||
*:after { |
|
||||
box-sizing: border-box; |
|
||||
} |
|
||||
</style> |
|
||||
<script src='{{ CLIENT_LIB_PATH }}'></script> |
|
||||
<script> |
|
||||
function receiveMessage(event) { |
|
||||
if (!event.data) { |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// Parse received message
|
|
||||
// If parsing fails, just ignore and wait for the next message
|
|
||||
let parsed |
|
||||
try { |
|
||||
parsed = JSON.parse(event.data) |
|
||||
} catch (error) { |
|
||||
console.error("Client received invalid JSON") |
|
||||
// Ignore
|
|
||||
} |
|
||||
if (!parsed || !parsed.isBudibaseEvent) { |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// Extract data from message
|
|
||||
const { |
|
||||
selectedComponentId, |
|
||||
layout, |
|
||||
screen, |
|
||||
appId, |
|
||||
theme, |
|
||||
customTheme, |
|
||||
previewDevice, |
|
||||
navigation, |
|
||||
hiddenComponentIds |
|
||||
} = parsed |
|
||||
|
|
||||
// Set some flags so the app knows we're in the builder
|
|
||||
window["##BUDIBASE_IN_BUILDER##"] = true |
|
||||
window["##BUDIBASE_APP_ID##"] = appId |
|
||||
window["##BUDIBASE_PREVIEW_LAYOUT##"] = layout |
|
||||
window["##BUDIBASE_PREVIEW_SCREEN##"] = screen |
|
||||
window["##BUDIBASE_SELECTED_COMPONENT_ID##"] = selectedComponentId |
|
||||
window["##BUDIBASE_PREVIEW_ID##"] = Math.random() |
|
||||
window["##BUDIBASE_PREVIEW_THEME##"] = theme |
|
||||
window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"] = customTheme |
|
||||
window["##BUDIBASE_PREVIEW_DEVICE##"] = previewDevice |
|
||||
window["##BUDIBASE_PREVIEW_NAVIGATION##"] = navigation |
|
||||
window["##BUDIBASE_HIDDEN_COMPONENT_IDS##"] = hiddenComponentIds |
|
||||
|
|
||||
// Initialise app
|
|
||||
try { |
|
||||
if (window.loadBudibase) { |
|
||||
window.loadBudibase() |
|
||||
document.documentElement.classList.add("loaded") |
|
||||
} else { |
|
||||
throw "The client library couldn't be loaded" |
|
||||
} |
|
||||
} catch (error) { |
|
||||
window.parent.postMessage({ type: "error", error }) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
window.addEventListener("message", receiveMessage) |
|
||||
window.parent.postMessage({ type: "ready" }) |
|
||||
</script> |
|
||||
</head> |
|
||||
<body/> |
|
||||
</html> |
|
||||
` |
|
||||
@ -0,0 +1,118 @@ |
|||||
|
<script> |
||||
|
import { onMount } from "svelte" |
||||
|
import { selectedComponent, selectedScreen, store } from "builderStore" |
||||
|
import { findComponent } from "builderStore/componentUtils" |
||||
|
import { goto, isActive } from "@roxi/routify" |
||||
|
import { notifications } from "@budibase/bbui" |
||||
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte" |
||||
|
|
||||
|
let confirmDeleteDialog |
||||
|
let componentToDelete |
||||
|
|
||||
|
const keyHandlers = { |
||||
|
["^ArrowUp"]: async component => { |
||||
|
await store.actions.components.moveUp(component) |
||||
|
}, |
||||
|
["^ArrowDown"]: async component => { |
||||
|
await store.actions.components.moveDown(component) |
||||
|
}, |
||||
|
["^c"]: component => { |
||||
|
store.actions.components.copy(component, false) |
||||
|
}, |
||||
|
["^x"]: component => { |
||||
|
store.actions.components.copy(component, true) |
||||
|
}, |
||||
|
["^v"]: async component => { |
||||
|
await store.actions.components.paste(component, "inside") |
||||
|
}, |
||||
|
["^d"]: async component => { |
||||
|
store.actions.components.copy(component) |
||||
|
await store.actions.components.paste(component, "below") |
||||
|
}, |
||||
|
["^Enter"]: () => { |
||||
|
$goto("./new") |
||||
|
}, |
||||
|
["Delete"]: component => { |
||||
|
// Don't show confirmation for the screen itself |
||||
|
if (component?._id === $selectedScreen.props._id) { |
||||
|
return false |
||||
|
} |
||||
|
componentToDelete = component |
||||
|
confirmDeleteDialog.show() |
||||
|
}, |
||||
|
["ArrowUp"]: () => { |
||||
|
store.actions.components.selectPrevious() |
||||
|
}, |
||||
|
["ArrowDown"]: () => { |
||||
|
store.actions.components.selectNext() |
||||
|
}, |
||||
|
["Escape"]: () => { |
||||
|
if (!$isActive("/new")) { |
||||
|
return false |
||||
|
} |
||||
|
$goto("./") |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
const handleKeyAction = async (component, key, ctrlKey = false) => { |
||||
|
if (!component || !key) { |
||||
|
return false |
||||
|
} |
||||
|
try { |
||||
|
// Delete and backspace are the same |
||||
|
if (key === "Backspace") { |
||||
|
key = "Delete" |
||||
|
} |
||||
|
// Prefix key with a caret for ctrl modifier |
||||
|
if (ctrlKey) { |
||||
|
key = "^" + key |
||||
|
} |
||||
|
const handler = keyHandlers[key] |
||||
|
if (!handler) { |
||||
|
return false |
||||
|
} |
||||
|
return handler(component) |
||||
|
} catch (error) { |
||||
|
console.error(error) |
||||
|
notifications.error("Error handling key press") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const handleKeyPress = async e => { |
||||
|
// Ignore repeating events |
||||
|
if (e.repeat) { |
||||
|
return |
||||
|
} |
||||
|
// Ignore events when typing |
||||
|
const activeTag = document.activeElement?.tagName.toLowerCase() |
||||
|
if (["input", "textarea"].indexOf(activeTag) !== -1 && e.key !== "Escape") { |
||||
|
return |
||||
|
} |
||||
|
// Key events are always for the selected component |
||||
|
return handleKeyAction($selectedComponent, e.key, e.ctrlKey || e.metaKey) |
||||
|
} |
||||
|
|
||||
|
const handleComponentMenu = async e => { |
||||
|
// Menu events can be for any component |
||||
|
const { id, key, ctrlKey } = e.detail |
||||
|
const component = findComponent($selectedScreen.props, id) |
||||
|
return await handleKeyAction(component, key, ctrlKey) |
||||
|
} |
||||
|
|
||||
|
onMount(() => { |
||||
|
document.addEventListener("keydown", handleKeyPress) |
||||
|
document.addEventListener("component-menu", handleComponentMenu) |
||||
|
return () => { |
||||
|
document.removeEventListener("keydown", handleKeyPress) |
||||
|
document.removeEventListener("component-menu", handleComponentMenu) |
||||
|
} |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
<ConfirmDialog |
||||
|
bind:this={confirmDeleteDialog} |
||||
|
title="Confirm Deletion" |
||||
|
body={`Are you sure you want to delete "${componentToDelete?._instanceName}"?`} |
||||
|
okText="Delete Component" |
||||
|
onOk={() => store.actions.components.delete(componentToDelete)} |
||||
|
/> |
||||
@ -0,0 +1,82 @@ |
|||||
|
<script> |
||||
|
import { setContext } from "svelte" |
||||
|
import { dndStore } from "./dndStore" |
||||
|
import { notifications } from "@budibase/bbui" |
||||
|
|
||||
|
let scrollRef |
||||
|
|
||||
|
const scrollTo = bounds => { |
||||
|
if (!bounds) { |
||||
|
return |
||||
|
} |
||||
|
const sidebarWidth = 259 |
||||
|
const navItemHeight = 32 |
||||
|
const { scrollLeft, scrollTop, offsetHeight } = scrollRef |
||||
|
let scrollBounds = scrollRef.getBoundingClientRect() |
||||
|
let newOffsets = {} |
||||
|
|
||||
|
// Calculate left offset |
||||
|
const offsetX = bounds.left + bounds.width + scrollLeft - 36 |
||||
|
if (offsetX > sidebarWidth) { |
||||
|
newOffsets.left = offsetX - sidebarWidth |
||||
|
} else { |
||||
|
newOffsets.left = 0 |
||||
|
} |
||||
|
if (newOffsets.left === scrollLeft) { |
||||
|
delete newOffsets.left |
||||
|
} |
||||
|
|
||||
|
// Calculate top offset |
||||
|
const offsetY = bounds.top - scrollBounds?.top + scrollTop |
||||
|
if (offsetY > scrollTop + offsetHeight - 2 * navItemHeight) { |
||||
|
newOffsets.top = offsetY - offsetHeight + 2 * navItemHeight |
||||
|
} else if (offsetY < scrollTop + navItemHeight) { |
||||
|
newOffsets.top = offsetY - navItemHeight |
||||
|
} else { |
||||
|
delete newOffsets.top |
||||
|
} |
||||
|
|
||||
|
// Skip if offset is unchanged |
||||
|
if (newOffsets.left == null && newOffsets.top == null) { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Smoothly scroll to the offset |
||||
|
scrollRef.scroll({ |
||||
|
...newOffsets, |
||||
|
behavior: "smooth", |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
// Set scroll context so components can invoke scrolling when selected |
||||
|
setContext("scroll", { |
||||
|
scrollTo, |
||||
|
}) |
||||
|
|
||||
|
const onDrop = async () => { |
||||
|
try { |
||||
|
await dndStore.actions.drop() |
||||
|
} catch (error) { |
||||
|
console.error(error) |
||||
|
notifications.error("Error saving component") |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<div |
||||
|
bind:this={scrollRef} |
||||
|
on:drop={onDrop} |
||||
|
ondragover="return false" |
||||
|
ondragenter="return false" |
||||
|
> |
||||
|
<slot /> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
div { |
||||
|
padding: var(--spacing-xl) 0; |
||||
|
flex: 1 1 auto; |
||||
|
overflow: auto; |
||||
|
height: 0; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,38 @@ |
|||||
|
<script> |
||||
|
import { createEventDispatcher } from "svelte" |
||||
|
import { Slider, Button } from "@budibase/bbui" |
||||
|
|
||||
|
export let customTheme |
||||
|
|
||||
|
const dispatch = createEventDispatcher() |
||||
|
const options = ["0", "4px", "8px", "16px"] |
||||
|
|
||||
|
$: index = options.indexOf(customTheme.buttonBorderRadius) ?? 2 |
||||
|
|
||||
|
const onChange = async e => { |
||||
|
dispatch("change", options[e.detail]) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<div class="container"> |
||||
|
<Slider min={0} max={3} step={1} value={index} on:change={onChange} /> |
||||
|
<div class="button" style="--radius: {customTheme.buttonBorderRadius};"> |
||||
|
<Button primary newStyles>Button</Button> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.container { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
justify-content: flex-start; |
||||
|
align-items: center; |
||||
|
gap: var(--spacing-xl); |
||||
|
} |
||||
|
.container :global(.spectrum-Form-item) { |
||||
|
flex: 1 1 auto; |
||||
|
} |
||||
|
.button :global(.spectrum-Button) { |
||||
|
border-radius: var(--radius) !important; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,73 @@ |
|||||
|
<script> |
||||
|
import { Body, ModalContent, Table } from "@budibase/bbui" |
||||
|
import { onMount } from "svelte" |
||||
|
|
||||
|
export let userData |
||||
|
export let deleteUsersResponse |
||||
|
|
||||
|
let successCount |
||||
|
let failureCount |
||||
|
let title |
||||
|
let unsuccessfulUsers |
||||
|
let message |
||||
|
|
||||
|
const setTitle = () => { |
||||
|
if (successCount) { |
||||
|
title = `${successCount} users deleted` |
||||
|
} else { |
||||
|
title = "Oops!" |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const setMessage = () => { |
||||
|
if (successCount) { |
||||
|
message = "However there was a problem deleting some users." |
||||
|
} else { |
||||
|
message = "There was a problem deleting some users." |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const setUsers = () => { |
||||
|
unsuccessfulUsers = deleteUsersResponse.unsuccessful.map(user => { |
||||
|
return { |
||||
|
email: user.email, |
||||
|
reason: user.reason, |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
onMount(() => { |
||||
|
successCount = deleteUsersResponse.successful.length |
||||
|
failureCount = deleteUsersResponse.unsuccessful.length |
||||
|
setTitle() |
||||
|
setMessage() |
||||
|
setUsers() |
||||
|
}) |
||||
|
|
||||
|
const schema = { |
||||
|
email: {}, |
||||
|
reason: {}, |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<ModalContent |
||||
|
size="M" |
||||
|
{title} |
||||
|
confirmText="Close" |
||||
|
showCloseIcon={false} |
||||
|
showCancelButton={false} |
||||
|
> |
||||
|
<Body size="XS"> |
||||
|
{message} |
||||
|
</Body> |
||||
|
<Table |
||||
|
{schema} |
||||
|
data={unsuccessfulUsers} |
||||
|
allowEditColumns={false} |
||||
|
allowEditRows={false} |
||||
|
allowSelectRows={false} |
||||
|
/> |
||||
|
</ModalContent> |
||||
|
|
||||
|
<style> |
||||
|
</style> |
||||
@ -0,0 +1,75 @@ |
|||||
|
<script> |
||||
|
import { Body, ModalContent, Table } from "@budibase/bbui" |
||||
|
import { onMount } from "svelte" |
||||
|
|
||||
|
export let inviteUsersResponse |
||||
|
|
||||
|
let hasSuccess |
||||
|
let hasFailure |
||||
|
let title |
||||
|
let failureMessage |
||||
|
|
||||
|
let unsuccessfulUsers |
||||
|
|
||||
|
const setTitle = () => { |
||||
|
if (hasSuccess) { |
||||
|
title = "Users invited!" |
||||
|
} else if (hasFailure) { |
||||
|
title = "Oops!" |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const setFailureMessage = () => { |
||||
|
if (hasSuccess) { |
||||
|
failureMessage = "However there was a problem inviting some users." |
||||
|
} else { |
||||
|
failureMessage = "There was a problem inviting users." |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const setUsers = () => { |
||||
|
unsuccessfulUsers = inviteUsersResponse.unsuccessful.map(user => { |
||||
|
return { |
||||
|
email: user.email, |
||||
|
reason: user.reason, |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
onMount(() => { |
||||
|
hasSuccess = inviteUsersResponse.successful.length |
||||
|
hasFailure = inviteUsersResponse.unsuccessful.length |
||||
|
setTitle() |
||||
|
setFailureMessage() |
||||
|
setUsers() |
||||
|
}) |
||||
|
|
||||
|
const failedSchema = { |
||||
|
email: {}, |
||||
|
reason: {}, |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<ModalContent showCancelButton={false} {title} confirmText="Done"> |
||||
|
{#if hasSuccess} |
||||
|
<Body size="XS"> |
||||
|
Your users should now receive an email invite to get access to their |
||||
|
Budibase account |
||||
|
</Body> |
||||
|
{/if} |
||||
|
{#if hasFailure} |
||||
|
<Body size="XS"> |
||||
|
{failureMessage} |
||||
|
</Body> |
||||
|
<Table |
||||
|
schema={failedSchema} |
||||
|
data={unsuccessfulUsers} |
||||
|
allowEditColumns={false} |
||||
|
allowEditRows={false} |
||||
|
allowSelectRows={false} |
||||
|
/> |
||||
|
{/if} |
||||
|
</ModalContent> |
||||
|
|
||||
|
<style> |
||||
|
</style> |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue