forked from tsai/budibase
18 changed files with 767 additions and 126 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> |
<script> |
||||
import { store, allScreens, selectedAccessRole } from "builderStore" |
import { store } from "builderStore" |
||||
import { tables } from "stores/backend" |
import { tables } from "stores/backend" |
||||
import { roles } from "stores/backend" |
import { ModalContent, Body, Detail, Layout, Icon } from "@budibase/bbui" |
||||
import { Input, Select, ModalContent, Toggle } from "@budibase/bbui" |
|
||||
import getTemplates from "builderStore/store/screenTemplates" |
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 = "" |
const blankScreen = "createFromScratch" |
||||
let routeError |
|
||||
let baseComponent = CONTAINER |
|
||||
let templateIndex |
|
||||
let draftScreen |
|
||||
let createLink = true |
|
||||
let roleId = $selectedAccessRole || "BASIC" |
|
||||
|
|
||||
$: templates = getTemplates($store, $tables.list) |
$: blankSelected = selectedScreens?.length === 1 |
||||
$: route = !route && $allScreens.length === 0 ? "*" : route |
$: autoSelected = selectedScreens?.length > 0 && !blankSelected |
||||
$: { |
|
||||
if (templates && templateIndex === undefined) { |
|
||||
templateIndex = 0 |
|
||||
templateChanged(0) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
const templateChanged = newTemplateIndex => { |
$: templates = getTemplates($store, $tables.list) |
||||
if (newTemplateIndex === undefined) return |
const toggleScreenSelection = table => { |
||||
draftScreen = templates[newTemplateIndex].create() |
if (selectedScreens.find(s => s.name.includes(table.name))) { |
||||
if (draftScreen.props._instanceName) { |
selectedScreens = selectedScreens.filter( |
||||
name = draftScreen.props._instanceName |
screen => !screen.name.includes(table.name) |
||||
} |
) |
||||
|
|
||||
if (draftScreen.props._component) { |
|
||||
baseComponent = draftScreen.props._component |
|
||||
} |
|
||||
|
|
||||
if (draftScreen.routing) { |
|
||||
route = draftScreen.routing.route |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
const save = async () => { |
|
||||
if (!route) { |
|
||||
routeError = "URL is required" |
|
||||
} else { |
} else { |
||||
if (routeExists(route, roleId)) { |
templates = templates.filter(template => |
||||
routeError = "This URL is already taken for this access role" |
template.name.includes(table.name) |
||||
} else { |
) |
||||
routeError = "" |
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 |
<Layout noPadding gap="S"> |
||||
draftScreen.props._component = baseComponent |
<Detail size="S">Blank screen</Detail> |
||||
draftScreen.routing = { route, roleId } |
<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) |
{#each $tables.list.filter(table => table.type !== "external") as table} |
||||
if (createLink) { |
<div |
||||
await store.actions.components.links.save(route, name) |
class:disabled={blankSelected} |
||||
} |
class:selected={selectedScreens.find(x => x.name.includes(table.name))} |
||||
await store.actions.routing.fetch() |
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) { |
<style> |
||||
const template = templates[templateIndex] |
.disabled { |
||||
analytics.captureEvent(Events.SCREEN.CREATED, { |
opacity: 0.3; |
||||
template: template.id || template.name, |
pointer-events: none; |
||||
}) |
|
||||
} |
|
||||
} |
} |
||||
|
|
||||
const routeExists = (route, roleId) => { |
.content { |
||||
return $allScreens.some( |
letter-spacing: 0px; |
||||
screen => |
color: #2c2c2c; |
||||
screen.routing.route.toLowerCase() === route.toLowerCase() && |
|
||||
screen.routing.roleId === roleId |
|
||||
) |
|
||||
} |
} |
||||
|
.item { |
||||
const routeChanged = event => { |
cursor: pointer; |
||||
if (!event.detail.startsWith("/")) { |
grid-gap: var(--spectrum-alias-grid-margin-xsmall); |
||||
route = "/" + event.detail |
padding: var(--spectrum-alias-item-padding-s); |
||||
} |
background: var(--background); |
||||
route = sanitizeUrl(route) |
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}> |
.item:hover, |
||||
<Select |
.selected { |
||||
label="Choose a Template" |
background: var(--spectrum-alias-background-color-tertiary); |
||||
bind:value={templateIndex} |
} |
||||
on:change={ev => templateChanged(ev.detail)} |
</style> |
||||
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> |
|
||||
|
|||||
@ -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