1
0
Fork 0
mirror of synced 2024-07-03 05:20:32 +12:00

Merge pull request #2606 from Budibase/responsive-portal

Responsive portal
This commit is contained in:
Andrew Kingston 2021-09-16 17:28:21 +01:00 committed by GitHub
commit 00b12463f6
21 changed files with 2118 additions and 262 deletions

View file

@ -21,8 +21,5 @@
.wide {
max-width: none;
margin: 0;
padding: var(--spacing-xl) calc(var(--spacing-xl) * 2)
calc(var(--spacing-xl) * 2) calc(var(--spacing-xl) * 2);
min-height: calc(100% - var(--spacing-xl) * 3);
}
</style>

View file

@ -13,7 +13,7 @@
}
}
export let value = false
export let value = null
export let minValue = 0
export let maxValue = 100

View file

@ -13,6 +13,7 @@
class="spectrum-SideNav-item"
class:is-selected={selected}
class:is-disabled={disabled}
on:click
>
{#if heading}
<h2 class="spectrum-SideNav-heading" id="nav-heading-{heading}">

View file

@ -34,7 +34,7 @@ Cypress.Commands.add("createApp", name => {
cy.get(".spectrum-Modal")
.within(() => {
cy.get("input").eq(0).type(name).should("have.value", name).blur()
cy.contains("Create app").click()
cy.get(".spectrum-ButtonGroup").contains("Create app").click()
})
.then(() => {
cy.expandBudibaseConnection()

View file

@ -8,9 +8,28 @@
} from "@budibase/bbui"
import { admin } from "stores/portal"
import { goto } from "@roxi/routify"
import { onMount } from "svelte"
let width = window.innerWidth
$: side = width < 500 ? "right" : "left"
const resizeObserver = new ResizeObserver(entries => {
if (entries?.[0]) {
width = entries[0].contentRect?.width
}
})
onMount(() => {
const doc = document.documentElement
resizeObserver.observe(doc)
return () => {
resizeObserver.unobserve(doc)
}
})
</script>
<ActionMenu>
<ActionMenu align={side}>
<div slot="control" class="icon">
<ProgressCircle size="S" value={$admin.onboardingProgress} />
</div>
@ -37,7 +56,7 @@
.item {
display: grid;
align-items: center;
grid-template-columns: 200px 20px;
grid-template-columns: 175px 20px;
}
.icon {
cursor: pointer;

View file

@ -104,8 +104,10 @@
// remove all iframe event listeners on component destroy
onDestroy(() => {
iframe.contentWindow.removeEventListener("bb-event", handleBudibaseEvent)
iframe.contentWindow.removeEventListener("keydown", handleKeydownEvent)
if (iframe.contentWindow) {
iframe.contentWindow.removeEventListener("bb-event", handleBudibaseEvent)
iframe.contentWindow.removeEventListener("keydown", handleKeydownEvent)
}
})
const handleBudibaseEvent = event => {
@ -123,17 +125,21 @@
} else {
console.warning(`Client sent unknown event type: ${type}`)
}
};
}
const handleKeydownEvent = event => {
if ((event.key === "Delete" || event.key === "Backspace") &&
if (
(event.key === "Delete" || event.key === "Backspace") &&
selectedComponentId &&
['input', 'textarea'].indexOf(iframe.contentWindow.document.activeElement?.tagName.toLowerCase()) === -1) {
confirmDeleteComponent(selectedComponentId);
["input", "textarea"].indexOf(
iframe.contentWindow.document.activeElement?.tagName.toLowerCase()
) === -1
) {
confirmDeleteComponent(selectedComponentId)
}
}
const confirmDeleteComponent = (componentId) => {
const confirmDeleteComponent = componentId => {
idToDelete = componentId
confirmDeleteDialog.show()
}

View file

@ -28,7 +28,7 @@
</Heading>
</div>
</div>
<div>
<div class="desktop">
{#if app.updatedAt}
{processStringSync("Updated {{ duration time 'millisecond' }} ago", {
time: new Date().getTime() - new Date(app.updatedAt).getTime(),
@ -37,7 +37,7 @@
Never updated
{/if}
</div>
<div>
<div class="desktop">
<StatusLight
positive={!app.lockedYou && !app.lockedOther}
notice={app.lockedYou}
@ -52,7 +52,7 @@
{/if}
</StatusLight>
</div>
<div>
<div class="desktop">
<StatusLight active={app.deployed} neutral={!app.deployed}>
{#if app.deployed}Published{:else}Unpublished{/if}
</StatusLight>
@ -109,4 +109,10 @@
cursor: pointer;
transition: color 130ms ease;
}
@media (max-width: 640px) {
.desktop {
display: none !important;
}
}
</style>

View file

@ -134,7 +134,7 @@
</script>
<ModalContent
title={template ? "Import app" : "Create new app"}
title={template ? "Import app" : "Create app"}
confirmText={template ? "Import app" : "Create app"}
onConfirm={createNewApp}
disabled={!valid}

View file

@ -41,17 +41,8 @@
<Page>
<div class="content">
<Layout noPadding>
<img alt="logo" src={$organisation.logoUrl || Logo} />
<div class="info-title">
<Layout noPadding gap="XS">
<Heading size="L">
Hey {$auth.user.firstName || $auth.user.email}
</Heading>
<Body>
Welcome to the {$organisation.company} portal. Below you'll find
the list of apps that you have access to.
</Body>
</Layout>
<div class="header">
<img alt="logo" src={$organisation.logoUrl || Logo} />
<ActionMenu align="right">
<div slot="control" class="avatar">
<Avatar
@ -81,6 +72,15 @@
<MenuItem icon="LogOut" on:click={auth.logout}>Log out</MenuItem>
</ActionMenu>
</div>
<Layout noPadding gap="XS">
<Heading size="M">
Hey {$auth.user.firstName || $auth.user.email}
</Heading>
<Body>
Welcome to the {$organisation.company} portal. Below you'll find the
list of apps that you have access to.
</Body>
</Layout>
<Divider />
{#if publishedApps.length}
<Heading>Apps</Heading>
@ -137,18 +137,18 @@
overflow: auto;
}
.content {
padding: 60px 0;
width: 100%;
}
.header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
img {
width: 40px;
margin-bottom: -12px;
}
.info-title {
display: grid;
grid-template-columns: 1fr auto;
grid-gap: var(--spacing-xl);
}
.avatar {
display: grid;
grid-template-columns: auto auto;
@ -160,7 +160,6 @@
filter: brightness(110%);
}
.group {
margin-top: var(--spacing-s);
}
.app {
display: grid;

View file

@ -9,6 +9,7 @@
ActionMenu,
MenuItem,
Modal,
clickOutside,
} from "@budibase/bbui"
import ConfigChecklist from "components/common/ConfigChecklist.svelte"
import { organisation, auth } from "stores/portal"
@ -21,6 +22,7 @@
let loaded = false
let userInfoModal
let changePasswordModal
let mobileMenuVisible = false
$: menu = buildMenu($auth.isAdmin)
@ -71,6 +73,9 @@
return menu
}
const showMobileMenu = () => (mobileMenuVisible = true)
const hideMobileMenu = () => (mobileMenuVisible = false)
onMount(async () => {
// Prevent non-builders from accessing the portal
if ($auth.user) {
@ -86,7 +91,11 @@
{#if $auth.user && loaded}
<div class="container">
<div class="nav">
<div
class="nav"
class:visible={mobileMenuVisible}
use:clickOutside={hideMobileMenu}
>
<Layout paddingX="L" paddingY="L">
<div class="branding">
<div class="name" on:click={() => $goto("./apps")}>
@ -100,7 +109,12 @@
<div class="menu">
<Navigation>
{#each menu as { title, href, heading }}
<Item selected={$isActive(href)} {href} {heading}>{title}</Item>
<Item
on:click={hideMobileMenu}
selected={$isActive(href)}
{href}
{heading}>{title}</Item
>
{/each}
</Navigation>
</div>
@ -108,30 +122,40 @@
</div>
<div class="main">
<div class="toolbar">
<div />
<ActionMenu align="right">
<div slot="control" class="avatar">
<Avatar
size="M"
initials={$auth.initials}
url={$auth.user.pictureUrl}
/>
<Icon size="XL" name="ChevronDown" />
</div>
<MenuItem icon="UserEdit" on:click={() => userInfoModal.show()}>
Update user information
</MenuItem>
<MenuItem
icon="LockClosed"
on:click={() => changePasswordModal.show()}
>
Update password
</MenuItem>
<MenuItem icon="UserDeveloper" on:click={() => $goto("../apps")}>
Close developer mode
</MenuItem>
<MenuItem icon="LogOut" on:click={auth.logout}>Log out</MenuItem>
</ActionMenu>
<div class="mobile-toggle">
<Icon hoverable name="ShowMenu" on:click={showMobileMenu} />
</div>
<div class="mobile-logo">
<img
src={$organisation?.logoUrl || Logo}
alt={$organisation?.company || "Budibase"}
/>
</div>
<div class="user-dropdown">
<ActionMenu align="right">
<div slot="control" class="avatar">
<Avatar
size="M"
initials={$auth.initials}
url={$auth.user.pictureUrl}
/>
<Icon size="XL" name="ChevronDown" />
</div>
<MenuItem icon="UserEdit" on:click={() => userInfoModal.show()}>
Update user information
</MenuItem>
<MenuItem
icon="LockClosed"
on:click={() => changePasswordModal.show()}
>
Update password
</MenuItem>
<MenuItem icon="UserDeveloper" on:click={() => $goto("../apps")}>
Close developer mode
</MenuItem>
<MenuItem icon="LogOut" on:click={auth.logout}>Log out</MenuItem>
</ActionMenu>
</div>
</div>
<div class="content">
<slot />
@ -149,16 +173,20 @@
<style>
.container {
height: 100%;
display: grid;
grid-template-columns: 250px 1fr;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: stretch;
}
.nav {
background: var(--background);
border-right: var(--border-light);
overflow: auto;
flex: 0 0 auto;
width: 250px;
}
.main {
flex: 1 1 auto;
display: grid;
grid-template-rows: auto 1fr;
overflow: hidden;
@ -192,11 +220,21 @@
.toolbar {
background: var(--background);
border-bottom: var(--border-light);
display: grid;
grid-template-columns: 250px auto;
display: flex;
flex-direction: row;
justify-content: space-between;
padding: var(--spacing-m) calc(var(--spacing-xl) * 2);
align-items: center;
padding: var(--spacing-m) calc(var(--spacing-xl) * 2);
}
.mobile-toggle,
.mobile-logo {
display: none;
}
.user-dropdown {
flex: 1 1 auto;
display: flex;
flex-direction: row;
justify-content: flex-end;
}
img {
width: 28px;
@ -210,4 +248,43 @@
.content {
overflow: auto;
}
@media (max-width: 640px) {
.toolbar {
background: var(--background);
border-bottom: var(--border-light);
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: var(--spacing-m) calc(var(--spacing-xl) * 1.5);
}
.nav {
position: absolute;
left: -250px;
height: 100%;
transition: left ease-in-out 230ms;
z-index: 100;
}
.nav.visible {
left: 0;
box-shadow: 0 0 80px 20px rgba(0, 0, 0, 0.3);
}
.mobile-toggle,
.mobile-logo {
display: block;
}
.mobile-toggle,
.user-dropdown {
flex: 1 1 0;
}
/* Reduce BBUI page padding */
.content :global(> *) {
padding: calc(var(--spacing-xl) * 1.5) !important;
}
}
</style>

View file

@ -201,7 +201,7 @@
<Heading>Apps</Heading>
<ButtonGroup>
<Button secondary on:click={initiateAppImport}>Import app</Button>
<Button cta on:click={initiateAppCreation}>Create new app</Button>
<Button cta on:click={initiateAppCreation}>Create app</Button>
</ButtonGroup>
</div>
<div class="filter">
@ -347,4 +347,10 @@
justify-content: center;
align-items: center;
}
@media (max-width: 640px) {
.appTable {
grid-template-columns: 1fr auto;
}
}
</style>

View file

@ -272,7 +272,7 @@
})
</script>
<Layout>
<Layout noPadding>
<Layout gap="XS" noPadding>
<Heading size="M">Authentication</Heading>
<Body>
@ -285,19 +285,17 @@
<Divider />
<Layout gap="XS" noPadding>
<Heading size="S">
<div>
<div class="provider-title">
<GoogleLogo />
Google
<div class="google-save-button">
<div>
<Button
disabled={googleSaveButtonDisabled}
size="s"
cta
on:click={() => save([providers.google])}>Save</Button
>
</div>
</div>
<span>Google</span>
<Button
disabled={googleSaveButtonDisabled}
size="s"
cta
on:click={() => save([providers.google])}
>
Save
</Button>
</div>
</Heading>
<Body size="S">
@ -317,12 +315,8 @@
</div>
{/each}
<div class="form-row">
<div class="field">
<Label size="L">Activated</Label>
<span class="alignedToggle">
<Toggle text="" bind:value={providers.google.config.activated} />
</span>
</div>
<Label size="L">Activated</Label>
<Toggle text="" bind:value={providers.google.config.activated} />
</div>
</Layout>
{/if}
@ -330,21 +324,19 @@
<Divider />
<Layout gap="XS" noPadding>
<Heading size="S">
<div>
<div class="provider-title">
<OidcLogo />
OpenID Connect
<div class="oidc-save-button">
<div>
<Button
disabled={oidcSaveButtonDisabled}
size="s"
cta
on:click={() => save([providers.oidc])}>Save</Button
>
</div>
</div>
</div></Heading
>
<span>OpenID Connect</span>
<Button
disabled={oidcSaveButtonDisabled}
size="s"
cta
on:click={() => save([providers.oidc])}
>
Save
</Button>
</div>
</Heading>
<Body size="S">
To allow users to authenticate using OIDC, fill out the fields below.
</Body>
@ -360,7 +352,8 @@
/>
</div>
{/each}
<br />
</Layout>
<Layout gap="XS" noPadding>
<Body size="S">
To customize your login button, fill out the fields below.
</Body>
@ -383,54 +376,37 @@
on:change={e => onFileSelected(e)}
bind:this={fileinput}
/>
</Layout>
<div class="form-row">
<div class="field">
<div class="form-row">
<Label size="L">Activated</Label>
<span class="alignedToggle">
<Toggle
text=""
bind:value={providers.oidc.config.configs[0].activated}
/>
</span>
<Toggle
text=""
bind:value={providers.oidc.config.configs[0].activated}
/>
</div>
</div>
</Layout>
{/if}
</Layout>
<style>
.field {
display: flex;
align-items: center;
}
.alignedToggle {
margin-left: 63%;
}
.form-row {
display: grid;
grid-template-columns: 20% 1fr;
grid-template-columns: 100px 1fr;
grid-gap: var(--spacing-l);
align-items: center;
}
span {
display: flex;
align-items: center;
gap: var(--spacing-s);
}
input {
input[type="file"] {
display: none;
}
.google-save-button {
display: inline-block;
margin-left: 400px;
.provider-title {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
gap: var(--spacing-m);
}
.oidc-save-button {
display: inline-block;
margin-left: 320px;
.provider-title span {
flex: 1 1 auto;
}
</style>

View file

@ -71,90 +71,109 @@
}
</script>
<Layout gap="XS" noPadding>
<div class="back">
<ActionButton
on:click={() => $goto("./")}
quiet
size="S"
icon="BackAndroid"
>
Back to email settings
</ActionButton>
<Layout noPadding>
<Layout gap="XS" noPadding>
<div>
<ActionButton
on:click={() => $goto("./")}
quiet
size="S"
icon="BackAndroid"
>
Back to email settings
</ActionButton>
</div>
<header>
<Heading>
Email Template: {name}
</Heading>
<Button cta on:click={saveTemplate}>Save</Button>
</header>
<Body>
{description}
<br />
Change the email template here. Add dynamic content by using the bindings menu
on the right.
</Body>
</Layout>
<div>
<Tabs selected="Edit" on:select={fixMountBug}>
<Tab title="Edit">
<div class="template-editor">
<div class="template-text-editor">
<Editor
editorHeight={640}
bind:this={htmlEditor}
mode="handlebars"
on:change={e => {
selectedTemplate.contents = e.detail.value
}}
value={selectedTemplate?.contents}
/>
</div>
<div class="bindings-editor">
<Detail size="L">Bindings</Detail>
{#if mounted}
<Tabs selected="Template">
<Tab title="Template">
<TemplateBindings
title="Template Bindings"
bindings={templateBindings}
onBindingClick={setTemplateBinding}
/>
</Tab>
<Tab title="Common">
<TemplateBindings
title="Common Bindings"
bindings={$email?.definitions?.bindings?.common}
onBindingClick={setTemplateBinding}
/>
</Tab>
</Tabs>
{/if}
</div>
</div>
</Tab>
<Tab title="Preview">
<div class="preview">
<iframe title="preview" srcdoc={previewContent} />
</div>
</Tab>
</Tabs>
</div>
<header>
<Heading>
Email Template: {name}
</Heading>
<Button cta on:click={saveTemplate}>Save</Button>
</header>
<Detail>Description</Detail>
<Body>{description}</Body>
<Body
>Change the email template here. Add dynamic content by using the bindings
menu on the right.</Body
>
</Layout>
<Tabs selected="Edit" on:select={fixMountBug}>
<Tab title="Edit">
<div class="template-editor">
<Editor
editorHeight={800}
bind:this={htmlEditor}
mode="handlebars"
on:change={e => {
selectedTemplate.contents = e.detail.value
}}
value={selectedTemplate?.contents}
/>
<div class="bindings-editor">
<Detail size="L">Bindings</Detail>
{#if mounted}
<Tabs selected="Template">
<Tab title="Template">
<TemplateBindings
title="Template Bindings"
bindings={templateBindings}
onBindingClick={setTemplateBinding}
/>
</Tab>
<Tab title="Common">
<TemplateBindings
title="Common Bindings"
bindings={$email?.definitions?.bindings?.common}
onBindingClick={setTemplateBinding}
/>
</Tab>
</Tabs>
{/if}
</div>
</div>
</Tab>
<Tab title="Preview">
<div class="preview">
<iframe title="preview" srcdoc={previewContent} />
</div>
</Tab>
</Tabs>
<style>
.template-editor {
display: grid;
grid-template-columns: 1fr minmax(250px, 20%);
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: stretch;
flex-wrap: wrap;
grid-gap: var(--spacing-xl);
margin-top: var(--spacing-xl);
}
.template-text-editor {
flex: 1 1 0;
min-width: 250px;
}
.bindings-editor {
margin-top: var(--spacing-s);
max-height: 640px;
overflow: auto;
flex: 0 0 300px;
}
header {
display: flex;
width: 100%;
justify-content: space-between;
margin-top: var(--spacing-l);
align-items: flex-start;
}
.preview {
height: 800px;
padding: var(--spacing-xl) 0;
height: 640px;
margin-top: calc(var(--spacing-xl) + var(--spacing-s));
position: relative;
}
iframe {

View file

@ -107,7 +107,7 @@
fetchSmtp()
</script>
<Layout>
<Layout noPadding>
<Layout noPadding gap="XS">
<Heading size="M">Email</Heading>
<Body>
@ -186,7 +186,7 @@
<style>
.form-row {
display: grid;
grid-template-columns: 25% 1fr;
grid-template-columns: 120px 1fr;
grid-gap: var(--spacing-l);
align-items: center;
}

View file

@ -95,7 +95,7 @@
<Layout noPadding>
<Layout gap="XS" noPadding>
<div class="back">
<div>
<ActionButton
on:click={() => $goto("./")}
quiet

View file

@ -49,7 +49,7 @@
}
</script>
<Layout>
<Layout noPadding>
<Layout gap="XS" noPadding>
<Heading>Users</Heading>
<Body>

View file

@ -83,7 +83,7 @@
</script>
{#if $auth.isAdmin}
<Layout>
<Layout noPadding>
<Layout gap="XS" noPadding>
<Heading size="M">Organisation</Heading>
<Body>
@ -99,7 +99,7 @@
</Layout>
<div class="fields">
<div class="field">
<Label size="L">Organization name</Label>
<Label size="L">Org. name</Label>
<Input thin bind:value={$values.company} />
</div>
<div class="field logo">
@ -138,12 +138,10 @@
please let us know below.
</Body>
</Layout>
<div class="fields">
<div class="field">
<Label size="L">Send Analytics to Budibase</Label>
<Toggle text="" bind:value={$values.analytics} />
</div>
</div>
<Toggle
text="Send Analytics to Budibase"
bind:value={$values.analytics}
/>
<div>
<Button disabled={loading} on:click={saveConfig} cta>Save</Button>
</div>
@ -158,7 +156,7 @@
}
.field {
display: grid;
grid-template-columns: 33% 1fr;
grid-template-columns: 100px 1fr;
align-items: center;
}
.file {

View file

@ -4,7 +4,7 @@
import { capitalise } from "helpers"
</script>
<Layout>
<Layout noPadding>
<Layout gap="XS" noPadding>
<Heading size="M">Theming</Heading>
<Body>Customize how Budibase looks and feels.</Body>
@ -30,7 +30,7 @@
}
.field {
display: grid;
grid-template-columns: 33% 1fr;
grid-template-columns: 120px 1fr;
align-items: center;
}
</style>

View file

@ -7,6 +7,7 @@
Button,
Divider,
notifications,
Label,
} from "@budibase/bbui"
import api from "builderStore/api"
import { auth } from "stores/portal"
@ -47,36 +48,25 @@
</script>
{#if $auth.isAdmin}
<Layout>
<Layout noPadding>
<Layout gap="XS" noPadding>
<Heading size="M">Update</Heading>
<Heading size="M">Updates</Heading>
<Body>
Keep your budibase installation up to date to take advantage of the
latest features, security updates and much more.
</Body>
</Layout>
<Divider size="S" />
<div class="fields">
<div class="field">
{#if version}
Current Version: {version}
{/if}
</div>
<div class="field">
<Button cta on:click={updateBudibase}>Check For Updates</Button>
{#if version}
<div>
<Label size="L">Current version</Label>
<Heading size="XS">
{version}
</Heading>
</div>
{/if}
<div>
<Button cta on:click={updateBudibase}>Check for updates</Button>
</div>
</Layout>
{/if}
<style>
.fields {
display: grid;
grid-gap: var(--spacing-m);
}
.field {
display: grid;
grid-template-columns: 33% 1fr;
align-items: center;
}
</style>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff