This commit is contained in:
Adria Navarro 2025-05-19 10:08:09 +02:00 committed by GitHub
commit ed26b35300
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 261 additions and 160 deletions

View File

@ -2,7 +2,7 @@
import { getContext } from "svelte"
import Context from "../context"
const { hide } = getContext(Context.Modal) as { hide: () => void }
const { hide } = getContext(Context.Modal)
let count: number = 0
const clicks: number = 5

View File

@ -5,6 +5,7 @@
import { fade, fly } from "svelte/transition"
import Portal from "svelte-portal"
import Context from "../context"
import { ModalCancelFrom } from "../constants"
export let fixed: boolean = false
export let inline: boolean = false
@ -15,7 +16,7 @@
const dispatch = createEventDispatcher<{
show: void
hide: void
cancel: void
cancel: ModalCancelFrom
}>()
let visible: boolean = fixed || inline
let modal: HTMLElement | undefined
@ -44,17 +45,17 @@
}
}
export function cancel(): void {
export function cancel(from: ModalCancelFrom): void {
if (!visible || disableCancel) {
return
}
dispatch("cancel")
dispatch("cancel", from)
hide()
}
function handleKey(e: KeyboardEvent): void {
if (visible && e.key === "Escape") {
cancel()
cancel(ModalCancelFrom.ESCAPE_KEY)
}
}
@ -107,7 +108,7 @@
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="spectrum-Underlay is-open"
on:mousedown|self={cancel}
on:mousedown|self={() => cancel(ModalCancelFrom.OUTSIDE_CLICK)}
style="z-index:{zIndex || 999}"
>
<div
@ -115,8 +116,14 @@
in:fade={{ duration: 200 }}
out:fade|local={{ duration: 200 }}
/>
<div class="modal-wrapper" on:mousedown|self={cancel}>
<div class="modal-inner-wrapper" on:mousedown|self={cancel}>
<div
class="modal-wrapper"
on:mousedown|self={() => cancel(ModalCancelFrom.OUTSIDE_CLICK)}
>
<div
class="modal-inner-wrapper"
on:mousedown|self={() => cancel(ModalCancelFrom.OUTSIDE_CLICK)}
>
<slot name="outside" />
<div
use:focusModal

View File

@ -10,6 +10,7 @@
import Icon from "../Icon/Icon.svelte"
import Context from "../context"
import ProgressCircle from "../ProgressCircle/ProgressCircle.svelte"
import { ModalCancelFrom } from "../constants"
export let title: string | undefined = undefined
export let size: "S" | "M" | "L" | "XL" = "S"
@ -30,10 +31,7 @@
export let secondaryButtonWarning: boolean = false
export let custom: boolean = false
const { hide, cancel } = getContext(Context.Modal) as {
hide: () => void
cancel: () => void
}
const { hide, cancel } = getContext(Context.Modal)
let loading: boolean = false
@ -59,7 +57,7 @@
async function close(): Promise<void> {
loading = true
if (!onCancel || (await onCancel()) !== keepOpen) {
cancel()
cancel(ModalCancelFrom.CANCEL_BUTTON)
}
loading = false
}
@ -139,7 +137,11 @@
{/if}
{#if showCloseIcon}
<div class="close-icon">
<Icon hoverable name="Close" on:click={cancel} />
<Icon
hoverable
name="Close"
on:click={() => cancel(ModalCancelFrom.CLOSE_BUTTON)}
/>
</div>
{/if}
</div>

View File

@ -21,3 +21,10 @@ export enum TooltipType {
Positive = "positive",
Negative = "negative",
}
export const enum ModalCancelFrom {
CLOSE_BUTTON = "close_button",
CANCEL_BUTTON = "cancel_button",
ESCAPE_KEY = "escape_key",
OUTSIDE_CLICK = "outside_click",
}

View File

@ -1,3 +1,6 @@
import { ModalCancelFrom } from "../constants"
export interface ModalContext {
cancel: (from: ModalCancelFrom) => void
hide: () => void
}

View File

@ -1,17 +1,17 @@
<script>
<script lang="ts">
import { Icon, Heading } from "@budibase/bbui"
export let showClose = false
export let onClose = () => {}
export let heading = ""
export let showClose: boolean = false
export let onClose: (() => void) | null = () => {}
export let heading: string = ""
</script>
<section class="page">
<div class="closeButton">
{#if showClose}
<div class="closeButton">
<Icon hoverable name="Close" on:click={onClose} />
{/if}
</div>
{/if}
<div class="heading">
<Heading weight="light">{heading}</Heading>
</div>

View File

@ -67,9 +67,9 @@ export const PlanModel = {
export const ChangelogURL = "https://docs.budibase.com/changelog"
export const AutoScreenTypes = {
BLANK: "blank",
TABLE: "table",
FORM: "form",
PDF: "pdf",
export const enum AutoScreenTypes {
BLANK = "blank",
TABLE = "table",
FORM = "form",
PDF = "pdf",
}

View File

@ -1,11 +1,11 @@
<script lang="ts">
import { Layout } from "@budibase/bbui"
import { sortedScreens } from "@/stores/builder"
import ScreenNavItem from "./ScreenNavItem.svelte"
import { goto } from "@roxi/routify"
import { getVerticalResizeActions } from "@/components/common/resizable"
import NavHeader from "@/components/common/NavHeader.svelte"
import { getVerticalResizeActions } from "@/components/common/resizable"
import { sortedScreens } from "@/stores/builder"
import { Layout } from "@budibase/bbui"
import type { Screen } from "@budibase/types"
import NewScreenModal from "../../../_components/NewScreen/index.svelte"
import ScreenNavItem from "./ScreenNavItem.svelte"
const [resizable, resizableHandle] = getVerticalResizeActions()
@ -13,6 +13,7 @@
let searchValue = ""
let screensContainer: HTMLDivElement
let scrolling = false
let newScreenModal: NewScreenModal
$: filteredScreens = getFilteredScreens($sortedScreens, searchValue)
@ -44,7 +45,7 @@
placeholder="Search for screens"
bind:value={searchValue}
bind:search={searching}
onAdd={() => $goto("../new")}
onAdd={() => newScreenModal.show()}
/>
</div>
<div on:scroll={handleScroll} bind:this={screensContainer} class="content">
@ -69,6 +70,8 @@
/>
</div>
<NewScreenModal bind:this={newScreenModal} />
<style>
.screens {
display: flex;

View File

@ -4,7 +4,7 @@
import TypeModal from "./TypeModal.svelte"
import tableTypes from "./tableTypes"
import formTypes from "./formTypes"
import { Modal, notifications } from "@budibase/bbui"
import { Modal, ModalCancelFrom, notifications } from "@budibase/bbui"
import {
screenStore,
navigationStore,
@ -41,7 +41,7 @@
export const show = (
newMode: string,
preselectedDatasource: Table | ViewV2 | null
preselectedDatasource: Table | ViewV2 | null = null
) => {
mode = newMode
selectedTablesAndViews = []
@ -230,7 +230,7 @@
}
</script>
<Modal bind:this={datasourceModal} autoFocus={false}>
<Modal bind:this={datasourceModal} autoFocus={false} on:cancel>
<DatasourceModal
{selectedTablesAndViews}
onConfirm={onSelectDatasources}
@ -238,32 +238,48 @@
/>
</Modal>
<Modal bind:this={tableTypeModal}>
<Modal
bind:this={tableTypeModal}
on:cancel={e => {
if (
[ModalCancelFrom.CANCEL_BUTTON, ModalCancelFrom.ESCAPE_KEY].includes(
e.detail
)
) {
tableTypeModal.hide()
datasourceModal.show()
}
}}
>
<TypeModal
title="Choose how you want to manage rows"
types={tableTypes}
onConfirm={createTableScreen}
onCancel={() => {
tableTypeModal.hide()
datasourceModal.show()
}}
showCancelButton={!hasPreselectedDatasource}
/>
</Modal>
<Modal bind:this={screenDetailsModal}>
<Modal bind:this={screenDetailsModal} on:cancel>
<ScreenDetailsModal onConfirm={createBasicScreen} />
</Modal>
<Modal bind:this={formTypeModal}>
<Modal
bind:this={formTypeModal}
on:cancel={e => {
if (
[ModalCancelFrom.CANCEL_BUTTON, ModalCancelFrom.ESCAPE_KEY].includes(
e.detail
)
) {
formTypeModal.hide()
datasourceModal.show()
}
}}
>
<TypeModal
title="Select form type"
types={formTypes}
onConfirm={createFormScreen}
onCancel={() => {
formTypeModal.hide()
datasourceModal.show()
}}
showCancelButton={!hasPreselectedDatasource}
/>
</Modal>

View File

@ -80,7 +80,7 @@
<ModalContent
title="Autogenerated screens"
confirmText="Next"
cancelText="Cancel"
cancelText="Back"
{onConfirm}
disabled={!selectedTablesAndViews.length}
size="L"

View File

@ -14,7 +14,6 @@
export let title: string
export let types: SelectableType[]
export let onConfirm: (_selectedType: string) => Promise<void>
export let onCancel: () => void
export let showCancelButton: boolean = true
let selectedType: string | null = null
@ -29,7 +28,6 @@
confirmText="Done"
cancelText="Back"
onConfirm={confirm}
{onCancel}
disabled={!selectedType}
size="L"
{showCancelButton}

View File

@ -1,38 +1,95 @@
<script>
import { Body, Tag, Tags } from "@budibase/bbui"
<script lang="ts">
import CreationPage from "@/components/common/CreationPage.svelte"
import blank from "./images/blank.svg"
import table from "./images/tableInline.svg"
import form from "./images/formUpdate.svg"
import pdf from "./images/pdf.svg"
import CreateScreenModal from "./CreateScreenModal.svelte"
import { AutoScreenTypes } from "@/constants"
import { screenStore } from "@/stores/builder"
import { licensing } from "@/stores/portal"
import { AutoScreenTypes } from "@/constants"
import {
Body,
Modal,
ModalCancelFrom,
ModalContent,
notifications,
Tag,
Tags,
} from "@budibase/bbui"
import CreateScreenModal from "./CreateScreenModal.svelte"
import blank from "./images/blank.svg"
import form from "./images/formUpdate.svg"
import pdf from "./images/pdf.svg"
import table from "./images/tableInline.svg"
export let onClose = null
let createScreenModal
export let onClose: (() => void) | null = null
export let inline: boolean = false
export let submitOnClick: boolean = false
$: hasScreens = $screenStore.screens?.length
$: title = hasScreens ? "Create new screen" : "Create your first screen"
let rootModal: Modal
export const show = () => rootModal.show()
let createScreenModal: CreateScreenModal
let selectedType: AutoScreenTypes | undefined
function onSelect(screenType: AutoScreenTypes) {
if (submitOnClick) {
onConfirm(screenType)
} else if (selectedType === screenType) {
selectedType = undefined
} else {
selectedType = screenType
}
}
function onConfirm(type = selectedType) {
if (!type) {
notifications.error("Select screen type")
return
}
rootModal.hide()
createScreenModal.show(type)
const selectedTypeSnapshot = selectedType
createScreenModal.$on("cancel", e => {
if (
[ModalCancelFrom.CANCEL_BUTTON, ModalCancelFrom.ESCAPE_KEY].includes(
e.detail
)
) {
selectedType = selectedTypeSnapshot
rootModal.show()
}
})
}
</script>
<Modal
bind:this={rootModal}
on:hide={() => (selectedType = undefined)}
{inline}
>
<ModalContent
title={inline ? "" : title}
size="L"
{onConfirm}
disabled={!selectedType}
confirmText="Next"
showDivider={!inline}
showCloseIcon={!inline}
showCancelButton={!inline}
showConfirmButton={!submitOnClick}
>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="page">
<CreationPage
showClose={!!onClose}
{onClose}
heading={hasScreens ? "Create new screen" : "Create your first screen"}
>
<div class="subHeading">
<Body>Start from scratch or create screens from your data</Body>
<CreationPage showClose={!!onClose} {onClose} heading={inline ? title : ""}>
<div class="subHeading" class:inline>
Start from scratch or create screens from your data
</div>
<div class="cards">
<div
class="card"
on:click={() => createScreenModal.show(AutoScreenTypes.BLANK)}
on:click={() => onSelect(AutoScreenTypes.BLANK)}
class:selected={selectedType === AutoScreenTypes.BLANK}
>
<div class="image">
<img alt="A blank screen" src={blank} />
@ -45,7 +102,8 @@
<div
class="card"
on:click={() => createScreenModal.show(AutoScreenTypes.TABLE)}
on:click={() => onSelect(AutoScreenTypes.TABLE)}
class:selected={selectedType === AutoScreenTypes.TABLE}
>
<div class="image">
<img alt="A table of data" src={table} />
@ -58,7 +116,8 @@
<div
class="card"
on:click={() => createScreenModal.show(AutoScreenTypes.FORM)}
on:click={() => onSelect(AutoScreenTypes.FORM)}
class:selected={selectedType === AutoScreenTypes.FORM}
>
<div class="image">
<img alt="A form containing data" src={form} />
@ -73,8 +132,9 @@
class="card"
class:disabled={!$licensing.pdfEnabled}
on:click={$licensing.pdfEnabled
? () => createScreenModal.show(AutoScreenTypes.PDF)
? () => onSelect(AutoScreenTypes.PDF)
: null}
class:selected={selectedType === AutoScreenTypes.PDF}
>
<div class="image">
<img alt="A PDF document" src={pdf} width="185" />
@ -93,20 +153,17 @@
</div>
</div>
</CreationPage>
</div>
</ModalContent></Modal
>
<CreateScreenModal bind:this={createScreenModal} />
<style>
.page {
padding: 28px 40px 40px 40px;
.subHeading {
padding-bottom: var(--spacing-l);
}
.subHeading :global(p) {
text-align: center;
margin-top: 12px;
margin-bottom: 36px;
color: var(--spectrum-global-color-gray-600);
.subHeading:not(.inline) {
align-self: start;
}
.cards {
@ -156,6 +213,7 @@
display: flex;
flex-direction: column;
gap: 2px;
min-height: 46px;
}
.text :global(p:first-child) {
display: flex;
@ -165,4 +223,9 @@
.text :global(p:nth-child(2)) {
color: var(--spectrum-global-color-gray-600);
}
.selected {
border-radius: 4px;
outline: 1px solid var(--blue);
}
</style>

View File

@ -1,23 +1,25 @@
<script>
<script lang="ts">
import { onMount } from "svelte"
import NewScreen from "./_components/NewScreen/index.svelte"
import { screenStore } from "@/stores/builder"
import { goto } from "@roxi/routify"
$: onClose = getOnClose($screenStore)
let newScreenModal: NewScreen
const getOnClose = ({ screens, selectedScreenId }) => {
if (!screens?.length) {
return null
}
if (selectedScreenId) {
return () => {
$goto(`./${selectedScreenId}`)
}
}
return () => {
$goto(`./${screens[0]._id}`)
}
}
onMount(() => {
newScreenModal.show()
})
</script>
<NewScreen {onClose} />
<div class="new-screen-picker">
<NewScreen bind:this={newScreenModal} inline submitOnClick />
</div>
<style>
.new-screen-picker {
justify-items: center;
}
.new-screen-picker :global(.spectrum-Modal) {
border: none;
background: none;
}
</style>