forked from tsai/budibase
10 changed files with 208 additions and 13 deletions
@ -0,0 +1,26 @@ |
|||||
|
<script> |
||||
|
import { store } from "builderStore" |
||||
|
import { onDestroy } from "svelte" |
||||
|
import { syncURLToState } from "helpers/urlStateSync" |
||||
|
import { goto, params, redirect } from "@roxi/routify" |
||||
|
|
||||
|
// Keep URL and state in sync for selected screen ID |
||||
|
const stopSyncing = syncURLToState({ |
||||
|
keys: [ |
||||
|
{ |
||||
|
url: "screenId", |
||||
|
state: "selectedScreenId", |
||||
|
validate: screenId => $store.screens.some(x => x._id === screenId), |
||||
|
fallbackUrl: "../", |
||||
|
}, |
||||
|
], |
||||
|
store, |
||||
|
params, |
||||
|
goto, |
||||
|
redirect, |
||||
|
}) |
||||
|
|
||||
|
onDestroy(stopSyncing) |
||||
|
</script> |
||||
|
|
||||
|
<slot /> |
||||
@ -0,0 +1,15 @@ |
|||||
|
<script> |
||||
|
import { selectedScreen } from "builderStore" |
||||
|
import { onMount } from "svelte" |
||||
|
import { redirect } from "@roxi/routify" |
||||
|
|
||||
|
onMount(() => { |
||||
|
if ($selectedScreen) { |
||||
|
// Select the screen slot if a screen exists |
||||
|
$redirect(`./slot`) |
||||
|
} else { |
||||
|
// Otherwise go up so we can select a new valid screen |
||||
|
$redirect("../") |
||||
|
} |
||||
|
}) |
||||
|
</script> |
||||
@ -1,21 +1,143 @@ |
|||||
<script> |
<script> |
||||
import NavigationPanel from "components/design/NavigationPanel/NavigationPanel.svelte" |
import NavigationPanel from "components/design/NavigationPanel/NavigationPanel.svelte" |
||||
import { goto } from "@roxi/routify" |
import { goto } from "@roxi/routify" |
||||
import { Layout, Search } from "@budibase/bbui" |
import { |
||||
|
Layout, |
||||
|
ActionGroup, |
||||
|
ActionButton, |
||||
|
Search, |
||||
|
DetailSummary, |
||||
|
Icon, |
||||
|
Body, |
||||
|
} from "@budibase/bbui" |
||||
|
import structure from "./componentStructure.json" |
||||
|
import { store } from "builderStore" |
||||
|
|
||||
|
let section = "components" |
||||
let searchString |
let searchString |
||||
|
|
||||
|
$: enrichedStructure = enrichStructure(structure, $store.components) |
||||
|
$: filteredStructure = filterStructure( |
||||
|
enrichedStructure, |
||||
|
section, |
||||
|
searchString |
||||
|
) |
||||
|
|
||||
|
const enrichStructure = (structure, definitions) => { |
||||
|
let enrichedStructure = [] |
||||
|
structure.forEach(item => { |
||||
|
if (typeof item === "string") { |
||||
|
const def = definitions[`@budibase/standard-components/${item}`] |
||||
|
if (def) { |
||||
|
enrichedStructure.push({ |
||||
|
...def, |
||||
|
isCategory: false, |
||||
|
}) |
||||
|
} |
||||
|
} else { |
||||
|
enrichedStructure.push({ |
||||
|
...item, |
||||
|
isCategory: true, |
||||
|
children: enrichStructure(item.children || [], definitions), |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
return enrichedStructure |
||||
|
} |
||||
|
|
||||
|
const filterStructure = (structure, section, search) => { |
||||
|
if (!structure?.length) { |
||||
|
return [] |
||||
|
} |
||||
|
|
||||
|
if (section === "components") { |
||||
|
structure = structure.filter(category => category.name !== "Blocks") |
||||
|
} else { |
||||
|
structure = structure.filter(category => category.name === "Blocks") |
||||
|
} |
||||
|
if (search) { |
||||
|
let filteredStructure = [] |
||||
|
structure.forEach(category => { |
||||
|
let matchedChildren = category.children.filter(child => { |
||||
|
return child.name.toLowerCase().includes(search.toLowerCase()) |
||||
|
}) |
||||
|
if (matchedChildren.length) { |
||||
|
filteredStructure.push({ |
||||
|
...category, |
||||
|
children: matchedChildren, |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
structure = filteredStructure |
||||
|
} |
||||
|
return structure |
||||
|
} |
||||
|
|
||||
|
const addComponent = () => {} |
||||
</script> |
</script> |
||||
|
|
||||
<NavigationPanel |
<NavigationPanel |
||||
title="Components" |
title="Add component" |
||||
showAddButton |
showBackButton |
||||
onClickAddButton={() => $goto("../new")} |
onClickBackButton={() => $goto("../slot")} |
||||
|
wide |
||||
> |
> |
||||
<Layout paddingX="L" paddingY="XL" gap="S"> |
<Layout paddingX="L" paddingY="XL" gap="S"> |
||||
|
<ActionGroup compact justified> |
||||
|
<ActionButton |
||||
|
fullWidth |
||||
|
selected={section === "components"} |
||||
|
on:click={() => (section = "components")}>Components</ActionButton |
||||
|
> |
||||
|
<ActionButton |
||||
|
fullWidth |
||||
|
selected={section === "blocks"} |
||||
|
on:click={() => (section = "blocks")}>Blocks</ActionButton |
||||
|
> |
||||
|
</ActionGroup> |
||||
<Search |
<Search |
||||
placeholder="Search" |
placeholder="Search" |
||||
value={searchString} |
value={searchString} |
||||
on:change={e => (searchString = e.detail)} |
on:change={e => (searchString = e.detail)} |
||||
/> |
/> |
||||
</Layout> |
</Layout> |
||||
|
{#each filteredStructure as category} |
||||
|
<DetailSummary name={category.name} collapsible={false}> |
||||
|
<div class="component-grid"> |
||||
|
{#each category.children as component} |
||||
|
<div class="component" on:click={() => addComponent(component)}> |
||||
|
<Icon name={component.icon} /> |
||||
|
<Body size="XS">{component.name}</Body> |
||||
|
</div> |
||||
|
{/each} |
||||
|
</div> |
||||
|
</DetailSummary> |
||||
|
{/each} |
||||
</NavigationPanel> |
</NavigationPanel> |
||||
|
|
||||
|
<style> |
||||
|
.component-grid { |
||||
|
display: grid; |
||||
|
grid-template-columns: repeat(4, minmax(0, 1fr)); |
||||
|
gap: var(--spacing-s); |
||||
|
} |
||||
|
.component { |
||||
|
background-color: var(--spectrum-global-color-gray-200); |
||||
|
border-radius: 4px; |
||||
|
height: 72px; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
text-align: center; |
||||
|
padding: 0 var(--spacing-s); |
||||
|
gap: var(--spacing-s); |
||||
|
padding-top: 4px; |
||||
|
transition: background 130ms ease-out; |
||||
|
line-height: 1.2; |
||||
|
} |
||||
|
.component:hover { |
||||
|
cursor: pointer; |
||||
|
background: var(--spectrum-alias-background-color-tertiary); |
||||
|
} |
||||
|
</style> |
||||
|
|||||
@ -0,0 +1,7 @@ |
|||||
|
<script> |
||||
|
import ComponentListPanel from "./_components/ComponentListPanel.svelte" |
||||
|
import AppPanel from "components/design/AppPanel/AppPanel.svelte" |
||||
|
</script> |
||||
|
|
||||
|
<ComponentListPanel /> |
||||
|
<AppPanel /> |
||||
@ -1 +1,19 @@ |
|||||
Components |
<script> |
||||
|
import { store } from "builderStore" |
||||
|
import { onMount } from "svelte" |
||||
|
import { redirect } from "@roxi/routify" |
||||
|
|
||||
|
let loaded = false |
||||
|
|
||||
|
onMount(() => { |
||||
|
if ($store.screens?.length) { |
||||
|
$redirect(`./${$store.screens[0]._id}`) |
||||
|
} else { |
||||
|
loaded = true |
||||
|
} |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
{#if loaded} |
||||
|
You need to create a screen! |
||||
|
{/if} |
||||
|
|||||
Loading…
Reference in new issue