1
0
Fork 0
mirror of synced 2024-09-08 21:51:58 +12:00

Merge branch 'cheeks-lab-day-portal-redesign' of github.com:Budibase/budibase into feature/environment-variables

This commit is contained in:
mike12345567 2023-01-11 18:04:33 +00:00
commit d5ccbbe4f0
12 changed files with 419 additions and 257 deletions

View file

@ -42,6 +42,7 @@
justify-content: flex-start;
align-items: stretch;
flex: 1 1 auto;
overflow-x: hidden;
}
.main {
overflow: auto;
@ -57,10 +58,10 @@
padding: 50px;
z-index: 1;
}
.wide {
.content.wide {
max-width: none;
}
.narrow {
.content.narrow {
max-width: 840px;
}
#side-panel {
@ -71,6 +72,7 @@
background: var(--background);
border-left: var(--border-light);
width: 320px;
max-width: calc(100vw - 48px - 48px);
overflow: auto;
overflow-x: hidden;
transform: translateX(100%);
@ -81,4 +83,13 @@
#side-panel.visible {
transform: translateX(0);
}
@media (max-width: 640px) {
.content {
padding: 24px;
max-width: calc(100vw - 48px) !important;
width: calc(100vw - 48px) !important;
overflow: auto;
}
}
</style>

View file

@ -63,7 +63,8 @@
name="LockClosed"
hoverable
size={buttonSize}
on:click={() => {
on:click={e => {
e.stopPropagation()
appLockModal.show()
}}
/>

View file

@ -1,9 +1,10 @@
<script>
export let narrow = false
export let showMobileNav = false
</script>
<div class="content">
<div class="side-nav">
<div class="side-nav" class:show-mobile={showMobileNav}>
<slot name="side-nav" />
</div>
<div class="main" class:narrow>
@ -28,4 +29,18 @@
.main.narrow {
max-width: 600px;
}
@media (max-width: 640px) {
.content {
flex-direction: column;
}
.side-nav:not(.show-mobile) {
display: none;
}
.side-nav.show-mobile :global(.side-nav) {
border-bottom: var(--border-light);
margin: 0 -24px;
padding: 0 24px 32px 24px;
}
}
</style>

View file

@ -2,9 +2,10 @@
import { Heading } from "@budibase/bbui"
export let title
export let wrap = true
</script>
<div class="header">
<div class="header" class:wrap>
<slot name="icon" />
<Heading size="L">{title}</Heading>
<div class="buttons">
@ -20,10 +21,19 @@
align-items: center;
gap: var(--spacing-xl);
}
.header.wrap {
flex-wrap: wrap;
}
.header :global(.spectrum-Heading) {
flex: 1 1 auto;
margin-top: -2px;
}
.header:not(.wrap) :global(.spectrum-Heading) {
overflow: hidden;
width: 0;
white-space: nowrap;
text-overflow: ellipsis;
}
.buttons {
display: flex;
flex-direction: row;
@ -34,4 +44,9 @@
.buttons :global(> div) {
display: contents;
}
@media (max-width: 640px) {
.wrap .buttons {
margin-bottom: var(--spacing-m);
}
}
</style>

View file

@ -8,41 +8,39 @@
export let appOverview
</script>
<div class="app-row">
<div class="header">
<div class="title" data-cy={`${app.devId}`}>
<div class="app-icon">
<Icon
size="L"
name={app.icon?.name || "Apps"}
color={app.icon?.color}
/>
</div>
<div class="name" data-cy="app-name-link" on:click={() => editApp(app)}>
<Heading size="S">
{app.name}
</Heading>
</div>
<div class="app-row" on:click={() => editApp(app)}>
<div class="title" data-cy={`${app.devId}`}>
<div class="app-icon">
<Icon size="L" name={app.icon?.name || "Apps"} color={app.icon?.color} />
</div>
<div class="name" data-cy="app-name-link">
<Heading size="S">
{app.name}
</Heading>
</div>
</div>
<div class="updated">
{#if app.updatedAt}
{processStringSync("Updated {{ duration time 'millisecond' }} ago", {
time: new Date().getTime() - new Date(app.updatedAt).getTime(),
})}
{:else}
Never updated
{/if}
</div>
<div class="updated">
{#if app.updatedAt}
{processStringSync("Updated {{ duration time 'millisecond' }} ago", {
time: new Date().getTime() - new Date(app.updatedAt).getTime(),
})}
{:else}
Never updated
{/if}
</div>
<div class="title app-status" class:deployed={app.deployed}>
<Icon size="L" name={app.deployed ? "GlobeCheck" : "GlobeStrike"} />
<Body size="S">{`${window.origin}/app${app.url}`}</Body>
<Body size="S">{app.deployed ? "Published" : "Unpublished"}</Body>
</div>
<div data-cy={`row_actions_${app.appId}`}>
<div class="app-row-actions">
<AppLockModal {app} buttonSize="M" />
<Button size="S" secondary on:click={() => appOverview(app)}>
Manage
</Button>
<Button
size="S"
primary
@ -51,10 +49,6 @@
>
Edit
</Button>
<Button size="S" secondary on:click={() => appOverview(app)}>
Manage
</Button>
<AppLockModal {app} buttonSize="M" />
</div>
</div>
</div>
@ -64,24 +58,29 @@
background: var(--background);
padding: 24px 32px;
border-radius: 8px;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: var(--spacing-m);
}
.header {
display: flex;
flex-direction: row;
justify-content: space-between;
display: grid;
grid-template-columns: 35% 25% 15% auto;
align-items: center;
gap: var(--spacing-m);
transition: border 130ms ease-out;
border: 1px solid transparent;
}
.app-row:hover {
cursor: pointer;
border-color: var(--spectrum-global-color-gray-300);
}
.updated {
color: var(--spectrum-global-color-gray-700);
}
.title,
.name {
flex: 1 1 auto;
}
.name {
width: 0;
}
.title,
.app-status {
display: flex;
@ -106,9 +105,8 @@
gap: var(--spacing-m);
display: flex;
flex-direction: row;
justify-content: flex-start;
justify-content: flex-end;
align-items: center;
margin-top: var(--spacing-m);
}
.name {
@ -120,15 +118,26 @@
white-space: nowrap;
text-overflow: ellipsis;
}
.title :global(h1:hover) {
color: var(--spectrum-global-color-blue-600);
cursor: pointer;
transition: color 130ms ease;
}
@media (max-width: 1000px) {
.app-row {
grid-template-columns: 45% 30% auto;
}
.updated {
display: none;
}
}
@media (max-width: 800px) {
.app-row {
grid-template-columns: 1fr auto;
}
.app-status {
display: none;
}
}
@media (max-width: 640px) {
.desktop {
display: none !important;
.app-row {
padding: 20px;
}
}
</style>

View file

@ -0,0 +1,13 @@
<script>
import Logo from "assets/bb-emblem.svg"
import { goto } from "@roxi/routify"
</script>
<img src={Logo} alt="Budibase Logo" on:click={() => $goto("./apps")} />
<style>
img {
width: 30px;
height: 30px;
}
</style>

View file

@ -0,0 +1,79 @@
<script>
import { Layout } from "@budibase/bbui"
import { SideNav, SideNavItem } from "components/portal/page"
import { createEventDispatcher } from "svelte"
import { isActive } from "@roxi/routify"
import UpgradeButton from "./UpgradeButton.svelte"
import { fade } from "svelte/transition"
import Logo from "./Logo.svelte"
export let visible = false
export let menu
const dispatch = createEventDispatcher()
const close = () => dispatch("close")
</script>
{#if visible}
<div
class="mobile-nav-underlay"
transition:fade={{ duration: 130 }}
on:click={close}
/>
{/if}
<div class="mobile-nav" class:visible>
<Layout noPadding gap="M">
<div on:click={close}>
<Logo />
</div>
<SideNav>
{#each menu as { title, href }}
<SideNavItem
text={title}
url={href}
active={$isActive(href)}
on:click={close}
/>
{/each}
</SideNav>
<div>
<UpgradeButton />
</div>
</Layout>
</div>
<style>
.mobile-nav {
display: none;
}
@media (max-width: 640px) {
.mobile-nav-underlay {
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
background: rgba(0, 0, 0, 0.5);
}
.mobile-nav {
transform: translateX(-100%);
display: block;
position: absolute;
top: 0;
left: 0;
padding: 16px 24px;
height: 100%;
width: 240px;
background: var(--background);
z-index: 2;
transition: transform 130ms ease-out;
}
.mobile-nav.visible {
transform: translateX(0);
}
}
</style>

View file

@ -0,0 +1,20 @@
<script>
import { Button } from "@budibase/bbui"
import { goto } from "@roxi/routify"
import { auth, admin } from "stores/portal"
</script>
{#if $admin.cloud && $auth?.user?.accountPortalAccess}
<Button
cta
on:click={() => {
$goto($admin.accountPortalUrl + "/portal/upgrade")
}}
>
Upgrade
</Button>
{:else if !$admin.cloud && $auth.isAdmin}
<Button cta on:click={() => $goto("/builder/portal/account/upgrade")}>
Upgrade
</Button>
{/if}

View file

@ -0,0 +1,78 @@
<script>
import { auth } from "stores/portal"
import { ActionMenu, Avatar, MenuItem, Icon, Modal } from "@budibase/bbui"
import { goto } from "@roxi/routify"
import ProfileModal from "components/settings/ProfileModal.svelte"
import ChangePasswordModal from "components/settings/ChangePasswordModal.svelte"
import ThemeModal from "components/settings/ThemeModal.svelte"
import APIKeyModal from "components/settings/APIKeyModal.svelte"
let themeModal
let profileModal
let updatePasswordModal
let apiKeyModal
const logout = async () => {
try {
await auth.logout()
} catch (error) {
// Swallow error and do nothing
}
}
</script>
<ActionMenu align="right" dataCy="user-menu">
<div slot="control" class="user-dropdown">
<Avatar size="L" initials={$auth.initials} url={$auth.user.pictureUrl} />
<Icon size="XL" name="ChevronDown" />
</div>
<MenuItem icon="Moon" on:click={() => themeModal.show()} dataCy="theme">
Theme
</MenuItem>
<MenuItem
icon="UserEdit"
on:click={() => profileModal.show()}
dataCy="user-info"
>
My profile
</MenuItem>
<MenuItem icon="LockClosed" on:click={() => updatePasswordModal.show()}>
Update password
</MenuItem>
<MenuItem icon="Key" on:click={() => apiKeyModal.show()} dataCy="api-key">
View API key
</MenuItem>
<MenuItem icon="UserDeveloper" on:click={() => $goto("../apps")}>
Close developer mode
</MenuItem>
<MenuItem dataCy="user-logout" icon="LogOut" on:click={logout}>
Log out
</MenuItem>
</ActionMenu>
<Modal bind:this={themeModal}>
<ThemeModal />
</Modal>
<Modal bind:this={profileModal}>
<ProfileModal />
</Modal>
<Modal bind:this={updatePasswordModal}>
<ChangePasswordModal />
</Modal>
<Modal bind:this={apiKeyModal}>
<APIKeyModal />
</Modal>
<style>
.user-dropdown {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
gap: var(--spacing-s);
}
.user-dropdown:hover {
cursor: pointer;
filter: brightness(110%);
}
</style>

View file

@ -1,30 +1,15 @@
<script>
import { isActive, redirect, goto, url } from "@roxi/routify"
import {
Icon,
Avatar,
ActionMenu,
MenuItem,
Modal,
notifications,
Tabs,
Tab,
Button,
} from "@budibase/bbui"
import { Icon, notifications, Tabs, Tab } from "@budibase/bbui"
import { organisation, auth, admin as adminStore } from "stores/portal"
import { onMount } from "svelte"
import ProfileModal from "components/settings/ProfileModal.svelte"
import ChangePasswordModal from "components/settings/ChangePasswordModal.svelte"
import ThemeModal from "components/settings/ThemeModal.svelte"
import APIKeyModal from "components/settings/APIKeyModal.svelte"
import Logo from "assets/bb-emblem.svg"
import { isEnabled, TENANT_FEATURE_FLAGS } from "helpers/featureFlags"
import UpgradeButton from "./_components/UpgradeButton.svelte"
import MobileMenu from "./_components/MobileMenu.svelte"
import Logo from "./_components/Logo.svelte"
import UserDropdown from "./_components/UserDropdown.svelte"
let loaded = false
let themeModal
let profileModal
let updatePasswordModal
let apiKeyModal
let mobileMenuVisible = false
let activeTab = "Apps"
@ -95,14 +80,6 @@
return menu
}
const logout = async () => {
try {
await auth.logout()
} catch (error) {
// Swallow error and do nothing
}
}
const showMobileMenu = () => (mobileMenuVisible = true)
const hideMobileMenu = () => (mobileMenuVisible = false)
@ -126,92 +103,31 @@
{#if $auth.user && loaded}
<div class="container">
<div class="nav">
<div class="branding" on:click={() => $goto("./apps")}>
<img src={Logo} alt="Logotype" />
<div class="branding">
<Logo />
</div>
<Tabs selected={activeTab}>
{#each menu as { title, href }}
<Tab {title} on:click={() => $goto(href)} />
{/each}
</Tabs>
<div class="toolbar">
<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>
{#if !$adminStore.cloud && $auth.isAdmin}
<Button cta on:click={() => $goto("/builder/portal/account/upgrade")}>
Upgrade
</Button>
{/if}
<div class="user-dropdown">
<ActionMenu align="right" dataCy="user-menu">
<div slot="control" class="avatar">
<Avatar
size="L"
initials={$auth.initials}
url={$auth.user.pictureUrl}
/>
<Icon size="XL" name="ChevronDown" />
</div>
<MenuItem
icon="Moon"
on:click={() => themeModal.show()}
dataCy="theme"
>
Theme
</MenuItem>
<MenuItem
icon="UserEdit"
on:click={() => profileModal.show()}
dataCy="user-info"
>
My profile
</MenuItem>
<MenuItem
icon="LockClosed"
on:click={() => updatePasswordModal.show()}
>
Update password
</MenuItem>
<MenuItem
icon="Key"
on:click={() => apiKeyModal.show()}
dataCy="api-key"
>
View API key
</MenuItem>
<MenuItem icon="UserDeveloper" on:click={() => $goto("../apps")}>
Close developer mode
</MenuItem>
<MenuItem dataCy="user-logout" icon="LogOut" on:click={logout}>
Log out
</MenuItem>
</ActionMenu>
</div>
<div class="desktop">
<Tabs selected={activeTab}>
{#each menu as { title, href }}
<Tab {title} on:click={() => $goto(href)} />
{/each}
</Tabs>
</div>
<div class="mobile">
<Icon hoverable name="ShowMenu" on:click={showMobileMenu} />
</div>
<div class="desktop">
<UpgradeButton />
</div>
<div class="dropdown">
<UserDropdown />
</div>
</div>
<div class="main">
<slot />
</div>
<MobileMenu visible={mobileMenuVisible} {menu} on:close={hideMobileMenu} />
</div>
<Modal bind:this={themeModal}>
<ThemeModal />
</Modal>
<Modal bind:this={profileModal}>
<ProfileModal />
</Modal>
<Modal bind:this={updatePasswordModal}>
<ChangePasswordModal />
</Modal>
<Modal bind:this={apiKeyModal}>
<APIKeyModal />
</Modal>
{/if}
<style>
@ -227,54 +143,30 @@
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: stretch;
align-items: center;
border-bottom: var(--border-light);
padding: 0 20px;
padding: 0 24px;
gap: 24px;
position: relative;
}
/* Customise tabs appearance*/
.nav :global(.spectrum-Tabs) {
margin-bottom: -2px;
padding: 7px 0;
flex: 1 1 auto;
}
.nav :global(.spectrum-Tabs-content) {
display: none;
}
.nav :global(.spectrum-Tabs-itemLabel) {
font-weight: 600;
}
.branding {
display: grid;
place-items: center;
}
.avatar {
display: grid;
grid-template-columns: auto auto;
place-items: center;
grid-gap: var(--spacing-s);
}
.avatar:hover {
cursor: pointer;
filter: brightness(110%);
}
.toolbar {
flex: 1 1 auto;
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
gap: 24px;
}
.mobile-toggle,
.mobile-logo {
display: none;
}
.user-dropdown {
display: flex;
flex-direction: row;
justify-content: flex-end;
}
img {
width: 30px;
height: 30px;
}
.main {
flex: 1 1 auto;
display: flex;
@ -283,38 +175,29 @@
align-items: stretch;
overflow: auto;
}
.mobile {
display: none;
}
.desktop {
display: contents;
}
@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);
.mobile {
display: contents;
}
.desktop {
display: none;
}
.nav {
flex: 0 0 52px;
justify-content: space-between;
}
.branding {
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: 0 1 0;
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(-50%);
}
}
</style>

View file

@ -49,8 +49,6 @@
)
: true)
)
$: lockedApps = filteredApps.filter(app => app?.lockedYou || app?.lockedOther)
$: unlocked = lockedApps?.length === 0
$: automationErrors = getAutomationErrors(enrichedApps)
const enrichApps = (apps, user, sortBy) => {
@ -309,7 +307,7 @@
{/if}
</div>
<div class="appTable" class:unlocked>
<div class="app-table">
{#each filteredApps as app (app.appId)}
<AppRow {app} {editApp} {appOverview} />
{/each}
@ -356,11 +354,6 @@
gap: var(--spacing-xl);
flex-wrap: wrap;
}
@media (max-width: 1000px) {
.img-logo {
display: none;
}
}
.app-actions {
display: flex;
flex-direction: row;
@ -369,19 +362,15 @@
gap: var(--spacing-xl);
}
.appTable {
.app-table {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: 24px;
gap: var(--spacing-xl);
overflow: hidden;
}
@media (max-width: 640px) {
.appTable {
grid-template-columns: 1fr auto !important;
}
}
.empty-wrapper {
flex: 1 1 auto;
height: 100%;
@ -394,4 +383,23 @@
width: 160px;
height: 160px;
}
@media (max-width: 1000px) {
.img-logo {
display: none;
}
}
@media (max-width: 640px) {
.app-actions {
margin-top: var(--spacing-xl);
margin-bottom: calc(-1 * var(--spacing-m));
}
/* Hide download apps icon */
.app-actions :global(> .spectrum-Icon) {
display: none;
}
.app-actions > :global(*) {
flex: 0 0 50%;
}
}
</style>

View file

@ -120,7 +120,7 @@
<Breadcrumb url={$url("../")} text="Apps" />
<Breadcrumb text={app?.name} />
</Breadcrumbs>
<Header title={app?.name}>
<Header title={app?.name} wrap={false}>
<div slot="icon">
<EditableIcon
{app}
@ -132,28 +132,46 @@
</div>
<div slot="buttons">
<AppLockModal {app} />
<Button
size="M"
quiet
secondary
disabled={!isPublished}
on:click={viewApp}
dataCy="view-app"
>
View
</Button>
<Button
size="M"
cta
disabled={appLocked && !lockedByYou}
on:click={editApp}
>
Edit
</Button>
<span class="desktop">
<Button
size="M"
quiet
secondary
disabled={!isPublished}
on:click={viewApp}
dataCy="view-app"
>
View
</Button>
</span>
<span class="desktop">
<Button
size="M"
cta
disabled={appLocked && !lockedByYou}
on:click={editApp}
>
Edit
</Button>
</span>
<ActionMenu align="right" dataCy="app-overview-menu-popover">
<span slot="control" class="app-overview-actions-icon">
<Icon hoverable name="More" />
</span>
<span class="mobile">
<MenuItem icon="Globe" disabled={!isPublished} on:click={viewApp}>
View
</MenuItem>
</span>
<span class="mobile">
<MenuItem
icon="Edit"
disabled={appLocked && !lockedByYou}
on:click={editApp}
>
Edit
</MenuItem>
</span>
<MenuItem
on:click={() => exportApp({ published: false })}
icon="DownloadFromCloud"
@ -177,7 +195,7 @@
</ActionMenu>
</div>
</Header>
<Content>
<Content showMobileNav>
<SideNav slot="side-nav">
<SideNavItem
text="Overview"
@ -237,3 +255,15 @@
/>
</ConfirmDialog>
{/key}
<style>
.desktop {
display: contents;
}
@media (max-width: 640px) {
.desktop {
display: none;
}
}
</style>