Replace screen creation modal with page
This commit is contained in:
parent
1274bc32c3
commit
64129fa6b9
|
@ -1,201 +0,0 @@
|
||||||
<script>
|
|
||||||
import { store } from "builderStore"
|
|
||||||
import {
|
|
||||||
ModalContent,
|
|
||||||
Layout,
|
|
||||||
notifications,
|
|
||||||
Icon,
|
|
||||||
Body,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { tables, datasources } from "stores/backend"
|
|
||||||
import getTemplates from "builderStore/store/screenTemplates"
|
|
||||||
import ICONS from "components/backend/DatasourceNavigator/icons"
|
|
||||||
import { IntegrationNames } from "constants"
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
|
|
||||||
export let onCancel
|
|
||||||
export let onConfirm
|
|
||||||
export let initalScreens = []
|
|
||||||
|
|
||||||
let selectedScreens = [...initalScreens]
|
|
||||||
|
|
||||||
const toggleScreenSelection = (table, datasource) => {
|
|
||||||
if (selectedScreens.find(s => s.table === table._id)) {
|
|
||||||
selectedScreens = selectedScreens.filter(
|
|
||||||
screen => screen.table !== table._id
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
let partialTemplates = getTemplates($store, $tables.list).reduce(
|
|
||||||
(acc, template) => {
|
|
||||||
if (template.table === table._id) {
|
|
||||||
template.datasource = datasource.name
|
|
||||||
acc.push(template)
|
|
||||||
}
|
|
||||||
return acc
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
selectedScreens = [...partialTemplates, ...selectedScreens]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirmDatasourceSelection = async () => {
|
|
||||||
await onConfirm({
|
|
||||||
templates: selectedScreens,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
$: filteredSources = Array.isArray($datasources.list)
|
|
||||||
? $datasources.list.reduce((acc, datasource) => {
|
|
||||||
if (
|
|
||||||
datasource.source !== IntegrationNames.REST &&
|
|
||||||
datasource["entities"]
|
|
||||||
) {
|
|
||||||
acc.push(datasource)
|
|
||||||
}
|
|
||||||
return acc
|
|
||||||
}, [])
|
|
||||||
: []
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
try {
|
|
||||||
await datasources.fetch()
|
|
||||||
} catch (error) {
|
|
||||||
notifications.error("Error fetching datasources")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<span>
|
|
||||||
<ModalContent
|
|
||||||
title="Autogenerated screens"
|
|
||||||
confirmText="Confirm"
|
|
||||||
cancelText="Back"
|
|
||||||
onConfirm={confirmDatasourceSelection}
|
|
||||||
{onCancel}
|
|
||||||
disabled={!selectedScreens.length}
|
|
||||||
size="L"
|
|
||||||
>
|
|
||||||
<Body size="S">
|
|
||||||
Select which datasources you would like to use to create your screens
|
|
||||||
</Body>
|
|
||||||
<Layout noPadding gap="S">
|
|
||||||
{#each filteredSources as datasource}
|
|
||||||
<div class="data-source-wrap">
|
|
||||||
<div class="data-source-header">
|
|
||||||
<svelte:component
|
|
||||||
this={ICONS[datasource.source]}
|
|
||||||
height="24"
|
|
||||||
width="24"
|
|
||||||
/>
|
|
||||||
<div class="data-source-name">{datasource.name}</div>
|
|
||||||
</div>
|
|
||||||
{#if Array.isArray(datasource.entities)}
|
|
||||||
{#each datasource.entities.filter(table => table._id !== "ta_users") as table}
|
|
||||||
<div
|
|
||||||
class="data-source-entry"
|
|
||||||
class:selected={selectedScreens.find(
|
|
||||||
x => x.table === table._id
|
|
||||||
)}
|
|
||||||
on:click={() => toggleScreenSelection(table, datasource)}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
width="16px"
|
|
||||||
height="16px"
|
|
||||||
class="spectrum-Icon"
|
|
||||||
style="color: white"
|
|
||||||
focusable="false"
|
|
||||||
>
|
|
||||||
<use xlink:href="#spectrum-icon-18-Table" />
|
|
||||||
</svg>
|
|
||||||
{table.name}
|
|
||||||
{#if selectedScreens.find(x => x.table === table._id)}
|
|
||||||
<span class="data-source-check">
|
|
||||||
<Icon size="S" name="CheckmarkCircle" />
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
{#if datasource["entities"] && !Array.isArray(datasource.entities)}
|
|
||||||
{#each Object.keys(datasource.entities).filter(table => table._id !== "ta_users") as table_key}
|
|
||||||
<div
|
|
||||||
class="data-source-entry"
|
|
||||||
class:selected={selectedScreens.find(
|
|
||||||
x => x.table === datasource.entities[table_key]._id
|
|
||||||
)}
|
|
||||||
on:click={() =>
|
|
||||||
toggleScreenSelection(
|
|
||||||
datasource.entities[table_key],
|
|
||||||
datasource
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
width="16px"
|
|
||||||
height="16px"
|
|
||||||
class="spectrum-Icon"
|
|
||||||
style="color: white"
|
|
||||||
focusable="false"
|
|
||||||
>
|
|
||||||
<use xlink:href="#spectrum-icon-18-Table" />
|
|
||||||
</svg>
|
|
||||||
{datasource.entities[table_key].name}
|
|
||||||
{#if selectedScreens.find(x => x.table === datasource.entities[table_key]._id)}
|
|
||||||
<span class="data-source-check">
|
|
||||||
<Icon size="S" name="CheckmarkCircle" />
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</Layout>
|
|
||||||
</ModalContent>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.data-source-wrap {
|
|
||||||
padding-bottom: var(--spectrum-alias-item-padding-s);
|
|
||||||
display: grid;
|
|
||||||
grid-gap: var(--spacing-s);
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-source-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-m);
|
|
||||||
padding-bottom: var(--spacing-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-source-entry {
|
|
||||||
cursor: pointer;
|
|
||||||
grid-gap: var(--spectrum-alias-grid-margin-xsmall);
|
|
||||||
padding: var(--spectrum-alias-item-padding-s);
|
|
||||||
background: var(--spectrum-alias-background-color-secondary);
|
|
||||||
transition: 0.3s all;
|
|
||||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
|
||||||
border-radius: 4px;
|
|
||||||
border-width: 1px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-source-entry:hover,
|
|
||||||
.selected {
|
|
||||||
background: var(--spectrum-alias-background-color-tertiary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-source-entry .data-source-check {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-source-entry :global(.spectrum-Icon) {
|
|
||||||
min-width: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-source-entry .data-source-check :global(.spectrum-Icon) {
|
|
||||||
color: var(--spectrum-global-color-green-600);
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,165 +0,0 @@
|
||||||
<script>
|
|
||||||
import { tables } from "stores/backend"
|
|
||||||
import { ModalContent, Body, Layout, Icon, Heading } from "@budibase/bbui"
|
|
||||||
import blankScreenPreview from "./blankScreenPreview.png"
|
|
||||||
import listScreenPreview from "./listScreenPreview.png"
|
|
||||||
|
|
||||||
export let onConfirm
|
|
||||||
export let onCancel
|
|
||||||
|
|
||||||
let listScreenModeKey = "autoCreate"
|
|
||||||
let blankScreenModeKey = "blankScreen"
|
|
||||||
|
|
||||||
let selectedScreenMode
|
|
||||||
|
|
||||||
const confirmScreenSelection = async () => {
|
|
||||||
await onConfirm(selectedScreenMode)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<ModalContent
|
|
||||||
title="Add screens"
|
|
||||||
confirmText="Continue"
|
|
||||||
cancelText="Cancel"
|
|
||||||
onConfirm={confirmScreenSelection}
|
|
||||||
{onCancel}
|
|
||||||
disabled={!selectedScreenMode}
|
|
||||||
size="M"
|
|
||||||
>
|
|
||||||
<Layout noPadding gap="S">
|
|
||||||
<div
|
|
||||||
class="screen-type item blankView"
|
|
||||||
class:selected={selectedScreenMode == blankScreenModeKey}
|
|
||||||
on:click={() => {
|
|
||||||
selectedScreenMode = blankScreenModeKey
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class="content screen-type-wrap">
|
|
||||||
<img
|
|
||||||
alt="blank screen preview"
|
|
||||||
class="preview"
|
|
||||||
src={blankScreenPreview}
|
|
||||||
/>
|
|
||||||
<div class="screen-type-text">
|
|
||||||
<Heading size="XS">Blank screen</Heading>
|
|
||||||
<Body size="S">Add an empty blank screen</Body>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style="color: var(--spectrum-global-color-green-600); float: right"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class={`checkmark-spacing ${
|
|
||||||
selectedScreenMode == blankScreenModeKey ? "visible" : ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Icon size="S" name="CheckmarkCircle" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="listViewTitle">
|
|
||||||
<Heading size="XS">Quickly create a screen from your data</Heading>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="screen-type item"
|
|
||||||
class:selected={selectedScreenMode == listScreenModeKey}
|
|
||||||
on:click={() => {
|
|
||||||
selectedScreenMode = listScreenModeKey
|
|
||||||
}}
|
|
||||||
class:disabled={!$tables.list.filter(table => table._id !== "ta_users")
|
|
||||||
.length}
|
|
||||||
>
|
|
||||||
<div class="content screen-type-wrap">
|
|
||||||
<img
|
|
||||||
alt="list screen preview"
|
|
||||||
class="preview"
|
|
||||||
src={listScreenPreview}
|
|
||||||
/>
|
|
||||||
<div class="screen-type-text">
|
|
||||||
<Heading size="XS">List view</Heading>
|
|
||||||
<Body size="S">
|
|
||||||
Create, edit and view your data in a list view screen with side
|
|
||||||
panel
|
|
||||||
</Body>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style="color: var(--spectrum-global-color-green-600); float: right"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class={`checkmark-spacing ${
|
|
||||||
selectedScreenMode == listScreenModeKey ? "visible" : ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Icon size="S" name="CheckmarkCircle" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
</ModalContent>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.screen-type-wrap {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.disabled {
|
|
||||||
opacity: 0.3;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.checkmark-spacing {
|
|
||||||
margin-right: var(--spacing-m);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
letter-spacing: 0px;
|
|
||||||
}
|
|
||||||
.item {
|
|
||||||
cursor: pointer;
|
|
||||||
grid-gap: var(--spectrum-alias-grid-margin-xsmall);
|
|
||||||
background: var(--spectrum-alias-background-color-secondary);
|
|
||||||
transition: 0.3s all;
|
|
||||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
|
||||||
border-radius: 4px;
|
|
||||||
border-width: 1px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.item:hover,
|
|
||||||
.selected {
|
|
||||||
background: var(--spectrum-alias-background-color-tertiary);
|
|
||||||
}
|
|
||||||
.screen-type-wrap .screen-type-text {
|
|
||||||
padding-left: var(--spectrum-alias-item-padding-xl);
|
|
||||||
}
|
|
||||||
.screen-type-wrap .screen-type-text :global(h1) {
|
|
||||||
padding-bottom: var(--spacing-xs);
|
|
||||||
}
|
|
||||||
.screen-type-wrap :global(.spectrum-Icon) {
|
|
||||||
min-width: var(--spectrum-icon-size-m);
|
|
||||||
}
|
|
||||||
.screen-type-wrap :global(.spectrum-Heading) {
|
|
||||||
padding-bottom: var(--spectrum-alias-item-padding-s);
|
|
||||||
}
|
|
||||||
.preview {
|
|
||||||
width: 140px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.listViewTitle {
|
|
||||||
margin-top: 35px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blankView {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.visible {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -9,7 +9,7 @@
|
||||||
Helpers,
|
Helpers,
|
||||||
notifications,
|
notifications,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import ScreenDetailsModal from "./ScreenDetailsModal.svelte"
|
import ScreenDetailsModal from "components/design/ScreenDetailsModal.svelte"
|
||||||
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
||||||
import { makeComponentUnique } from "builderStore/componentUtils"
|
import { makeComponentUnique } from "builderStore/componentUtils"
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
<script>
|
<script>
|
||||||
import { Search, Layout, Select, Body, Button } from "@budibase/bbui"
|
import { Search, Layout, Select, Body, Button } from "@budibase/bbui"
|
||||||
import Panel from "components/design/Panel.svelte"
|
import Panel from "components/design/Panel.svelte"
|
||||||
|
import { goto } from "@roxi/routify"
|
||||||
import { roles } from "stores/backend"
|
import { roles } from "stores/backend"
|
||||||
import { store, sortedScreens } from "builderStore"
|
import { store, sortedScreens } from "builderStore"
|
||||||
import NavItem from "components/common/NavItem.svelte"
|
import NavItem from "components/common/NavItem.svelte"
|
||||||
import ScreenDropdownMenu from "./ScreenDropdownMenu.svelte"
|
import ScreenDropdownMenu from "./ScreenDropdownMenu.svelte"
|
||||||
import ScreenWizard from "./ScreenWizard.svelte"
|
|
||||||
import RoleIndicator from "./RoleIndicator.svelte"
|
import RoleIndicator from "./RoleIndicator.svelte"
|
||||||
import { RoleUtils } from "@budibase/frontend-core"
|
import { RoleUtils } from "@budibase/frontend-core"
|
||||||
|
|
||||||
let searchString
|
let searchString
|
||||||
let accessRole = "all"
|
let accessRole = "all"
|
||||||
let showNewScreenModal
|
|
||||||
|
|
||||||
$: filteredScreens = getFilteredScreens(
|
$: filteredScreens = getFilteredScreens(
|
||||||
$sortedScreens,
|
$sortedScreens,
|
||||||
|
@ -31,7 +30,7 @@
|
||||||
|
|
||||||
<Panel title="Screens" borderRight>
|
<Panel title="Screens" borderRight>
|
||||||
<Layout paddingX="L" paddingY="XL" gap="S">
|
<Layout paddingX="L" paddingY="XL" gap="S">
|
||||||
<Button on:click={showNewScreenModal} cta>Add screen</Button>
|
<Button on:click={() => $goto("../../new")} cta>Add screen</Button>
|
||||||
<Search
|
<Search
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
value={searchString}
|
value={searchString}
|
||||||
|
@ -73,5 +72,3 @@
|
||||||
</Layout>
|
</Layout>
|
||||||
{/if}
|
{/if}
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
<ScreenWizard bind:showModal={showNewScreenModal} />
|
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
<script>
|
|
||||||
import { Select, ModalContent } from "@budibase/bbui"
|
|
||||||
import { RoleUtils } from "@budibase/frontend-core"
|
|
||||||
import { roles } from "stores/backend"
|
|
||||||
import { get } from "svelte/store"
|
|
||||||
import { store } from "builderStore"
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
|
|
||||||
export let onConfirm
|
|
||||||
export let onCancel
|
|
||||||
export let screenUrl
|
|
||||||
export let screenAccessRole
|
|
||||||
|
|
||||||
let error
|
|
||||||
|
|
||||||
const onChangeRole = e => {
|
|
||||||
const roleId = e.detail
|
|
||||||
if (routeExists(screenUrl, roleId)) {
|
|
||||||
error = "This URL is already taken for this access role"
|
|
||||||
} else {
|
|
||||||
error = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const routeExists = (url, role) => {
|
|
||||||
if (!url || !role) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return get(store).screens.some(
|
|
||||||
screen =>
|
|
||||||
screen.routing.route.toLowerCase() === url.toLowerCase() &&
|
|
||||||
screen.routing.roleId === role
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
// Validate the initial role
|
|
||||||
onChangeRole({ detail: screenAccessRole })
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<ModalContent
|
|
||||||
title="Autogenerated screens"
|
|
||||||
confirmText="Done"
|
|
||||||
cancelText="Back"
|
|
||||||
{onConfirm}
|
|
||||||
{onCancel}
|
|
||||||
disabled={!!error}
|
|
||||||
>
|
|
||||||
Select which level of access you want your screens to have
|
|
||||||
<Select
|
|
||||||
bind:value={screenAccessRole}
|
|
||||||
on:change={onChangeRole}
|
|
||||||
label="Access"
|
|
||||||
{error}
|
|
||||||
getOptionLabel={role => role.name}
|
|
||||||
getOptionValue={role => role._id}
|
|
||||||
getOptionColour={role => RoleUtils.getRoleColour(role._id)}
|
|
||||||
options={$roles}
|
|
||||||
placeholder={null}
|
|
||||||
/>
|
|
||||||
</ModalContent>
|
|
|
@ -1,204 +0,0 @@
|
||||||
<script>
|
|
||||||
import ScreenDetailsModal from "./ScreenDetailsModal.svelte"
|
|
||||||
import NewScreenModal from "./NewScreenModal.svelte"
|
|
||||||
import DatasourceModal from "./DatasourceModal.svelte"
|
|
||||||
import ScreenRoleModal from "./ScreenRoleModal.svelte"
|
|
||||||
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
|
||||||
import { Modal, notifications } from "@budibase/bbui"
|
|
||||||
import { store } from "builderStore"
|
|
||||||
import { get } from "svelte/store"
|
|
||||||
import getTemplates from "builderStore/store/screenTemplates"
|
|
||||||
import { tables } from "stores/backend"
|
|
||||||
import { Roles } from "constants/backend"
|
|
||||||
import { capitalise } from "helpers"
|
|
||||||
|
|
||||||
let pendingScreen
|
|
||||||
|
|
||||||
// Modal refs
|
|
||||||
let newScreenModal
|
|
||||||
let screenDetailsModal
|
|
||||||
let datasourceModal
|
|
||||||
let screenAccessRoleModal
|
|
||||||
|
|
||||||
// Cache variables for workflow
|
|
||||||
let screenAccessRole = Roles.BASIC
|
|
||||||
let selectedTemplates = null
|
|
||||||
let blankScreenUrl = null
|
|
||||||
let screenMode = null
|
|
||||||
|
|
||||||
// External handler to show the screen wizard
|
|
||||||
export const showModal = () => {
|
|
||||||
selectedTemplates = null
|
|
||||||
blankScreenUrl = null
|
|
||||||
screenMode = null
|
|
||||||
pendingScreen = null
|
|
||||||
screenAccessRole = Roles.BASIC
|
|
||||||
newScreenModal.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates an array of screens, checking and sanitising their URLs
|
|
||||||
const createScreens = async ({ screens, screenAccessRole }) => {
|
|
||||||
if (!screens?.length) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (let screen of screens) {
|
|
||||||
// Check we aren't clashing with an existing URL
|
|
||||||
if (hasExistingUrl(screen.routing.route)) {
|
|
||||||
let suffix = 2
|
|
||||||
let candidateUrl = makeCandidateUrl(screen, suffix)
|
|
||||||
while (hasExistingUrl(candidateUrl)) {
|
|
||||||
candidateUrl = makeCandidateUrl(screen, ++suffix)
|
|
||||||
}
|
|
||||||
screen.routing.route = candidateUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sanitise URL
|
|
||||||
screen.routing.route = sanitizeUrl(screen.routing.route)
|
|
||||||
|
|
||||||
// Use the currently selected role
|
|
||||||
if (!screenAccessRole) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
screen.routing.roleId = screenAccessRole
|
|
||||||
|
|
||||||
// Create the screen
|
|
||||||
await store.actions.screens.save(screen)
|
|
||||||
|
|
||||||
// Add link in layout for list screens
|
|
||||||
if (screen.props._instanceName.endsWith("List")) {
|
|
||||||
await store.actions.links.save(
|
|
||||||
screen.routing.route,
|
|
||||||
capitalise(screen.routing.route.split("/")[1])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
notifications.error("Error creating screens")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if any screens exist in the store with the given route and
|
|
||||||
// currently selected role
|
|
||||||
const hasExistingUrl = url => {
|
|
||||||
const roleId = screenAccessRole
|
|
||||||
const screens = get(store).screens.filter(s => s.routing.roleId === roleId)
|
|
||||||
return !!screens.find(s => s.routing?.route === url)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constructs a candidate URL for a new screen, suffixing the base of the
|
|
||||||
// screen's URL with a given suffix.
|
|
||||||
// e.g. "/sales/:id" => "/sales-1/:id"
|
|
||||||
const makeCandidateUrl = (screen, suffix) => {
|
|
||||||
let url = screen.routing?.route || ""
|
|
||||||
if (url.startsWith("/")) {
|
|
||||||
url = url.slice(1)
|
|
||||||
}
|
|
||||||
if (!url.includes("/")) {
|
|
||||||
return `/${url}-${suffix}`
|
|
||||||
} else {
|
|
||||||
const split = url.split("/")
|
|
||||||
return `/${split[0]}-${suffix}/${split.slice(1).join("/")}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler for NewScreenModal
|
|
||||||
const confirmScreenSelection = async mode => {
|
|
||||||
screenMode = mode
|
|
||||||
|
|
||||||
if (mode === "autoCreate") {
|
|
||||||
datasourceModal.show()
|
|
||||||
} else {
|
|
||||||
let templates = getTemplates($store, $tables.list)
|
|
||||||
const blankScreenTemplate = templates.find(
|
|
||||||
t => t.id === "createFromScratch"
|
|
||||||
)
|
|
||||||
pendingScreen = blankScreenTemplate.create()
|
|
||||||
screenDetailsModal.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler for DatasourceModal confirmation, move to screen access select
|
|
||||||
const confirmScreenDatasources = async ({ templates }) => {
|
|
||||||
selectedTemplates = templates
|
|
||||||
screenAccessRoleModal.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler for Datasource Screen Creation
|
|
||||||
const completeDatasourceScreenCreation = async () => {
|
|
||||||
const screens = selectedTemplates.map(template => {
|
|
||||||
let screenTemplate = template.create()
|
|
||||||
screenTemplate.datasource = template.datasource
|
|
||||||
screenTemplate.autoTableId = template.table
|
|
||||||
return screenTemplate
|
|
||||||
})
|
|
||||||
await createScreens({ screens, screenAccessRole })
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirmScreenBlank = async ({ screenUrl }) => {
|
|
||||||
blankScreenUrl = screenUrl
|
|
||||||
screenAccessRoleModal.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Submit request for a blank screen
|
|
||||||
const confirmBlankScreenCreation = async ({
|
|
||||||
screenUrl,
|
|
||||||
screenAccessRole,
|
|
||||||
}) => {
|
|
||||||
if (!pendingScreen) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pendingScreen.routing.route = screenUrl
|
|
||||||
await createScreens({ screens: [pendingScreen], screenAccessRole })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Submit screen config for creation.
|
|
||||||
const confirmScreenCreation = async () => {
|
|
||||||
if (screenMode === "blankScreen") {
|
|
||||||
confirmBlankScreenCreation({
|
|
||||||
screenUrl: blankScreenUrl,
|
|
||||||
screenAccessRole,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
completeDatasourceScreenCreation()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const roleSelectBack = () => {
|
|
||||||
if (screenMode === "blankScreen") {
|
|
||||||
screenDetailsModal.show()
|
|
||||||
} else {
|
|
||||||
datasourceModal.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Modal bind:this={newScreenModal}>
|
|
||||||
<NewScreenModal onConfirm={confirmScreenSelection} />
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<Modal bind:this={datasourceModal}>
|
|
||||||
<DatasourceModal
|
|
||||||
onConfirm={confirmScreenDatasources}
|
|
||||||
onCancel={() => newScreenModal.show()}
|
|
||||||
initalScreens={!selectedTemplates ? [] : [...selectedTemplates]}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<Modal bind:this={screenAccessRoleModal}>
|
|
||||||
<ScreenRoleModal
|
|
||||||
onConfirm={confirmScreenCreation}
|
|
||||||
onCancel={roleSelectBack}
|
|
||||||
bind:screenAccessRole
|
|
||||||
screenUrl={blankScreenUrl}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<Modal bind:this={screenDetailsModal}>
|
|
||||||
<ScreenDetailsModal
|
|
||||||
onConfirm={confirmScreenBlank}
|
|
||||||
onCancel={() => newScreenModal.show()}
|
|
||||||
initialUrl={blankScreenUrl}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
Binary file not shown.
Before Width: | Height: | Size: 69 KiB |
Binary file not shown.
Before Width: | Height: | Size: 106 KiB |
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import ScreenDetailsModal from "./ScreenDetailsModal.svelte"
|
import ScreenDetailsModal from "components/design/ScreenDetailsModal.svelte"
|
||||||
import DatasourceModal from "./DatasourceModal.svelte"
|
import DatasourceModal from "./DatasourceModal.svelte"
|
||||||
import ScreenRoleModal from "./ScreenRoleModal.svelte"
|
import ScreenRoleModal from "./ScreenRoleModal.svelte"
|
||||||
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
<script>
|
|
||||||
import { ModalContent, Input } from "@budibase/bbui"
|
|
||||||
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
|
||||||
import { get } from "svelte/store"
|
|
||||||
import { store } from "builderStore"
|
|
||||||
|
|
||||||
export let onConfirm
|
|
||||||
export let onCancel
|
|
||||||
export let screenUrl
|
|
||||||
export let screenRole
|
|
||||||
export let confirmText = "Continue"
|
|
||||||
|
|
||||||
const appPrefix = "/app"
|
|
||||||
let touched = false
|
|
||||||
let error
|
|
||||||
|
|
||||||
$: appUrl = screenUrl
|
|
||||||
? `${window.location.origin}${appPrefix}${screenUrl}`
|
|
||||||
: `${window.location.origin}${appPrefix}`
|
|
||||||
|
|
||||||
const routeChanged = event => {
|
|
||||||
if (!event.detail.startsWith("/")) {
|
|
||||||
screenUrl = "/" + event.detail
|
|
||||||
}
|
|
||||||
touched = true
|
|
||||||
screenUrl = sanitizeUrl(screenUrl)
|
|
||||||
if (routeExists(screenUrl)) {
|
|
||||||
error = "This URL is already taken for this access role"
|
|
||||||
} else {
|
|
||||||
error = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const routeExists = url => {
|
|
||||||
if (!screenRole) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return get(store).screens.some(
|
|
||||||
screen =>
|
|
||||||
screen.routing.route.toLowerCase() === url.toLowerCase() &&
|
|
||||||
screen.routing.roleId === screenRole
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirmScreenDetails = async () => {
|
|
||||||
await onConfirm({
|
|
||||||
screenUrl,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<ModalContent
|
|
||||||
size="M"
|
|
||||||
title={"Screen details"}
|
|
||||||
{confirmText}
|
|
||||||
onConfirm={confirmScreenDetails}
|
|
||||||
{onCancel}
|
|
||||||
cancelText={"Back"}
|
|
||||||
disabled={!screenUrl || error || !touched}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
label="Enter a URL for the new screen"
|
|
||||||
{error}
|
|
||||||
bind:value={screenUrl}
|
|
||||||
on:change={routeChanged}
|
|
||||||
/>
|
|
||||||
<div class="app-server" title={appUrl}>
|
|
||||||
{appUrl}
|
|
||||||
</div>
|
|
||||||
</ModalContent>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.app-server {
|
|
||||||
color: var(--spectrum-global-color-gray-600);
|
|
||||||
width: 320px;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,107 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
import { store, selectedScreen } from "builderStore"
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
import { redirect } from "@roxi/routify"
|
import { redirect } from "@roxi/routify"
|
||||||
import { Body } from "@budibase/bbui"
|
import { store as frontendStore } from "builderStore"
|
||||||
import CreationPage from "components/common/CreationPage.svelte"
|
|
||||||
import blankImage from "./blank.png"
|
|
||||||
import tableImage from "./table.png"
|
|
||||||
import CreateScreenModal from "./_components/CreateScreenModal.svelte"
|
|
||||||
|
|
||||||
let loaded = false
|
$: {
|
||||||
let createScreenModal
|
if ($frontendStore.screens.length > 0) {
|
||||||
|
$redirect(`./${$frontendStore.screens[0]._id}`)
|
||||||
onMount(() => {
|
|
||||||
loaded = true
|
|
||||||
if ($selectedScreen) {
|
|
||||||
$redirect(`./${$selectedScreen._id}`)
|
|
||||||
} else if ($store.screens?.length) {
|
|
||||||
$redirect(`./${$store.screens[0]._id}`)
|
|
||||||
} else {
|
} else {
|
||||||
loaded = true
|
$redirect("./new")
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if loaded}
|
|
||||||
<CreationPage showClose={false} heading="Create your first screen">
|
|
||||||
<div class="subHeading">
|
|
||||||
<Body size="L">Start from scratch or create screens from your data</Body>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="cards">
|
|
||||||
<div class="card" on:click={() => createScreenModal.show("blank")}>
|
|
||||||
<div class="image">
|
|
||||||
<img alt="" src={blankImage} />
|
|
||||||
</div>
|
|
||||||
<div class="text">
|
|
||||||
<Body size="S">Blank screen</Body>
|
|
||||||
<Body size="XS">Add an empty blank screen</Body>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card" on:click={() => createScreenModal.show("table")}>
|
|
||||||
<div class="image">
|
|
||||||
<img alt="" src={tableImage} />
|
|
||||||
</div>
|
|
||||||
<div class="text">
|
|
||||||
<Body size="S">Table</Body>
|
|
||||||
<Body size="XS">View, edit and delete rows on a table</Body>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CreationPage>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<CreateScreenModal bind:this={createScreenModal} />
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.subHeading :global(p) {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 12px;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
color: var(--grey-6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.cards {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
margin: 12px;
|
|
||||||
max-width: 235px;
|
|
||||||
transition: filter 150ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card:hover {
|
|
||||||
filter: brightness(1.1);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image {
|
|
||||||
border-radius: 4px 4px 0 0;
|
|
||||||
width: 100%;
|
|
||||||
max-height: 127px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image img {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
border: 1px solid var(--grey-4);
|
|
||||||
border-radius: 0 0 4px 4px;
|
|
||||||
padding: 8px 16px 13px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text :global(p:nth-child(1)) {
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text :global(p:nth-child(2)) {
|
|
||||||
color: var(--grey-6);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
<script>
|
||||||
|
import { Body } from "@budibase/bbui"
|
||||||
|
import CreationPage from "components/common/CreationPage.svelte"
|
||||||
|
import blankImage from "./blank.png"
|
||||||
|
import tableImage from "./table.png"
|
||||||
|
import CreateScreenModal from "./_components/CreateScreenModal.svelte"
|
||||||
|
import { store as frontendStore } from "builderStore"
|
||||||
|
import { goto } from "@roxi/routify"
|
||||||
|
|
||||||
|
let createScreenModal
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="page">
|
||||||
|
<CreationPage
|
||||||
|
showClose={$frontendStore.screens.length > 0}
|
||||||
|
onClose={() => $goto(`./${$frontendStore.screens[0]._id}`)}
|
||||||
|
heading="Create your first screen"
|
||||||
|
>
|
||||||
|
<div class="subHeading">
|
||||||
|
<Body size="L">Start from scratch or create screens from your data</Body>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cards">
|
||||||
|
<div class="card" on:click={() => createScreenModal.show("blank")}>
|
||||||
|
<div class="image">
|
||||||
|
<img alt="" src={blankImage} />
|
||||||
|
</div>
|
||||||
|
<div class="text">
|
||||||
|
<Body size="S">Blank screen</Body>
|
||||||
|
<Body size="XS">Add an empty blank screen</Body>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" on:click={() => createScreenModal.show("table")}>
|
||||||
|
<div class="image">
|
||||||
|
<img alt="" src={tableImage} />
|
||||||
|
</div>
|
||||||
|
<div class="text">
|
||||||
|
<Body size="S">Table</Body>
|
||||||
|
<Body size="XS">View, edit and delete rows on a table</Body>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CreationPage>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CreateScreenModal bind:this={createScreenModal} />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.page {
|
||||||
|
padding: 28px 40px 40px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subHeading :global(p) {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
color: var(--grey-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cards {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin: 12px;
|
||||||
|
max-width: 235px;
|
||||||
|
transition: filter 150ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
filter: brightness(1.1);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 127px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
border: 1px solid var(--grey-4);
|
||||||
|
border-radius: 0 0 4px 4px;
|
||||||
|
padding: 8px 16px 13px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text :global(p:nth-child(1)) {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text :global(p:nth-child(2)) {
|
||||||
|
color: var(--grey-6);
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue