Add non-gallery option to dropzeon and update create app modal

This commit is contained in:
Andrew Kingston 2021-05-10 11:53:32 +01:00
parent f1b2fea3c0
commit df607e78eb
7 changed files with 202 additions and 201 deletions

View File

@ -15,6 +15,8 @@
export let fileSizeLimit = BYTES_IN_MB * 20 export let fileSizeLimit = BYTES_IN_MB * 20
export let processFiles = null export let processFiles = null
export let handleFileTooLarge = null export let handleFileTooLarge = null
export let gallery = true
export let error = null
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const imageExtensions = [ const imageExtensions = [
@ -52,6 +54,8 @@
const newValue = [...value, ...processedFiles] const newValue = [...value, ...processedFiles]
dispatch("change", newValue) dispatch("change", newValue)
selectedImageIdx = newValue.length - 1 selectedImageIdx = newValue.length - 1
} else {
dispatch("change", fileList)
} }
} }
@ -94,47 +98,68 @@
<div class="container"> <div class="container">
{#if selectedImage} {#if selectedImage}
<div class="gallery"> {#if gallery}
<div class="title"> <div class="gallery">
<div class="filename">{selectedImage.name}</div> <div class="title">
<div class="filesize"> <div class="filename">{selectedImage.name}</div>
{#if selectedImage.size <= BYTES_IN_MB} <div class="filesize">
{`${selectedImage.size / BYTES_IN_KB} KB`} {#if selectedImage.size <= BYTES_IN_MB}
{:else}{`${selectedImage.size / BYTES_IN_MB} MB`}{/if} {`${selectedImage.size / BYTES_IN_KB} KB`}
{:else}{`${selectedImage.size / BYTES_IN_MB} MB`}{/if}
</div>
{#if !disabled}
<div class="delete-button" on:click={removeFile}>
<Icon name="Close" />
</div>
{/if}
</div> </div>
{#if !disabled} {#if isImage}
<div class="delete-button" on:click={removeFile}> <img alt="preview" src={selectedImage.url} />
<Icon name="Close" /> {:else}
<div class="placeholder">
<div class="extension">{selectedImage.extension}</div>
<div>Preview not supported</div>
</div> </div>
{/if} {/if}
</div> <div
{#if isImage} class="nav left"
<img alt="preview" src={selectedImage.url} /> class:visible={selectedImageIdx > 0}
{:else} on:click={navigateLeft}
<div class="placeholder"> >
<div class="extension">{selectedImage.extension}</div> <Icon name="ChevronLeft" />
<div>Preview not supported</div>
</div> </div>
{/if} <div
<div class="nav right"
class="nav left" class:visible={selectedImageIdx < fileCount - 1}
class:visible={selectedImageIdx > 0} on:click={navigateRight}
on:click={navigateLeft} >
> <Icon name="ChevronRight" />
<Icon name="ChevronLeft" /> </div>
<div class="footer">File {selectedImageIdx + 1} of {fileCount}</div>
</div> </div>
<div {:else if value?.length}
class="nav right" {#each value as file}
class:visible={selectedImageIdx < fileCount - 1} <div class="gallery">
on:click={navigateRight} <div class="title">
> <div class="filename">{file.name}</div>
<Icon name="ChevronRight" /> <div class="filesize">
</div> {#if file.size <= BYTES_IN_MB}
<div class="footer">File {selectedImageIdx + 1} of {fileCount}</div> {`${file.size / BYTES_IN_KB} KB`}
</div> {:else}{`${file.size / BYTES_IN_MB} MB`}{/if}
</div>
{#if !disabled}
<div class="delete-button" on:click={removeFile}>
<Icon name="Close" />
</div>
{/if}
</div>
</div>
{/each}
{/if}
{/if} {/if}
<div <div
class="spectrum-Dropzone" class="spectrum-Dropzone"
class:is-invalid={!!error}
class:disabled class:disabled
role="region" role="region"
tabindex="0" tabindex="0"
@ -245,6 +270,9 @@
.spectrum-Dropzone { .spectrum-Dropzone {
user-select: none; user-select: none;
} }
.spectrum-Dropzone.is-invalid {
border-color: var(--spectrum-global-color-red-400);
}
input[type="file"] { input[type="file"] {
display: none; display: none;
} }
@ -276,7 +304,7 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;
align-items: stretch; align-items: center;
} }
.filename { .filename {
flex: 1 1 auto; flex: 1 1 auto;
@ -331,6 +359,7 @@
.delete-button { .delete-button {
transition: all 0.3s; transition: all 0.3s;
margin-left: 10px; margin-left: 10px;
display: flex;
} }
.delete-button i { .delete-button i {
font-size: 2em; font-size: 2em;

View File

@ -37,6 +37,7 @@
} }
focus = false focus = false
updateValue(event.target.value) updateValue(event.target.value)
dispatch("blur")
} }
const updateValueOnEnter = event => { const updateValueOnEnter = event => {

View File

@ -11,6 +11,7 @@
export let fileSizeLimit = undefined export let fileSizeLimit = undefined
export let processFiles = undefined export let processFiles = undefined
export let handleFileTooLarge = undefined export let handleFileTooLarge = undefined
export let gallery = true
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const onChange = e => { const onChange = e => {
@ -27,6 +28,7 @@
{fileSizeLimit} {fileSizeLimit}
{processFiles} {processFiles}
{handleFileTooLarge} {handleFileTooLarge}
{gallery}
on:change={onChange} on:change={onChange}
/> />
</Field> </Field>

View File

@ -30,5 +30,6 @@
on:change={onChange} on:change={onChange}
on:click on:click
on:input on:input
on:blur
/> />
</Field> </Field>

View File

@ -18,6 +18,7 @@
.wide { .wide {
max-width: none; max-width: none;
margin: 0; margin: 0;
padding: var(--spacing-xl) calc(var(--spacing-xl) * 2); padding: var(--spacing-xl) calc(var(--spacing-xl) * 2)
calc(var(--spacing-xl) * 2) calc(var(--spacing-xl) * 2);
} }
</style> </style>

View File

@ -1,58 +1,50 @@
<script> <script>
import { writable, get as svelteGet } from "svelte/store" import { writable, get as svelteGet } from "svelte/store"
import { notifications, Heading, Button } from "@budibase/bbui" import {
notifications,
Input,
ModalContent,
Dropzone,
Body,
Checkbox,
} from "@budibase/bbui"
import { store, automationStore, hostingStore } from "builderStore" import { store, automationStore, hostingStore } from "builderStore"
import { string, object } from "yup" import { string, mixed, object } from "yup"
import api, { get } from "builderStore/api" import api, { get } from "builderStore/api"
import Spinner from "components/common/Spinner.svelte"
import { Info, User } from "./Steps"
import Indicator from "./Indicator.svelte"
import { fade } from "svelte/transition"
import { post } from "builderStore/api" import { post } from "builderStore/api"
import analytics from "analytics" import analytics from "analytics"
import { onMount } from "svelte" import { onMount } from "svelte"
import Logo from "/assets/bb-logo.svg"
import { capitalise } from "../../helpers" import { capitalise } from "../../helpers"
import { goto } from "@roxi/routify" import { goto } from "@roxi/routify"
export let template export let template
const currentStep = writable(0) const values = writable({ name: null })
const values = writable({ roleId: "ADMIN" })
const errors = writable({}) const errors = writable({})
const touched = writable({}) const touched = writable({})
const steps = [Info, User] const validator = {
let validators = [ name: string().required("Your application must have a name"),
{ file: mixed().required("Please choose a file to import"),
applicationName: string().required("Your application must have a name"), }
},
{
roleId: string()
.nullable()
.required("You need to select a role for this app"),
},
]
let submitting = false let submitting = false
let valid = false let valid = false
$: checkValidity($values, validators[$currentStep]) $: checkValidity($values, validator)
onMount(async () => { onMount(async () => {
const hostingInfo = await hostingStore.actions.fetch() await hostingStore.actions.fetchDeployedApps()
if (hostingInfo.type === "self") { const existingAppNames = svelteGet(hostingStore).deployedAppNames
await hostingStore.actions.fetchDeployedApps() validator.name = string()
const existingAppNames = svelteGet(hostingStore).deployedAppNames .required("Your application must have a name")
validators[0].applicationName = string() .test(
.required("Your application must have a name.") "non-existing-app-name",
.test( "Another app with the same name already exists",
"non-existing-app-name", value => {
"App with same name already exists. Please try another app name.", return !existingAppNames.some(
value => appName => appName.toLowerCase() === value.toLowerCase()
!existingAppNames.some( )
appName => appName.toLowerCase() === value.toLowerCase() }
) )
)
}
}) })
const checkValidity = async (values, validator) => { const checkValidity = async (values, validator) => {
@ -70,15 +62,24 @@
async function createNewApp() { async function createNewApp() {
submitting = true submitting = true
// Check a template exists if we are important
if (template && !$values.file) {
$errors.file = "Please choose a file to import"
valid = false
submitting = false
return false
}
try { try {
// Create form data to create app // Create form data to create app
let data = new FormData() let data = new FormData()
data.append("name", $values.applicationName) data.append("name", $values.name)
data.append("useTemplate", template != null) data.append("useTemplate", template != null)
if (template) { if (template) {
data.append("templateName", template.name) data.append("templateName", template.name)
data.append("templateKey", template.key) data.append("templateKey", template.key)
data.append("templateFile", template.file) data.append("templateFile", $values.file)
} }
// Create App // Create App
@ -89,7 +90,7 @@
} }
analytics.captureEvent("App Created", { analytics.captureEvent("App Created", {
name: $values.applicationName, name: $values.name,
appId: appJson._id, appId: appJson._id,
template, template,
}) })
@ -121,129 +122,91 @@
} }
</script> </script>
<div class="container"> <ModalContent
<div class="sidebar"> title={template ? "Import app" : "Create new app"}
<img src={Logo} alt="budibase icon" /> confirmText={template ? "Import app" : "Create app"}
<div class="steps"> onConfirm={createNewApp}
{#each steps as component, i} disabled={!valid}
<Indicator >
active={$currentStep === i} {#if template}
done={i < $currentStep} <Dropzone
step={i + 1} error={$touched.file && $errors.file}
/> gallery={false}
{/each} label="File to import"
</div> value={[$values.file]}
</div> on:change={e => {
<div class="body"> $values.file = e.detail?.[0]
<div class="heading"> $touched.file = true
<Heading size="L">Get started with Budibase</Heading> }}
</div> />
<div class="step">
{#each steps as component, i (i)}
<div class:hidden={$currentStep !== i}>
<svelte:component
this={component}
{template}
{values}
{errors}
{touched}
/>
</div>
{/each}
</div>
<div class="footer">
{#if $currentStep > 0}
<Button medium secondary on:click={() => $currentStep--}>Back</Button>
{/if}
{#if $currentStep < steps.length - 1}
<Button medium cta on:click={() => $currentStep++} disabled={!valid}>
Next
</Button>
{/if}
{#if $currentStep === steps.length - 1}
<Button
medium
cta
on:click={createNewApp}
disabled={!valid || submitting}
>
{submitting ? "Loading..." : "Submit"}
</Button>
{/if}
</div>
</div>
{#if submitting}
<div in:fade class="spinner-container">
<Spinner />
<span class="spinner-text">Creating your app...</span>
</div>
{/if} {/if}
</div> <Body size="S">
Give your new app a name, and choose which groups have access (paid plans
only).
</Body>
<Input
bind:value={$values.name}
error={$touched.name && $errors.name}
on:blur={() => ($touched.name = true)}
label="Name"
/>
<Checkbox label="Group access" disabled value={true} text="All users" />
</ModalContent>
<style> <!--<div class="container">-->
.container { <!-- <div class="sidebar">-->
min-height: 600px; <!-- <img src={Logo} alt="budibase icon" />-->
display: grid; <!-- <div class="steps">-->
grid-template-columns: 80px 1fr; <!-- {#each steps as component, i}-->
position: relative; <!-- <Indicator-->
} <!-- active={$currentStep === i}-->
.sidebar { <!-- done={i < $currentStep}-->
display: flex; <!-- step={i + 1}-->
flex-direction: column; <!-- />-->
justify-content: flex-start; <!-- {/each}-->
align-items: stretch; <!-- </div>-->
padding: 40px 0; <!-- </div>-->
background: var(--grey-1); <!-- <div class="body">-->
} <!-- <div class="heading">-->
.steps { <!-- <Heading size="L">Get started with Budibase</Heading>-->
flex: 1 1 auto; <!-- </div>-->
display: grid; <!-- <div class="step">-->
border-bottom-left-radius: 0.5rem; <!-- {#each steps as component, i (i)}-->
border-top-left-radius: 0.5rem; <!-- <div class:hidden={$currentStep !== i}>-->
grid-gap: 30px; <!-- <svelte:component-->
align-content: center; <!-- this={component}-->
} <!-- {template}-->
.heading { <!-- {values}-->
display: flex; <!-- {errors}-->
flex-direction: row; <!-- {touched}-->
align-items: center; <!-- />-->
margin-bottom: 20px; <!-- </div>-->
} <!-- {/each}-->
.body { <!-- </div>-->
padding: 40px 60px 40px 60px; <!-- <div class="footer">-->
display: grid; <!-- {#if $currentStep > 0}-->
align-items: center; <!-- <Button medium secondary on:click={() => $currentStep&#45;&#45;}>Back</Button>-->
grid-template-rows: auto 1fr auto; <!-- {/if}-->
} <!-- {#if $currentStep < steps.length - 1}-->
.footer { <!-- <Button medium cta on:click={() => $currentStep++} disabled={!valid}>-->
display: flex; <!-- Next-->
flex-direction: row; <!-- </Button>-->
justify-content: flex-end; <!-- {/if}-->
align-items: center; <!-- {#if $currentStep === steps.length - 1}-->
gap: 15px; <!-- <Button-->
} <!-- medium-->
.spinner-container { <!-- cta-->
background: var(--background); <!-- on:click={createNewApp}-->
position: absolute; <!-- disabled={!valid || submitting}-->
border-radius: 5px; <!-- >-->
left: 0; <!-- {submitting ? "Loading..." : "Submit"}-->
top: 0; <!-- </Button>-->
right: 0; <!-- {/if}-->
bottom: 0; <!-- </div>-->
display: grid; <!-- </div>-->
justify-items: center; <!-- {#if submitting}-->
align-content: center; <!-- <div in:fade class="spinner-container">-->
grid-gap: 50px; <!-- <Spinner />-->
} <!-- <span class="spinner-text">Creating your app...</span>-->
.spinner-text { <!-- </div>-->
font-size: 2em; <!-- {/if}-->
}
.hidden {
display: none;
}
img {
height: 40px;
margin-bottom: 20px;
}
</style>

View File

@ -85,10 +85,14 @@
/> />
</ActionGroup> </ActionGroup>
</div> </div>
{#if layout === "grid"} {#if $apps.length}
<AppGridView {exportApp} /> {#if layout === "grid"}
<AppGridView {exportApp} />
{:else}
<AppTableView {exportApp} />
{/if}
{:else} {:else}
<AppTableView {exportApp} /> <div>No apps.</div>
{/if} {/if}
</Layout> </Layout>
</Page> </Page>