Misc fixes and improvements (#9503)
* Rewrite position dropdown helper to properly work as a svelte action, improve performance and fix bugs * Update action button styles * Update spacing on some onboarding pages and update background gradient * Prevent special characters in first app name * Fix type in onboarding tour * Default first app name and url to having a value * Update text in first app onboarding file upload * Fix double mounting of apps page causing issues and templates error * Fix null app ID when creating your first app using data upload * Fix app deletion not causing app list to be refreshed
This commit is contained in:
parent
6ec098efdd
commit
659cfd2492
|
@ -86,7 +86,7 @@
|
|||
}
|
||||
.is-selected:not(.spectrum-ActionButton--emphasized):not(.spectrum-ActionButton--quiet) {
|
||||
background: var(--spectrum-global-color-gray-300);
|
||||
border-color: var(--spectrum-global-color-gray-700);
|
||||
border-color: var(--spectrum-global-color-gray-500);
|
||||
}
|
||||
.noPadding {
|
||||
padding: 0;
|
||||
|
|
|
@ -1,11 +1,21 @@
|
|||
export default function positionDropdown(
|
||||
element,
|
||||
{ anchor, align, maxWidth, useAnchorWidth, offset = 5 }
|
||||
) {
|
||||
const update = () => {
|
||||
export default function positionDropdown(element, opts) {
|
||||
let resizeObserver
|
||||
let latestOpts = opts
|
||||
|
||||
// We need a static reference to this function so that we can properly
|
||||
// clean up the scroll listener.
|
||||
const scrollUpdate = () => {
|
||||
updatePosition(latestOpts)
|
||||
}
|
||||
|
||||
// Updates the position of the dropdown
|
||||
const updatePosition = opts => {
|
||||
const { anchor, align, maxWidth, useAnchorWidth, offset = 5 } = opts
|
||||
if (!anchor) {
|
||||
return
|
||||
}
|
||||
|
||||
// Compute bounds
|
||||
const anchorBounds = anchor.getBoundingClientRect()
|
||||
const elementBounds = element.getBoundingClientRect()
|
||||
let styles = {
|
||||
|
@ -51,26 +61,47 @@ export default function positionDropdown(
|
|||
})
|
||||
}
|
||||
|
||||
// The actual svelte action callback which creates observers on the relevant
|
||||
// DOM elements
|
||||
const update = newOpts => {
|
||||
latestOpts = newOpts
|
||||
|
||||
// Cleanup old state
|
||||
if (resizeObserver) {
|
||||
resizeObserver.disconnect()
|
||||
}
|
||||
|
||||
// Do nothing if no anchor
|
||||
const { anchor } = newOpts
|
||||
if (!anchor) {
|
||||
return
|
||||
}
|
||||
|
||||
// Observe both anchor and element and resize the popover as appropriate
|
||||
resizeObserver = new ResizeObserver(() => updatePosition(newOpts))
|
||||
resizeObserver.observe(anchor)
|
||||
resizeObserver.observe(element)
|
||||
resizeObserver.observe(document.body)
|
||||
}
|
||||
|
||||
// Apply initial styles which don't need to change
|
||||
element.style.position = "absolute"
|
||||
element.style.zIndex = "9999"
|
||||
|
||||
// Observe both anchor and element and resize the popover as appropriate
|
||||
const resizeObserver = new ResizeObserver(entries => {
|
||||
entries.forEach(update)
|
||||
})
|
||||
if (anchor) {
|
||||
resizeObserver.observe(anchor)
|
||||
}
|
||||
resizeObserver.observe(element)
|
||||
resizeObserver.observe(document.body)
|
||||
// Set up a scroll listener
|
||||
document.addEventListener("scroll", scrollUpdate, true)
|
||||
|
||||
document.addEventListener("scroll", update, true)
|
||||
// Perform initial update
|
||||
update(opts)
|
||||
|
||||
return {
|
||||
update,
|
||||
destroy() {
|
||||
resizeObserver.disconnect()
|
||||
document.removeEventListener("scroll", update, true)
|
||||
// Cleanup
|
||||
if (resizeObserver) {
|
||||
resizeObserver.disconnect()
|
||||
}
|
||||
document.removeEventListener("scroll", scrollUpdate, true)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,30 +57,28 @@
|
|||
</script>
|
||||
|
||||
{#if open}
|
||||
{#key anchor}
|
||||
<Portal {target}>
|
||||
<div
|
||||
tabindex="0"
|
||||
use:positionDropdown={{
|
||||
anchor,
|
||||
align,
|
||||
maxWidth,
|
||||
useAnchorWidth,
|
||||
offset,
|
||||
}}
|
||||
use:clickOutside={{
|
||||
callback: dismissible ? handleOutsideClick : () => {},
|
||||
anchor,
|
||||
}}
|
||||
on:keydown={handleEscape}
|
||||
class="spectrum-Popover is-open"
|
||||
role="presentation"
|
||||
transition:fly|local={{ y: -20, duration: 200 }}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</Portal>
|
||||
{/key}
|
||||
<Portal {target}>
|
||||
<div
|
||||
tabindex="0"
|
||||
use:positionDropdown={{
|
||||
anchor,
|
||||
align,
|
||||
maxWidth,
|
||||
useAnchorWidth,
|
||||
offset,
|
||||
}}
|
||||
use:clickOutside={{
|
||||
callback: dismissible ? handleOutsideClick : () => {},
|
||||
anchor,
|
||||
}}
|
||||
on:keydown={handleEscape}
|
||||
class="spectrum-Popover is-open"
|
||||
role="presentation"
|
||||
transition:fly|local={{ y: -20, duration: 200 }}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</Portal>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div>
|
||||
In this section you can mange the data for your app:
|
||||
In this section you can manage the data for your app:
|
||||
<ul class="feature-list">
|
||||
<li>Connect data sources</li>
|
||||
<li>Edit data</li>
|
||||
|
|
|
@ -138,7 +138,6 @@
|
|||
}
|
||||
|
||||
$goto(`/builder/app/${createdApp.instance._id}`)
|
||||
// apps.load()
|
||||
} catch (error) {
|
||||
creating = false
|
||||
console.error(error)
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
<span class="back-chev" on:click={() => $goto("../")}>
|
||||
<Icon name="ChevronLeft" size="XL" />
|
||||
</span>
|
||||
Forgotten your password?
|
||||
Forgot your password?
|
||||
</div>
|
||||
</Heading>
|
||||
</span>
|
||||
|
@ -83,7 +83,12 @@
|
|||
</FancyForm>
|
||||
</Layout>
|
||||
<div>
|
||||
<Button disabled={!email || error || submitted} cta on:click={forgot}>
|
||||
<Button
|
||||
size="L"
|
||||
disabled={!email || error || submitted}
|
||||
cta
|
||||
on:click={forgot}
|
||||
>
|
||||
Reset password
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -92,7 +97,7 @@
|
|||
|
||||
<style>
|
||||
img {
|
||||
width: 48px;
|
||||
width: 46px;
|
||||
}
|
||||
.back-chev {
|
||||
display: inline-block;
|
||||
|
@ -102,5 +107,6 @@
|
|||
.heading-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
<svelte:window on:keydown={handleKeydown} />
|
||||
|
||||
<TestimonialPage>
|
||||
<Layout gap="S" noPadding>
|
||||
<Layout gap="L" noPadding>
|
||||
<Layout justifyItems="center" noPadding>
|
||||
{#if loaded}
|
||||
<img alt="logo" src={$organisation.logoUrl || Logo} />
|
||||
|
@ -124,14 +124,19 @@
|
|||
</FancyForm>
|
||||
</Layout>
|
||||
<Layout gap="XS" noPadding justifyItems="center">
|
||||
<Button cta disabled={Object.keys(errors).length > 0} on:click={login}>
|
||||
<Button
|
||||
size="L"
|
||||
cta
|
||||
disabled={Object.keys(errors).length > 0}
|
||||
on:click={login}
|
||||
>
|
||||
Log in to {company}
|
||||
</Button>
|
||||
</Layout>
|
||||
<Layout gap="XS" noPadding justifyItems="center">
|
||||
<div class="user-actions">
|
||||
<ActionButton quiet on:click={() => $goto("./forgot")}>
|
||||
Forgot password
|
||||
<ActionButton size="L" quiet on:click={() => $goto("./forgot")}>
|
||||
Forgot password?
|
||||
</ActionButton>
|
||||
</div>
|
||||
</Layout>
|
||||
|
|
|
@ -68,7 +68,7 @@
|
|||
</script>
|
||||
|
||||
<TestimonialPage>
|
||||
<Layout gap="S" noPadding>
|
||||
<Layout gap="M" noPadding>
|
||||
<img alt="logo" src={$organisation.logoUrl || Logo} />
|
||||
<Layout gap="XS" noPadding>
|
||||
<Heading size="M">Join {company}</Heading>
|
||||
|
@ -175,6 +175,7 @@
|
|||
</Layout>
|
||||
<div>
|
||||
<Button
|
||||
size="L"
|
||||
disabled={Object.keys(errors).length > 0 || onboarding}
|
||||
cta
|
||||
on:click={acceptInvite}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
let activeTab = "Apps"
|
||||
|
||||
$: $url(), updateActiveTab($menu)
|
||||
$: fullScreen = !$apps?.length
|
||||
$: fullscreen = !$apps.length
|
||||
|
||||
const updateActiveTab = menu => {
|
||||
for (let entry of menu) {
|
||||
|
@ -37,7 +37,8 @@
|
|||
$redirect("../")
|
||||
} else {
|
||||
try {
|
||||
await organisation.init()
|
||||
// We need to load apps to know if we need to show onboarding fullscreen
|
||||
await Promise.all([apps.load(), organisation.init()])
|
||||
} catch (error) {
|
||||
notifications.error("Error getting org config")
|
||||
}
|
||||
|
@ -47,37 +48,39 @@
|
|||
})
|
||||
</script>
|
||||
|
||||
{#if fullScreen}
|
||||
<slot />
|
||||
{:else if $auth.user && loaded}
|
||||
<HelpMenu />
|
||||
<div class="container">
|
||||
<div class="nav">
|
||||
<div class="branding">
|
||||
<Logo />
|
||||
{#if $auth.user && loaded}
|
||||
{#if fullscreen}
|
||||
<slot />
|
||||
{:else}
|
||||
<HelpMenu />
|
||||
<div class="container">
|
||||
<div class="nav">
|
||||
<div class="branding">
|
||||
<Logo />
|
||||
</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="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 class="main">
|
||||
<slot />
|
||||
</div>
|
||||
<MobileMenu visible={mobileMenuVisible} on:close={hideMobileMenu} />
|
||||
</div>
|
||||
<div class="main">
|
||||
<slot />
|
||||
</div>
|
||||
<MobileMenu visible={mobileMenuVisible} on:close={hideMobileMenu} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
|
|
|
@ -10,13 +10,11 @@
|
|||
onMount(async () => {
|
||||
try {
|
||||
// Always load latest
|
||||
await apps.load()
|
||||
await licensing.init()
|
||||
await templates.load()
|
||||
|
||||
if ($licensing.groupsEnabled) {
|
||||
await groups.actions.init()
|
||||
}
|
||||
await Promise.all([
|
||||
licensing.init(),
|
||||
templates.load(),
|
||||
groups.actions.init(),
|
||||
])
|
||||
|
||||
if ($templates?.length === 0) {
|
||||
notifications.error("There was a problem loading quick start templates")
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
export let name = ""
|
||||
export let url = ""
|
||||
export let onNext = () => {}
|
||||
|
||||
const nameRegex = /^[a-zA-Z0-9\s]*$/
|
||||
let nameError = null
|
||||
let urlError = null
|
||||
|
||||
|
@ -14,6 +16,9 @@
|
|||
if (name.length < 1) {
|
||||
return "Name must be provided"
|
||||
}
|
||||
if (!nameRegex.test(name)) {
|
||||
return "No special characters are allowed"
|
||||
}
|
||||
}
|
||||
|
||||
const validateUrl = url => {
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
import createFromScratchScreen from "builderStore/store/screenTemplates/createFromScratchScreen"
|
||||
import { Roles } from "constants/backend"
|
||||
|
||||
let name = ""
|
||||
let url = ""
|
||||
let name = "My first app"
|
||||
let url = "my-first-app"
|
||||
let stage = "name"
|
||||
let appId = null
|
||||
|
||||
|
@ -57,7 +57,7 @@
|
|||
defaultScreenTemplate.routing.roldId = Roles.BASIC
|
||||
await store.actions.screens.save(defaultScreenTemplate)
|
||||
|
||||
return createdApp.instance._id
|
||||
appId = createdApp.instance._id
|
||||
}
|
||||
|
||||
const getIntegrations = async () => {
|
||||
|
@ -79,14 +79,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
const goToApp = appId => {
|
||||
const goToApp = () => {
|
||||
$goto(`/builder/app/${appId}`)
|
||||
notifications.success(`App created successfully`)
|
||||
}
|
||||
|
||||
const handleCreateApp = async ({ datasourceConfig, useSampleData }) => {
|
||||
try {
|
||||
appId = await createApp(useSampleData)
|
||||
await createApp(useSampleData)
|
||||
|
||||
if (datasourceConfig) {
|
||||
await saveDatasource({
|
||||
|
@ -99,7 +99,7 @@
|
|||
})
|
||||
}
|
||||
|
||||
goToApp(appId)
|
||||
goToApp()
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
notifications.error("There was a problem creating your app")
|
||||
|
@ -111,7 +111,7 @@
|
|||
<CreateTableModal
|
||||
name="Your Data"
|
||||
beforeSave={createApp}
|
||||
afterSave={() => goToApp(appId)}
|
||||
afterSave={goToApp}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
|
@ -142,7 +142,7 @@
|
|||
<div class="dataButtonIcon">
|
||||
<FontAwesomeIcon name="fa-solid fa-file-arrow-up" />
|
||||
</div>
|
||||
Upload file
|
||||
Upload data (CSV or JSON)
|
||||
</div>
|
||||
</FancyButton>
|
||||
</div>
|
||||
|
|
|
@ -100,8 +100,9 @@
|
|||
const deleteApp = async () => {
|
||||
try {
|
||||
await API.deleteApp(app?.devId)
|
||||
apps.load()
|
||||
notifications.success("App deleted successfully")
|
||||
$goto("../")
|
||||
$goto("../../")
|
||||
} catch (err) {
|
||||
notifications.error("Error deleting app")
|
||||
}
|
||||
|
|
|
@ -1,17 +1,12 @@
|
|||
<script>
|
||||
import { apps, groups, licensing } from "stores/portal"
|
||||
import { groups } from "stores/portal"
|
||||
import { onMount } from "svelte"
|
||||
|
||||
let loaded = !!$apps?.length
|
||||
let loaded = false
|
||||
|
||||
onMount(async () => {
|
||||
if (!loaded) {
|
||||
await apps.load()
|
||||
if ($licensing.groupsEnabled) {
|
||||
await groups.actions.init()
|
||||
}
|
||||
loaded = true
|
||||
}
|
||||
await groups.actions.init()
|
||||
loaded = true
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
@ -146,7 +146,7 @@
|
|||
|
||||
onMount(async () => {
|
||||
try {
|
||||
await Promise.all([groups.actions.init(), apps.load(), roles.fetch()])
|
||||
await Promise.all([groups.actions.init(), roles.fetch()])
|
||||
loaded = true
|
||||
} catch (error) {
|
||||
notifications.error("Error fetching user group data")
|
||||
|
|
|
@ -80,9 +80,7 @@
|
|||
try {
|
||||
// always load latest
|
||||
await licensing.init()
|
||||
if ($licensing.groupsEnabled) {
|
||||
await groups.actions.init()
|
||||
}
|
||||
await groups.actions.init()
|
||||
} catch (error) {
|
||||
notifications.error("Error getting user groups")
|
||||
}
|
||||
|
|
|
@ -215,12 +215,7 @@
|
|||
|
||||
onMount(async () => {
|
||||
try {
|
||||
await Promise.all([
|
||||
fetchUser(),
|
||||
groups.actions.init(),
|
||||
apps.load(),
|
||||
roles.fetch(),
|
||||
])
|
||||
await Promise.all([fetchUser(), groups.actions.init(), roles.fetch()])
|
||||
loaded = true
|
||||
} catch (error) {
|
||||
notifications.error("Error getting user groups")
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 2.3 MiB |
Loading…
Reference in New Issue