mirror of https://github.com/Budibase/budibase.git
committed by
GitHub
9 changed files with 748 additions and 107 deletions
|
After Width: | Height: | Size: 27 KiB |
@ -0,0 +1,179 @@ |
|||
<script> |
|||
import { ModalContent, Body, Detail } from "@budibase/bbui" |
|||
import { store, selectedAccessRole, allScreens } from "builderStore" |
|||
import { cloneDeep } from "lodash/fp" |
|||
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl" |
|||
import { onDestroy } from "svelte" |
|||
|
|||
export let selectedScreens |
|||
export let screenName |
|||
export let url |
|||
export let chooseModal |
|||
|
|||
let roleId = $selectedAccessRole || "BASIC" |
|||
|
|||
let routeError |
|||
let selectedNav |
|||
let createdScreens = [] |
|||
$: { |
|||
selectedScreens?.forEach(screen => { |
|||
createdScreens = [...createdScreens, screen.create()] |
|||
}) |
|||
} |
|||
|
|||
$: blankSelected = selectedScreens.length === 1 |
|||
|
|||
const save = async screens => { |
|||
for (let screen of screens) { |
|||
await saveScreens(screen) |
|||
} |
|||
|
|||
let navLayout = cloneDeep( |
|||
$store.layouts.find(layout => layout._id === "layout_private_master") |
|||
) |
|||
navLayout.props.navigation = selectedNav |
|||
|
|||
await store.actions.routing.fetch() |
|||
await store.actions.layouts.save(navLayout) |
|||
selectedScreens = [] |
|||
screenName = "" |
|||
url = "" |
|||
} |
|||
const saveScreens = async draftScreen => { |
|||
let existingScreenCount = $store.screens.filter( |
|||
s => s.props._instanceName == draftScreen.props._instanceName |
|||
).length |
|||
|
|||
if (existingScreenCount > 0) { |
|||
let oldUrlArr = draftScreen.routing.route.split("/") |
|||
oldUrlArr[1] = `${oldUrlArr[1]}-${existingScreenCount + 1}` |
|||
draftScreen.routing.route = oldUrlArr.join("/") |
|||
} |
|||
|
|||
let route = url ? sanitizeUrl(`${url}`) : draftScreen.routing.route |
|||
if (draftScreen) { |
|||
if (!route) { |
|||
routeError = "URL is required" |
|||
} else { |
|||
if (routeExists(route, roleId)) { |
|||
routeError = "This URL is already taken for this access role" |
|||
} else { |
|||
routeError = "" |
|||
} |
|||
} |
|||
|
|||
if (routeError) return false |
|||
|
|||
if (screenName) { |
|||
draftScreen.props._instanceName = screenName |
|||
} |
|||
|
|||
draftScreen.routing.route = route |
|||
|
|||
await store.actions.screens.create(draftScreen) |
|||
if (draftScreen.props._instanceName.endsWith("List")) { |
|||
await store.actions.components.links.save( |
|||
draftScreen.routing.route, |
|||
draftScreen.routing.route.split("/")[1] |
|||
) |
|||
} |
|||
} |
|||
} |
|||
|
|||
const routeExists = (route, roleId) => { |
|||
return $allScreens.some( |
|||
screen => |
|||
screen.routing.route.toLowerCase() === route.toLowerCase() && |
|||
screen.routing.roleId === roleId |
|||
) |
|||
} |
|||
|
|||
onDestroy(() => { |
|||
selectedScreens = [] |
|||
screenName = "" |
|||
url = "" |
|||
}) |
|||
</script> |
|||
|
|||
<ModalContent |
|||
title="Select navigation" |
|||
cancelText="Back" |
|||
onCancel={() => (blankSelected ? chooseModal(1) : chooseModal(0))} |
|||
size="M" |
|||
onConfirm={() => { |
|||
save(createdScreens) |
|||
}} |
|||
disabled={!selectedNav} |
|||
> |
|||
<Body size="S" |
|||
>Please select your preferred layout for the new application:</Body |
|||
> |
|||
|
|||
<div class="wrapper"> |
|||
<div |
|||
data-cy="left-nav" |
|||
on:click={() => (selectedNav = "Left")} |
|||
class:unselected={selectedNav && selectedNav !== "Left"} |
|||
> |
|||
<div class="box"> |
|||
<div class="side-nav" /> |
|||
</div> |
|||
<div><Detail>Side Nav</Detail></div> |
|||
</div> |
|||
<div |
|||
on:click={() => (selectedNav = "Top")} |
|||
class:unselected={selectedNav && selectedNav !== "Top"} |
|||
> |
|||
<div class="box"> |
|||
<div class="top-nav" /> |
|||
</div> |
|||
<div><Detail>Top Nav</Detail></div> |
|||
</div> |
|||
<div |
|||
on:click={() => (selectedNav = "None")} |
|||
class:unselected={selectedNav && selectedNav !== "None"} |
|||
> |
|||
<div class="box" /> |
|||
<div><Detail>No Nav</Detail></div> |
|||
</div> |
|||
</div> |
|||
</ModalContent> |
|||
|
|||
<style> |
|||
.side-nav { |
|||
float: left; |
|||
background: #d3d3d3 0% 0% no-repeat padding-box; |
|||
border-radius: 2px 0px 0px 2px; |
|||
height: 100%; |
|||
width: 10%; |
|||
} |
|||
|
|||
.top-nav { |
|||
background: #d3d3d3 0% 0% no-repeat padding-box; |
|||
vertical-align: top; |
|||
width: 100%; |
|||
height: 15%; |
|||
} |
|||
.box { |
|||
display: inline-block; |
|||
background: #eaeaea 0% 0% no-repeat padding-box; |
|||
border: 1px solid #d3d3d3; |
|||
border-radius: 2px; |
|||
opacity: 1; |
|||
width: 120px; |
|||
height: 70px; |
|||
margin-right: 20px; |
|||
} |
|||
|
|||
.wrapper { |
|||
display: flex; |
|||
padding-top: 4%; |
|||
list-style-type: none; |
|||
margin: 0; |
|||
padding: 0; |
|||
margin-right: 5%; |
|||
} |
|||
.unselected { |
|||
opacity: 0.3; |
|||
} |
|||
</style> |
|||
@ -1,117 +1,115 @@ |
|||
<script> |
|||
import { store, allScreens, selectedAccessRole } from "builderStore" |
|||
import { store } from "builderStore" |
|||
import { tables } from "stores/backend" |
|||
import { roles } from "stores/backend" |
|||
import { Input, Select, ModalContent, Toggle } from "@budibase/bbui" |
|||
import { ModalContent, Body, Detail, Layout, Icon } from "@budibase/bbui" |
|||
import getTemplates from "builderStore/store/screenTemplates" |
|||
import analytics, { Events } from "analytics" |
|||
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl" |
|||
|
|||
const CONTAINER = "@budibase/standard-components/container" |
|||
export let selectedScreens = [] |
|||
export let chooseModal |
|||
|
|||
let name = "" |
|||
let routeError |
|||
let baseComponent = CONTAINER |
|||
let templateIndex |
|||
let draftScreen |
|||
let createLink = true |
|||
let roleId = $selectedAccessRole || "BASIC" |
|||
const blankScreen = "createFromScratch" |
|||
|
|||
$: templates = getTemplates($store, $tables.list) |
|||
$: route = !route && $allScreens.length === 0 ? "*" : route |
|||
$: { |
|||
if (templates && templateIndex === undefined) { |
|||
templateIndex = 0 |
|||
templateChanged(0) |
|||
} |
|||
} |
|||
$: blankSelected = selectedScreens?.length === 1 |
|||
$: autoSelected = selectedScreens?.length > 0 && !blankSelected |
|||
|
|||
const templateChanged = newTemplateIndex => { |
|||
if (newTemplateIndex === undefined) return |
|||
draftScreen = templates[newTemplateIndex].create() |
|||
if (draftScreen.props._instanceName) { |
|||
name = draftScreen.props._instanceName |
|||
} |
|||
|
|||
if (draftScreen.props._component) { |
|||
baseComponent = draftScreen.props._component |
|||
} |
|||
|
|||
if (draftScreen.routing) { |
|||
route = draftScreen.routing.route |
|||
} |
|||
} |
|||
|
|||
const save = async () => { |
|||
if (!route) { |
|||
routeError = "URL is required" |
|||
$: templates = getTemplates($store, $tables.list) |
|||
const toggleScreenSelection = table => { |
|||
if (selectedScreens.find(s => s.name.includes(table.name))) { |
|||
selectedScreens = selectedScreens.filter( |
|||
screen => !screen.name.includes(table.name) |
|||
) |
|||
} else { |
|||
if (routeExists(route, roleId)) { |
|||
routeError = "This URL is already taken for this access role" |
|||
} else { |
|||
routeError = "" |
|||
} |
|||
templates = templates.filter(template => |
|||
template.name.includes(table.name) |
|||
) |
|||
selectedScreens = [...templates, ...selectedScreens] |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
if (routeError) return false |
|||
<ModalContent |
|||
title="Add screens" |
|||
confirmText="Add Screens" |
|||
cancelText="Cancel" |
|||
onConfirm={() => (autoSelected ? chooseModal(2) : chooseModal(1))} |
|||
disabled={!selectedScreens.length} |
|||
size="L" |
|||
> |
|||
<Body size="XS" |
|||
>Please select the screens you would like to add to your application. |
|||
Autogenerated screens come with CRUD functionality.</Body |
|||
> |
|||
|
|||
draftScreen.props._instanceName = name |
|||
draftScreen.props._component = baseComponent |
|||
draftScreen.routing = { route, roleId } |
|||
<Layout noPadding gap="S"> |
|||
<Detail size="S">Blank screen</Detail> |
|||
<div |
|||
class="item" |
|||
class:selected={selectedScreens.find(x => x.id.includes(blankScreen))} |
|||
on:click={() => |
|||
toggleScreenSelection(templates.find(t => t.id === blankScreen))} |
|||
class:disabled={autoSelected} |
|||
> |
|||
<div data-cy="blank-screen" class="content"> |
|||
<Body size="S">Blank</Body> |
|||
</div> |
|||
<div style="color: var(--spectrum-global-color-green-600); float: right"> |
|||
{#if selectedScreens.find(x => x.id === blankScreen)} |
|||
<Icon size="S" name="CheckmarkCircleOutline" /> |
|||
{/if} |
|||
</div> |
|||
</div> |
|||
<Detail size="S">Autogenerated Screens</Detail> |
|||
|
|||
await store.actions.screens.create(draftScreen) |
|||
if (createLink) { |
|||
await store.actions.components.links.save(route, name) |
|||
} |
|||
await store.actions.routing.fetch() |
|||
{#each $tables.list.filter(table => table.type !== "external") as table} |
|||
<div |
|||
class:disabled={blankSelected} |
|||
class:selected={selectedScreens.find(x => x.name.includes(table.name))} |
|||
on:click={() => toggleScreenSelection(table)} |
|||
class="item" |
|||
> |
|||
<div class="content"> |
|||
{table.name} |
|||
</div> |
|||
<div |
|||
style="color: var(--spectrum-global-color-green-600); float: right" |
|||
> |
|||
{#if selectedScreens.find(x => x.name.includes(table.name))} |
|||
<Icon size="S" name="CheckmarkCircleOutline" /> |
|||
{/if} |
|||
</div> |
|||
</div> |
|||
{/each} |
|||
</Layout> |
|||
</ModalContent> |
|||
|
|||
if (templateIndex !== undefined) { |
|||
const template = templates[templateIndex] |
|||
analytics.captureEvent(Events.SCREEN.CREATED, { |
|||
template: template.id || template.name, |
|||
}) |
|||
} |
|||
<style> |
|||
.disabled { |
|||
opacity: 0.3; |
|||
pointer-events: none; |
|||
} |
|||
|
|||
const routeExists = (route, roleId) => { |
|||
return $allScreens.some( |
|||
screen => |
|||
screen.routing.route.toLowerCase() === route.toLowerCase() && |
|||
screen.routing.roleId === roleId |
|||
) |
|||
.content { |
|||
letter-spacing: 0px; |
|||
color: #2c2c2c; |
|||
} |
|||
|
|||
const routeChanged = event => { |
|||
if (!event.detail.startsWith("/")) { |
|||
route = "/" + event.detail |
|||
} |
|||
route = sanitizeUrl(route) |
|||
.item { |
|||
cursor: pointer; |
|||
grid-gap: var(--spectrum-alias-grid-margin-xsmall); |
|||
padding: var(--spectrum-alias-item-padding-s); |
|||
background: var(--background); |
|||
transition: 0.3s all; |
|||
border: solid var(--spectrum-alias-border-color); |
|||
border-radius: 2px; |
|||
box-sizing: border-box; |
|||
border-width: 1px; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
height: 60px; |
|||
} |
|||
</script> |
|||
|
|||
<ModalContent title="New Screen" confirmText="Create Screen" onConfirm={save}> |
|||
<Select |
|||
label="Choose a Template" |
|||
bind:value={templateIndex} |
|||
on:change={ev => templateChanged(ev.detail)} |
|||
options={templates} |
|||
placeholder={null} |
|||
getOptionLabel={x => x.name} |
|||
getOptionValue={(x, idx) => idx} |
|||
/> |
|||
<Input label="Name" bind:value={name} /> |
|||
<Input |
|||
label="Url" |
|||
error={routeError} |
|||
bind:value={route} |
|||
on:change={routeChanged} |
|||
/> |
|||
<Select |
|||
label="Access" |
|||
bind:value={roleId} |
|||
options={$roles} |
|||
getOptionLabel={x => x.name} |
|||
getOptionValue={x => x._id} |
|||
/> |
|||
<Toggle text="Create link in navigation bar" bind:value={createLink} /> |
|||
</ModalContent> |
|||
.item:hover, |
|||
.selected { |
|||
background: var(--spectrum-alias-background-color-tertiary); |
|||
} |
|||
</style> |
|||
|
|||
@ -0,0 +1,51 @@ |
|||
<script> |
|||
import { ModalContent, Input } from "@budibase/bbui" |
|||
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl" |
|||
import { selectedAccessRole, allScreens } from "builderStore" |
|||
|
|||
export let screenName |
|||
export let url |
|||
export let chooseModal |
|||
|
|||
let routeError |
|||
let roleId = $selectedAccessRole || "BASIC" |
|||
|
|||
const routeChanged = event => { |
|||
if (!event.detail.startsWith("/")) { |
|||
url = "/" + event.detail |
|||
} |
|||
url = sanitizeUrl(url) |
|||
|
|||
if (routeExists(url, roleId)) { |
|||
routeError = "This URL is already taken for this access role" |
|||
} else { |
|||
routeError = "" |
|||
} |
|||
} |
|||
|
|||
const routeExists = (url, roleId) => { |
|||
return $allScreens.some( |
|||
screen => |
|||
screen.routing.route.toLowerCase() === url.toLowerCase() && |
|||
screen.routing.roleId === roleId |
|||
) |
|||
} |
|||
</script> |
|||
|
|||
<ModalContent |
|||
size="M" |
|||
title={"Enter details"} |
|||
confirmText={"Continue"} |
|||
onCancel={() => chooseModal(0)} |
|||
onConfirm={() => chooseModal(2)} |
|||
cancelText={"Back"} |
|||
disabled={!screenName || !url || routeError} |
|||
> |
|||
<Input label="Name" bind:value={screenName} /> |
|||
<Input |
|||
label="URL" |
|||
error={routeError} |
|||
bind:value={url} |
|||
on:change={routeChanged} |
|||
/> |
|||
</ModalContent> |
|||
@ -0,0 +1,48 @@ |
|||
<script> |
|||
import NavigationSelectionModal from "components/design/NavigationPanel/NavigationSelectionModal.svelte" |
|||
import ScreenDetailsModal from "components/design/NavigationPanel/ScreenDetailsModal.svelte" |
|||
import NewScreenModal from "components/design/NavigationPanel/NewScreenModal.svelte" |
|||
import { Modal } from "@budibase/bbui" |
|||
|
|||
let newScreenModal |
|||
let navigationSelectionModal |
|||
let screenDetailsModal |
|||
let screenName = "" |
|||
let url = "" |
|||
let selectedScreens = [] |
|||
|
|||
export const showModal = () => { |
|||
newScreenModal.show() |
|||
} |
|||
|
|||
const chooseModal = index => { |
|||
/* |
|||
0 = newScreenModal |
|||
1 = screenDetailsModal |
|||
2 = navigationSelectionModal |
|||
*/ |
|||
if (index === 0) { |
|||
newScreenModal.show() |
|||
} else if (index === 1) { |
|||
screenDetailsModal.show() |
|||
} else if (index === 2) { |
|||
navigationSelectionModal.show() |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<Modal bind:this={newScreenModal}> |
|||
<NewScreenModal bind:selectedScreens {chooseModal} /> |
|||
</Modal> |
|||
|
|||
<Modal bind:this={screenDetailsModal}> |
|||
<ScreenDetailsModal bind:screenName bind:url {chooseModal} /> |
|||
</Modal> |
|||
<Modal bind:this={navigationSelectionModal}> |
|||
<NavigationSelectionModal |
|||
bind:url |
|||
bind:screenName |
|||
bind:selectedScreens |
|||
{chooseModal} |
|||
/> |
|||
</Modal> |
|||
Loading…
Reference in new issue