Add non-gallery option to dropzeon and update create app modal
This commit is contained in:
parent
d8120d107c
commit
2bf2045a0a
|
@ -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,6 +98,7 @@
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{#if selectedImage}
|
{#if selectedImage}
|
||||||
|
{#if gallery}
|
||||||
<div class="gallery">
|
<div class="gallery">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<div class="filename">{selectedImage.name}</div>
|
<div class="filename">{selectedImage.name}</div>
|
||||||
|
@ -132,9 +137,29 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">File {selectedImageIdx + 1} of {fileCount}</div>
|
<div class="footer">File {selectedImageIdx + 1} of {fileCount}</div>
|
||||||
</div>
|
</div>
|
||||||
|
{:else if value?.length}
|
||||||
|
{#each value as file}
|
||||||
|
<div class="gallery">
|
||||||
|
<div class="title">
|
||||||
|
<div class="filename">{file.name}</div>
|
||||||
|
<div class="filesize">
|
||||||
|
{#if file.size <= BYTES_IN_MB}
|
||||||
|
{`${file.size / BYTES_IN_KB} KB`}
|
||||||
|
{: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;
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
}
|
}
|
||||||
focus = false
|
focus = false
|
||||||
updateValue(event.target.value)
|
updateValue(event.target.value)
|
||||||
|
dispatch("blur")
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateValueOnEnter = event => {
|
const updateValueOnEnter = event => {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -30,5 +30,6 @@
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
on:click
|
on:click
|
||||||
on:input
|
on:input
|
||||||
|
on:blur
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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()
|
|
||||||
if (hostingInfo.type === "self") {
|
|
||||||
await hostingStore.actions.fetchDeployedApps()
|
await hostingStore.actions.fetchDeployedApps()
|
||||||
const existingAppNames = svelteGet(hostingStore).deployedAppNames
|
const existingAppNames = svelteGet(hostingStore).deployedAppNames
|
||||||
validators[0].applicationName = string()
|
validator.name = string()
|
||||||
.required("Your application must have a name.")
|
.required("Your application must have a name")
|
||||||
.test(
|
.test(
|
||||||
"non-existing-app-name",
|
"non-existing-app-name",
|
||||||
"App with same name already exists. Please try another app name.",
|
"Another app with the same name already exists",
|
||||||
value =>
|
value => {
|
||||||
!existingAppNames.some(
|
return !existingAppNames.some(
|
||||||
appName => appName.toLowerCase() === value.toLowerCase()
|
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}
|
|
||||||
done={i < $currentStep}
|
|
||||||
step={i + 1}
|
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="body">
|
|
||||||
<div class="heading">
|
|
||||||
<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"}
|
{#if template}
|
||||||
</Button>
|
<Dropzone
|
||||||
|
error={$touched.file && $errors.file}
|
||||||
|
gallery={false}
|
||||||
|
label="File to import"
|
||||||
|
value={[$values.file]}
|
||||||
|
on:change={e => {
|
||||||
|
$values.file = e.detail?.[0]
|
||||||
|
$touched.file = true
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
<Body size="S">
|
||||||
</div>
|
Give your new app a name, and choose which groups have access (paid plans
|
||||||
{#if submitting}
|
only).
|
||||||
<div in:fade class="spinner-container">
|
</Body>
|
||||||
<Spinner />
|
<Input
|
||||||
<span class="spinner-text">Creating your app...</span>
|
bind:value={$values.name}
|
||||||
</div>
|
error={$touched.name && $errors.name}
|
||||||
{/if}
|
on:blur={() => ($touched.name = true)}
|
||||||
</div>
|
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--}>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>
|
|
||||||
|
|
|
@ -85,11 +85,15 @@
|
||||||
/>
|
/>
|
||||||
</ActionGroup>
|
</ActionGroup>
|
||||||
</div>
|
</div>
|
||||||
|
{#if $apps.length}
|
||||||
{#if layout === "grid"}
|
{#if layout === "grid"}
|
||||||
<AppGridView {exportApp} />
|
<AppGridView {exportApp} />
|
||||||
{:else}
|
{:else}
|
||||||
<AppTableView {exportApp} />
|
<AppTableView {exportApp} />
|
||||||
{/if}
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<div>No apps.</div>
|
||||||
|
{/if}
|
||||||
</Layout>
|
</Layout>
|
||||||
</Page>
|
</Page>
|
||||||
<Modal
|
<Modal
|
||||||
|
|
Loading…
Reference in New Issue