mirror of https://github.com/Budibase/budibase.git
143 changed files with 2969 additions and 5550 deletions
@ -1,54 +1,60 @@ |
|||
xcontext('Create Components', () => { |
|||
xcontext("Create Components", () => { |
|||
before(() => { |
|||
cy.server() |
|||
cy.visit("localhost:4001/_builder") |
|||
// https://on.cypress.io/type
|
|||
cy.createApp("Table App", "Table App Description") |
|||
cy.createTable("dog", "name", "age") |
|||
cy.addRow("bob", "15") |
|||
}) |
|||
|
|||
before(() => { |
|||
cy.server() |
|||
cy.visit('localhost:4001/_builder') |
|||
// https://on.cypress.io/type
|
|||
cy.createApp('Table App', 'Table App Description') |
|||
cy.createTable('dog', 'name', 'age') |
|||
cy.addRow('bob', '15') |
|||
}) |
|||
// https://on.cypress.io/interacting-with-elements
|
|||
it("should add a container", () => { |
|||
cy.navigateToFrontend() |
|||
cy.get(".switcher > :nth-child(2)").click() |
|||
cy.contains("Container").click() |
|||
}) |
|||
it("should add a headline", () => { |
|||
cy.addHeadlineComponent("An Amazing headline!") |
|||
|
|||
// https://on.cypress.io/interacting-with-elements
|
|||
it('should add a container', () => { |
|||
cy.contains('frontend').click() |
|||
cy.get('.switcher > :nth-child(2)').click() |
|||
getIframeBody().contains("An Amazing headline!") |
|||
}) |
|||
it("change the font size of the headline", () => { |
|||
cy.contains("Typography").click() |
|||
cy.get("[data-cy=font-size-prop-control]").click() |
|||
cy.contains("60px").click() |
|||
cy.contains("Design").click() |
|||
|
|||
cy.contains('Container').click() |
|||
}) |
|||
it('should add a headline', () => { |
|||
cy.addHeadlineComponent('An Amazing headline!') |
|||
|
|||
getIframeBody().contains('An Amazing headline!') |
|||
}) |
|||
it('change the font size of the headline', () => { |
|||
cy.contains('Typography').click() |
|||
cy.get('[data-cy=font-size-prop-control]').click() |
|||
cy.contains("60px").click() |
|||
cy.contains('Design').click() |
|||
|
|||
getIframeBody().contains('An Amazing headline!').should('have.css', 'font-size', '60px') |
|||
}) |
|||
getIframeBody() |
|||
.contains("An Amazing headline!") |
|||
.should("have.css", "font-size", "60px") |
|||
}) |
|||
}) |
|||
|
|||
const getIframeDocument = () => { |
|||
return cy |
|||
.get('iframe') |
|||
// Cypress yields jQuery element, which has the real
|
|||
// DOM element under property "0".
|
|||
// From the real DOM iframe element we can get
|
|||
// the "document" element, it is stored in "contentDocument" property
|
|||
// Cypress "its" command can access deep properties using dot notation
|
|||
// https://on.cypress.io/its
|
|||
.its('0.contentDocument').should('exist') |
|||
return ( |
|||
cy |
|||
.get("iframe") |
|||
// Cypress yields jQuery element, which has the real
|
|||
// DOM element under property "0".
|
|||
// From the real DOM iframe element we can get
|
|||
// the "document" element, it is stored in "contentDocument" property
|
|||
// Cypress "its" command can access deep properties using dot notation
|
|||
// https://on.cypress.io/its
|
|||
.its("0.contentDocument") |
|||
.should("exist") |
|||
) |
|||
} |
|||
|
|||
const getIframeBody = () => { |
|||
// get the document
|
|||
return getIframeDocument() |
|||
// automatically retries until body is loaded
|
|||
.its('body').should('not.be.undefined') |
|||
// wraps "body" DOM element to allow
|
|||
// chaining more Cypress commands, like ".find(...)"
|
|||
.then(cy.wrap) |
|||
} |
|||
// get the document
|
|||
return ( |
|||
getIframeDocument() |
|||
// automatically retries until body is loaded
|
|||
.its("body") |
|||
.should("not.be.undefined") |
|||
// wraps "body" DOM element to allow
|
|||
// chaining more Cypress commands, like ".find(...)"
|
|||
.then(cy.wrap) |
|||
) |
|||
} |
|||
|
|||
@ -0,0 +1,136 @@ |
|||
<script> |
|||
import { sortBy } from "lodash/fp" |
|||
import { automationStore } from "builderStore" |
|||
import { DropdownMenu, Modal } from "@budibase/bbui" |
|||
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns" |
|||
import analytics from "analytics" |
|||
import CreateWebhookModal from "../Shared/CreateWebhookModal.svelte" |
|||
|
|||
$: hasTrigger = $automationStore.selectedAutomation.hasTrigger() |
|||
$: tabs = [ |
|||
{ |
|||
label: "Trigger", |
|||
value: "TRIGGER", |
|||
icon: "ri-organization-chart", |
|||
disabled: hasTrigger, |
|||
}, |
|||
{ |
|||
label: "Action", |
|||
value: "ACTION", |
|||
icon: "ri-flow-chart", |
|||
disabled: !hasTrigger, |
|||
}, |
|||
{ |
|||
label: "Logic", |
|||
value: "LOGIC", |
|||
icon: "ri-filter-line", |
|||
disabled: !hasTrigger, |
|||
}, |
|||
] |
|||
|
|||
let selectedIndex |
|||
let anchors = [] |
|||
let popover |
|||
let webhookModal |
|||
$: selectedTab = selectedIndex == null ? null : tabs[selectedIndex].value |
|||
$: anchor = selectedIndex === -1 ? null : anchors[selectedIndex] |
|||
$: blocks = sortBy(entry => entry[1].name)( |
|||
Object.entries($automationStore.blockDefinitions[selectedTab] ?? {}) |
|||
) |
|||
|
|||
function onChangeTab(idx) { |
|||
selectedIndex = idx |
|||
popover.show() |
|||
} |
|||
|
|||
function closePopover() { |
|||
selectedIndex = null |
|||
popover.hide() |
|||
} |
|||
|
|||
function addBlockToAutomation(stepId, blockDefinition) { |
|||
const newBlock = { |
|||
...blockDefinition, |
|||
inputs: blockDefinition.inputs || {}, |
|||
stepId, |
|||
type: selectedTab, |
|||
} |
|||
automationStore.actions.addBlockToAutomation(newBlock) |
|||
analytics.captureEvent("Added Automation Block", { |
|||
name: blockDefinition.name, |
|||
}) |
|||
closePopover() |
|||
if (stepId === "WEBHOOK") { |
|||
webhookModal.show() |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<div class="tab-container"> |
|||
{#each tabs as tab, idx} |
|||
<div |
|||
bind:this={anchors[idx]} |
|||
class="tab" |
|||
class:disabled={tab.disabled} |
|||
on:click={tab.disabled ? null : () => onChangeTab(idx)} |
|||
class:active={idx === selectedIndex}> |
|||
{#if tab.icon}<i class={tab.icon} />{/if} |
|||
<span>{tab.label}</span> |
|||
<i class="ri-arrow-down-s-line arrow" /> |
|||
</div> |
|||
{/each} |
|||
</div> |
|||
<DropdownMenu |
|||
on:close={() => (selectedIndex = null)} |
|||
bind:this={popover} |
|||
{anchor} |
|||
align="left"> |
|||
<DropdownContainer> |
|||
{#each blocks as [stepId, blockDefinition]} |
|||
<DropdownItem |
|||
icon={blockDefinition.icon} |
|||
title={blockDefinition.name} |
|||
subtitle={blockDefinition.description} |
|||
on:click={() => addBlockToAutomation(stepId, blockDefinition)} /> |
|||
{/each} |
|||
</DropdownContainer> |
|||
</DropdownMenu> |
|||
<Modal bind:this={webhookModal} width="30%"> |
|||
<CreateWebhookModal /> |
|||
</Modal> |
|||
|
|||
<style> |
|||
.tab-container { |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: flex-start; |
|||
align-items: center; |
|||
gap: var(--spacing-l); |
|||
min-height: 24px; |
|||
} |
|||
|
|||
.tab { |
|||
color: var(--grey-7); |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: flex-start; |
|||
align-items: center; |
|||
gap: var(--spacing-xs); |
|||
font-size: var(--font-size-xs); |
|||
} |
|||
.tab span { |
|||
font-weight: 500; |
|||
user-select: none; |
|||
} |
|||
.tab.active, |
|||
.tab:not(.disabled):hover { |
|||
color: var(--ink); |
|||
cursor: pointer; |
|||
} |
|||
.tab.disabled { |
|||
color: var(--grey-5); |
|||
} |
|||
.tab i:not(:last-child) { |
|||
font-size: 16px; |
|||
} |
|||
</style> |
|||
|
Before Width: | Height: | Size: 303 B After Width: | Height: | Size: 303 B |
@ -0,0 +1,34 @@ |
|||
<script> |
|||
import { onMount } from "svelte" |
|||
import { automationStore } from "builderStore" |
|||
import NavItem from "components/common/NavItem.svelte" |
|||
import EditAutomationPopover from "./EditAutomationPopover.svelte" |
|||
|
|||
$: selectedAutomationId = $automationStore.selectedAutomation?.automation?._id |
|||
|
|||
onMount(() => { |
|||
automationStore.actions.fetch() |
|||
}) |
|||
</script> |
|||
|
|||
<div class="automations-list"> |
|||
{#each $automationStore.automations as automation, idx} |
|||
<NavItem |
|||
border={idx > 0} |
|||
icon="ri-stackshare-line" |
|||
text={automation.name} |
|||
selected={automation._id === selectedAutomationId} |
|||
on:click={() => automationStore.actions.select(automation)}> |
|||
<EditAutomationPopover {automation} /> |
|||
</NavItem> |
|||
{/each} |
|||
</div> |
|||
|
|||
<style> |
|||
.automations-list { |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: flex-start; |
|||
align-items: stretch; |
|||
} |
|||
</style> |
|||
@ -1,79 +0,0 @@ |
|||
<script> |
|||
import { onMount } from "svelte" |
|||
import { automationStore } from "builderStore" |
|||
import CreateAutomationModal from "./CreateAutomationModal.svelte" |
|||
import { Button, Modal } from "@budibase/bbui" |
|||
|
|||
let modal |
|||
|
|||
$: selectedAutomationId = $automationStore.selectedAutomation?.automation?._id |
|||
|
|||
onMount(() => { |
|||
automationStore.actions.fetch() |
|||
}) |
|||
</script> |
|||
|
|||
<section> |
|||
<Button primary wide on:click={modal.show}>Create New Automation</Button> |
|||
<ul> |
|||
{#each $automationStore.automations as automation} |
|||
<li |
|||
class="automation-item" |
|||
class:selected={automation._id === selectedAutomationId} |
|||
on:click={() => automationStore.actions.select(automation)}> |
|||
<i class="ri-stackshare-line" class:live={automation.live} /> |
|||
{automation.name} |
|||
</li> |
|||
{/each} |
|||
</ul> |
|||
</section> |
|||
<Modal bind:this={modal}> |
|||
<CreateAutomationModal /> |
|||
</Modal> |
|||
|
|||
<style> |
|||
section { |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
ul { |
|||
list-style-type: none; |
|||
padding: 0; |
|||
margin: var(--spacing-xl) 0 0 0; |
|||
flex: 1; |
|||
} |
|||
|
|||
i { |
|||
color: var(--grey-6); |
|||
} |
|||
i.live { |
|||
color: var(--ink); |
|||
} |
|||
|
|||
li { |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.automation-item { |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: flex-start; |
|||
align-items: center; |
|||
border-radius: var(--border-radius-m); |
|||
padding: var(--spacing-s) var(--spacing-m); |
|||
margin-bottom: var(--spacing-xs); |
|||
color: var(--ink); |
|||
} |
|||
.automation-item i { |
|||
font-size: 24px; |
|||
margin-right: var(--spacing-m); |
|||
} |
|||
.automation-item:hover { |
|||
cursor: pointer; |
|||
background: var(--grey-1); |
|||
} |
|||
.automation-item.selected { |
|||
background: var(--grey-2); |
|||
} |
|||
</style> |
|||
@ -1,82 +0,0 @@ |
|||
<script> |
|||
import { backendUiStore, automationStore } from "builderStore" |
|||
import CreateWebookModal from "../../Shared/CreateWebhookModal.svelte" |
|||
import analytics from "analytics" |
|||
import { Modal } from "@budibase/bbui" |
|||
|
|||
export let blockDefinition |
|||
export let stepId |
|||
export let blockType |
|||
|
|||
let modal |
|||
|
|||
$: instanceId = $backendUiStore.selectedDatabase._id |
|||
$: automation = $automationStore.selectedAutomation?.automation |
|||
|
|||
function addBlockToAutomation() { |
|||
automationStore.actions.addBlockToAutomation({ |
|||
...blockDefinition, |
|||
inputs: blockDefinition.inputs || {}, |
|||
stepId, |
|||
type: blockType, |
|||
}) |
|||
if (stepId === "WEBHOOK") { |
|||
modal.show() |
|||
} |
|||
analytics.captureEvent("Added Automation Block", { |
|||
name: blockDefinition.name, |
|||
}) |
|||
} |
|||
</script> |
|||
|
|||
<div |
|||
class="automation-block hoverable" |
|||
on:click={addBlockToAutomation} |
|||
data-cy={stepId}> |
|||
<div><i class={blockDefinition.icon} /></div> |
|||
<div class="automation-text"> |
|||
<h4>{blockDefinition.name}</h4> |
|||
<p>{blockDefinition.description}</p> |
|||
</div> |
|||
</div> |
|||
<Modal bind:this={modal} width="30%"> |
|||
<CreateWebookModal /> |
|||
</Modal> |
|||
|
|||
<style> |
|||
.automation-block { |
|||
display: grid; |
|||
grid-template-columns: 20px auto; |
|||
align-items: center; |
|||
margin-top: var(--spacing-s); |
|||
padding: var(--spacing-m); |
|||
border-radius: var(--border-radius-m); |
|||
} |
|||
.automation-block:hover { |
|||
background-color: var(--grey-1); |
|||
} |
|||
.automation-block:first-child { |
|||
margin-top: 0; |
|||
} |
|||
|
|||
i { |
|||
color: var(--grey-7); |
|||
font-size: 20px; |
|||
} |
|||
|
|||
.automation-text { |
|||
margin-left: 16px; |
|||
} |
|||
.automation-text h4 { |
|||
font-size: 14px; |
|||
font-weight: 500; |
|||
margin-bottom: 5px; |
|||
margin-top: 0; |
|||
} |
|||
.automation-text p { |
|||
font-size: 12px; |
|||
line-height: 1.4; |
|||
color: var(--grey-7); |
|||
margin: 0; |
|||
} |
|||
</style> |
|||
@ -1,48 +0,0 @@ |
|||
<script> |
|||
import { sortBy } from "lodash/fp" |
|||
import { automationStore } from "builderStore" |
|||
import AutomationBlock from "./AutomationBlock.svelte" |
|||
import FlatButtonGroup from "components/userInterface/FlatButtonGroup.svelte" |
|||
|
|||
let selectedTab = "TRIGGER" |
|||
let buttonProps = [] |
|||
$: blocks = sortBy(entry => entry[1].name)( |
|||
Object.entries($automationStore.blockDefinitions[selectedTab]) |
|||
) |
|||
|
|||
$: { |
|||
if ($automationStore.selectedAutomation.hasTrigger()) { |
|||
buttonProps = [ |
|||
{ value: "ACTION", text: "Action" }, |
|||
{ value: "LOGIC", text: "Logic" }, |
|||
] |
|||
if (selectedTab === "TRIGGER") { |
|||
selectedTab = "ACTION" |
|||
} |
|||
} else { |
|||
buttonProps = [{ value: "TRIGGER", text: "Trigger" }] |
|||
if (selectedTab !== "TRIGGER") { |
|||
selectedTab = "TRIGGER" |
|||
} |
|||
} |
|||
} |
|||
|
|||
function onChangeTab(tab) { |
|||
selectedTab = tab |
|||
} |
|||
</script> |
|||
|
|||
<section> |
|||
<FlatButtonGroup value={selectedTab} {buttonProps} onChange={onChangeTab} /> |
|||
<div id="blocklist"> |
|||
{#each blocks as [stepId, blockDefinition]} |
|||
<AutomationBlock {blockDefinition} {stepId} blockType={selectedTab} /> |
|||
{/each} |
|||
</div> |
|||
</section> |
|||
|
|||
<style> |
|||
#blocklist { |
|||
margin-top: var(--spacing-xl); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,88 @@ |
|||
<script> |
|||
import { automationStore, backendUiStore } from "builderStore" |
|||
import { notifier } from "builderStore/store/notifications" |
|||
import { DropdownMenu } from "@budibase/bbui" |
|||
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns" |
|||
import ConfirmDialog from "components/common/ConfirmDialog.svelte" |
|||
|
|||
export let automation |
|||
|
|||
let anchor |
|||
let dropdown |
|||
let confirmDeleteDialog |
|||
$: instanceId = $backendUiStore.selectedDatabase._id |
|||
|
|||
function showModal() { |
|||
dropdown.hide() |
|||
confirmDeleteDialog.show() |
|||
} |
|||
|
|||
async function deleteAutomation() { |
|||
await automationStore.actions.delete({ |
|||
instanceId, |
|||
automation, |
|||
}) |
|||
notifier.success("Automation deleted.") |
|||
} |
|||
</script> |
|||
|
|||
<div on:click|stopPropagation> |
|||
<div bind:this={anchor} class="icon" on:click={dropdown.show}> |
|||
<i class="ri-more-line" /> |
|||
</div> |
|||
<DropdownMenu align="left" {anchor} bind:this={dropdown}> |
|||
<DropdownContainer> |
|||
<DropdownItem |
|||
icon="ri-delete-bin-line" |
|||
title="Delete" |
|||
on:click={showModal} /> |
|||
</DropdownContainer> |
|||
</DropdownMenu> |
|||
</div> |
|||
<ConfirmDialog |
|||
bind:this={confirmDeleteDialog} |
|||
okText="Delete Automation" |
|||
onOk={deleteAutomation} |
|||
title="Confirm Deletion"> |
|||
Are you sure you wish to delete the automation |
|||
<i>{automation.name}?</i> |
|||
This action cannot be undone. |
|||
</ConfirmDialog> |
|||
|
|||
<style> |
|||
div.icon { |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: flex-end; |
|||
align-items: center; |
|||
} |
|||
|
|||
div.icon i { |
|||
font-size: 16px; |
|||
} |
|||
|
|||
ul { |
|||
list-style: none; |
|||
margin: 0; |
|||
padding: var(--spacing-s) 0; |
|||
} |
|||
|
|||
li { |
|||
display: flex; |
|||
font-family: var(--font-sans); |
|||
font-size: var(--font-size-xs); |
|||
color: var(--ink); |
|||
padding: var(--spacing-s) var(--spacing-m); |
|||
margin: auto 0; |
|||
align-items: center; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
li:hover { |
|||
background-color: var(--grey-2); |
|||
} |
|||
|
|||
li:active { |
|||
color: var(--blue); |
|||
} |
|||
</style> |
|||
@ -1,3 +0,0 @@ |
|||
export { default as AutomationPanel } from "./AutomationPanel.svelte" |
|||
export { default as BlockList } from "./BlockList/BlockList.svelte" |
|||
export { default as AutomationList } from "./AutomationList/AutomationList.svelte" |
|||
@ -1 +0,0 @@ |
|||
export { default as SetupPanel } from "./SetupPanel.svelte" |
|||
@ -1,3 +0,0 @@ |
|||
export { default as AutomationBuilder } from "./AutomationBuilder/AutomationBuilder.svelte" |
|||
export { default as SetupPanel } from "./SetupPanel/SetupPanel.svelte" |
|||
export { default as AutomationPanel } from "./AutomationPanel/AutomationPanel.svelte" |
|||
@ -1,70 +0,0 @@ |
|||
<script> |
|||
export let disabled = false |
|||
export let hidden = false |
|||
export let secondary = false |
|||
export let primary = true |
|||
export let cancel = false |
|||
export let alert = false |
|||
export let warning = false |
|||
</script> |
|||
|
|||
<button |
|||
on:click |
|||
class="button" |
|||
class:hidden |
|||
class:secondary |
|||
class:primary |
|||
class:alert |
|||
class:cancel |
|||
class:warning |
|||
{disabled}> |
|||
<slot /> |
|||
</button> |
|||
|
|||
<style> |
|||
.primary { |
|||
color: #ffffff; |
|||
background: var(--blue); |
|||
border: solid 1px var(--blue); |
|||
} |
|||
|
|||
.alert { |
|||
color: white; |
|||
background: #e26d69; |
|||
border: solid 1px #e26d69; |
|||
} |
|||
|
|||
.cancel { |
|||
color: var(--secondary40); |
|||
background: none; |
|||
} |
|||
|
|||
.secondary { |
|||
color: var(--ink); |
|||
border: solid 1px var(--grey-4); |
|||
background: white; |
|||
} |
|||
|
|||
.button { |
|||
font-size: 14px; |
|||
font-weight: 500; |
|||
border-radius: 3px; |
|||
padding: 10px 20px; |
|||
height: 40px; |
|||
} |
|||
|
|||
.button:hover { |
|||
cursor: pointer; |
|||
filter: saturate(90%); |
|||
} |
|||
|
|||
.button:disabled { |
|||
color: rgba(22, 48, 87, 0.2); |
|||
cursor: default; |
|||
background: transparent; |
|||
} |
|||
|
|||
.hidden { |
|||
visibility: hidden; |
|||
} |
|||
</style> |
|||
@ -1,171 +0,0 @@ |
|||
<script> |
|||
export let color = "primary" |
|||
export let className = "" |
|||
export let style = "" |
|||
export let groupPosition = "" |
|||
export let grouped = false |
|||
|
|||
$: borderClass = grouped ? "" : "border-normal" |
|||
</script> |
|||
|
|||
<button |
|||
class="{color} |
|||
{className} |
|||
{borderClass} |
|||
{grouped ? 'grouped' : ''}" |
|||
{style} |
|||
on:click> |
|||
<slot /> |
|||
</button> |
|||
|
|||
<style> |
|||
.border-normal { |
|||
border-radius: var(--borderradiusall); |
|||
} |
|||
.border-left { |
|||
border-radius: var(--borderradius) 0 0 var(--borderradius); |
|||
} |
|||
.border-right { |
|||
border-radius: 0 var(--borderradius) var(--borderradius) 0; |
|||
} |
|||
.border-middle { |
|||
border-radius: 0; |
|||
} |
|||
|
|||
button { |
|||
border-style: solid; |
|||
padding: 7.5px 15px; |
|||
cursor: pointer; |
|||
margin: 5px; |
|||
border-radius: 5px; |
|||
} |
|||
|
|||
/* ---- PRIMARY ----*/ |
|||
.primary { |
|||
background-color: var(--blue); |
|||
border-color: var(--blue); |
|||
color: var(--white); |
|||
} |
|||
|
|||
.primary:hover { |
|||
background-color: var(--blue); |
|||
border-color: var(--blue); |
|||
} |
|||
|
|||
.primary:active { |
|||
background-color: var(--primarydark); |
|||
border-color: var(--primarydark); |
|||
} |
|||
|
|||
.primary-outline { |
|||
background-color: var(--white); |
|||
border-color: var(--blue); |
|||
color: var(--blue); |
|||
} |
|||
|
|||
.primary-outline:hover { |
|||
background-color: var(--primary10); |
|||
} |
|||
|
|||
.primary-outline:pressed { |
|||
background-color: var(--primary25); |
|||
} |
|||
|
|||
/* ---- secondary ----*/ |
|||
|
|||
.secondary { |
|||
background-color: var(--ink); |
|||
border-color: var(--ink); |
|||
color: var(--white); |
|||
} |
|||
|
|||
.secondary:hover { |
|||
background-color: var(--secondary75); |
|||
border-color: var(--secondary75); |
|||
} |
|||
|
|||
.secondary:pressed { |
|||
background-color: var(--secondarydark); |
|||
border-color: var(--secondarydark); |
|||
} |
|||
|
|||
.secondary-outline { |
|||
background-color: var(--white); |
|||
border-color: var(--ink); |
|||
color: var(--ink); |
|||
} |
|||
|
|||
.secondary-outline:hover { |
|||
background-color: var(--secondary10); |
|||
} |
|||
|
|||
.secondary-outline:pressed { |
|||
background-color: var(--secondary25); |
|||
} |
|||
|
|||
/* ---- success ----*/ |
|||
.success { |
|||
background-color: var(--success100); |
|||
border-color: var(--success100); |
|||
color: var(--white); |
|||
} |
|||
|
|||
.success:hover { |
|||
background-color: var(--success75); |
|||
border-color: var(--success75); |
|||
} |
|||
|
|||
.success:pressed { |
|||
background-color: var(--successdark); |
|||
border-color: var(--successdark); |
|||
} |
|||
|
|||
.success-outline { |
|||
background-color: var(--white); |
|||
border-color: var(--success100); |
|||
color: var(--success100); |
|||
} |
|||
|
|||
.success-outline:hover { |
|||
background-color: var(--success10); |
|||
} |
|||
|
|||
.success-outline:pressed { |
|||
background-color: var(--success25); |
|||
} |
|||
|
|||
/* ---- deletion ----*/ |
|||
.deletion { |
|||
background-color: var(--red); |
|||
border-color: var(--red); |
|||
color: var(--white); |
|||
} |
|||
|
|||
.deletion:hover { |
|||
background-color: var(--red-light); |
|||
border-color: var(--red); |
|||
color: var(--red); |
|||
} |
|||
|
|||
.deletion:pressed { |
|||
background-color: var(--red-dark); |
|||
border-color: var(--red-dark); |
|||
color: var(--white); |
|||
} |
|||
|
|||
.deletion-outline { |
|||
background-color: var(--white); |
|||
border-color: var(--red); |
|||
color: var(--red); |
|||
} |
|||
|
|||
.deletion-outline:hover { |
|||
background-color: var(--red-light); |
|||
color: var(--red); |
|||
} |
|||
|
|||
.deletion-outline:pressed { |
|||
background-color: var(--red-dark); |
|||
color: var(--white); |
|||
} |
|||
</style> |
|||
@ -1,15 +0,0 @@ |
|||
<script> |
|||
export let style = "" |
|||
</script> |
|||
|
|||
<div class="root" {style}> |
|||
<slot /> |
|||
</div> |
|||
|
|||
<style> |
|||
.root { |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: space-between; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,13 @@ |
|||
<div class="dropdown-container" on:click> |
|||
<slot /> |
|||
</div> |
|||
|
|||
<style> |
|||
.dropdown-container { |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: flex-start; |
|||
align-items: stretch; |
|||
padding: var(--spacing-s) 0; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,70 @@ |
|||
<script> |
|||
export let icon |
|||
export let title |
|||
export let subtitle |
|||
export let disabled |
|||
</script> |
|||
|
|||
<div |
|||
class="dropdown-item" |
|||
class:disabled |
|||
on:click |
|||
class:big={subtitle != null} |
|||
{...$$restProps}> |
|||
{#if icon}<i class={icon} />{/if} |
|||
<div class="content"> |
|||
<div class="title">{title}</div> |
|||
{#if subtitle != null} |
|||
<div class="subtitle">{subtitle}</div> |
|||
{/if} |
|||
</div> |
|||
</div> |
|||
|
|||
<style> |
|||
.dropdown-item { |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: flex-start; |
|||
align-items: center; |
|||
gap: var(--spacing-m); |
|||
padding: var(--spacing-xs) var(--spacing-l); |
|||
color: var(--ink); |
|||
} |
|||
.dropdown-item.disabled, |
|||
.dropdown-item.disabled .subtitle { |
|||
pointer-events: none; |
|||
color: var(--grey-5); |
|||
} |
|||
.dropdown-item.big { |
|||
padding: var(--spacing-s) var(--spacing-l); |
|||
} |
|||
.dropdown-item:not(.disabled):hover { |
|||
background-color: var(--grey-2); |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.content { |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
align-items: flex-start; |
|||
} |
|||
|
|||
.title, |
|||
.subtitle { |
|||
font-size: var(--font-size-xs); |
|||
} |
|||
|
|||
.title { |
|||
font-weight: 400; |
|||
} |
|||
|
|||
.subtitle { |
|||
color: var(--grey-7); |
|||
font-weight: 400; |
|||
} |
|||
|
|||
i { |
|||
font-size: 16px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,2 @@ |
|||
export { default as DropdownContainer } from "./DropdownContainer.svelte" |
|||
export { default as DropdownItem } from "./DropdownItem.svelte" |
|||
@ -0,0 +1,109 @@ |
|||
<script> |
|||
export let icon |
|||
export let withArrow = false |
|||
export let withActions = true |
|||
export let indentLevel = 0 |
|||
export let text |
|||
export let border = true |
|||
export let selected = false |
|||
export let opened = false |
|||
export let draggable = false |
|||
</script> |
|||
|
|||
<div |
|||
class="nav-item" |
|||
class:border |
|||
class:selected |
|||
style={`padding-left: ${indentLevel * 18}px`} |
|||
{draggable} |
|||
on:dragend |
|||
on:dragstart |
|||
on:dragover |
|||
on:drop |
|||
on:click |
|||
ondragover="return false" |
|||
ondragenter="return false"> |
|||
<div class="content"> |
|||
{#if withArrow} |
|||
<div class:opened class="icon arrow"> |
|||
<i class="ri-arrow-right-s-line" /> |
|||
</div> |
|||
{/if} |
|||
{#if icon} |
|||
<div class="icon"><i class={icon} /></div> |
|||
{/if} |
|||
<div class="text">{text}</div> |
|||
{#if withActions} |
|||
<div class="actions"> |
|||
<slot /> |
|||
</div> |
|||
{/if} |
|||
</div> |
|||
</div> |
|||
|
|||
<style> |
|||
.nav-item { |
|||
border-radius: var(--border-radius-m); |
|||
cursor: pointer; |
|||
color: var(--grey-7); |
|||
} |
|||
.nav-item.border { |
|||
border-top: 1px solid var(--grey-1); |
|||
} |
|||
.nav-item.selected { |
|||
background-color: var(--grey-2); |
|||
color: var(--ink); |
|||
} |
|||
.nav-item:hover { |
|||
background-color: var(--grey-1); |
|||
} |
|||
.nav-item:hover .actions { |
|||
display: flex; |
|||
} |
|||
|
|||
.content { |
|||
padding: 0 var(--spacing-m); |
|||
height: 32px; |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: flex-start; |
|||
align-items: center; |
|||
gap: var(--spacing-xs); |
|||
} |
|||
|
|||
.icon { |
|||
font-size: 16px; |
|||
flex: 0 0 20px; |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
.icon.arrow { |
|||
margin: 0 -2px 0 -6px; |
|||
font-size: 12px; |
|||
} |
|||
.icon.arrow.opened { |
|||
transform: rotate(90deg); |
|||
} |
|||
.icon + .icon { |
|||
margin-left: -4px; |
|||
} |
|||
|
|||
.text { |
|||
flex: 1 1 auto; |
|||
font-weight: 500; |
|||
font-size: var(--font-size-xs); |
|||
} |
|||
|
|||
.actions { |
|||
display: none; |
|||
width: 20px; |
|||
height: 20px; |
|||
cursor: pointer; |
|||
position: relative; |
|||
flex-direction: row; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
</style> |
|||
@ -1,36 +0,0 @@ |
|||
<script> |
|||
export let value |
|||
export let label |
|||
|
|||
const inputChanged = ev => { |
|||
try { |
|||
value = Number(ev.target.value) |
|||
} catch (_) { |
|||
value = null |
|||
} |
|||
} |
|||
|
|||
let numberText = value === null || value === undefined ? "" : value.toString() |
|||
</script> |
|||
|
|||
<div class="numberbox"> |
|||
<label>{label}</label> |
|||
<input |
|||
class="budibase__input" |
|||
type="number" |
|||
{value} |
|||
on:change={inputChanged} /> |
|||
</div> |
|||
|
|||
<style> |
|||
.numberbox { |
|||
display: grid; |
|||
align-items: center; |
|||
} |
|||
|
|||
label { |
|||
font-size: 14px; |
|||
font-weight: 500; |
|||
margin-bottom: 8px; |
|||
} |
|||
</style> |
|||
@ -1,76 +0,0 @@ |
|||
<script> |
|||
export let tabs = [] |
|||
export const selectTab = tabName => { |
|||
selected = tabName |
|||
selectedIndex = tabs.indexOf(selected) |
|||
} |
|||
|
|||
let selected = tabs.length > 0 && tabs[0] |
|||
let selectedIndex = 0 |
|||
|
|||
const isSelected = tab => selected === tab |
|||
</script> |
|||
|
|||
<div class="root"> |
|||
<div class="switcher"> |
|||
{#each tabs as tab} |
|||
<button class:selected={selected === tab} on:click={() => selectTab(tab)}> |
|||
{tab} |
|||
</button> |
|||
{/each} |
|||
</div> |
|||
|
|||
<div class="panel"> |
|||
{#if selectedIndex === 0} |
|||
<slot name="0" /> |
|||
{:else if selectedIndex === 1} |
|||
<slot name="1" /> |
|||
{:else if selectedIndex === 2} |
|||
<slot name="2" /> |
|||
{:else if selectedIndex === 3} |
|||
<slot name="3" /> |
|||
{/if} |
|||
</div> |
|||
</div> |
|||
|
|||
<style> |
|||
.root { |
|||
height: 100%; |
|||
display: flex; |
|||
flex-direction: column; |
|||
padding: 20px 20px; |
|||
border-left: solid 1px var(--grey-2); |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.switcher { |
|||
display: flex; |
|||
margin: 0px 20px 20px 0px; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.switcher > button { |
|||
display: inline-block; |
|||
border: none; |
|||
margin: 0; |
|||
padding: 0; |
|||
cursor: pointer; |
|||
font-size: 18px; |
|||
font-weight: 600; |
|||
color: var(--grey-5); |
|||
margin-right: 20px; |
|||
background: none; |
|||
outline: none; |
|||
font-family: Inter; |
|||
} |
|||
|
|||
.switcher > .selected { |
|||
color: var(--ink); |
|||
} |
|||
|
|||
.panel { |
|||
min-height: 0; |
|||
height: 100%; |
|||
overflow-y: auto; |
|||
} |
|||
</style> |
|||
@ -1,3 +0,0 @@ |
|||
import { flow } from "lodash/fp" |
|||
|
|||
export const pipe = (arg, funcs) => flow(funcs)(arg) |
|||
@ -1,13 +0,0 @@ |
|||
import { eventHandlers } from "../../../../client/src/state/eventHandlers" |
|||
export { EVENT_TYPE_MEMBER_NAME } from "../../../../client/src/state/eventHandlers" |
|||
|
|||
export const allHandlers = () => { |
|||
const handlersObj = eventHandlers() |
|||
|
|||
const handlers = Object.keys(handlersObj).map(name => ({ |
|||
name, |
|||
...handlersObj[name], |
|||
})) |
|||
|
|||
return handlers |
|||
} |
|||
@ -1,32 +1,36 @@ |
|||
<script> |
|||
import SettingsModal from "./SettingsModal.svelte" |
|||
import { SettingsIcon } from "components/common/Icons/" |
|||
import { Modal } from "@budibase/bbui" |
|||
|
|||
let modal |
|||
</script> |
|||
|
|||
<span class="topnavitemright settings" on:click={modal.show}> |
|||
<SettingsIcon /> |
|||
</span> |
|||
<div class="topnavitemright settings" on:click={modal.show}> |
|||
<i class="ri-settings-3-line" /> |
|||
</div> |
|||
<Modal bind:this={modal} width="600px"> |
|||
<SettingsModal /> |
|||
</Modal> |
|||
|
|||
<style> |
|||
span:first-letter { |
|||
text-transform: capitalize; |
|||
i { |
|||
font-size: 18px; |
|||
color: var(--grey-7); |
|||
} |
|||
.topnavitemright { |
|||
cursor: pointer; |
|||
color: var(--grey-7); |
|||
margin: 0 20px 0 0; |
|||
margin: 0 12px 0 0; |
|||
font-weight: 500; |
|||
font-size: 1rem; |
|||
height: 100%; |
|||
display: flex; |
|||
flex: 1; |
|||
flex-direction: row; |
|||
justify-content: center; |
|||
align-items: center; |
|||
box-sizing: border-box; |
|||
height: 24px; |
|||
width: 24px; |
|||
} |
|||
.topnavitemright:hover i { |
|||
color: var(--ink); |
|||
} |
|||
</style> |
|||
|
|||
@ -1,13 +0,0 @@ |
|||
<h3> |
|||
<slot /> |
|||
</h3> |
|||
|
|||
<style> |
|||
h3 { |
|||
font-size: 18px; |
|||
font-weight: bold; |
|||
color: var(--ink); |
|||
margin-top: 40px; |
|||
margin-bottom: 20px; |
|||
} |
|||
</style> |
|||
@ -1,291 +1,291 @@ |
|||
<script> |
|||
import { DropdownMenu, Button, Input } from "@budibase/bbui" |
|||
import { createEventDispatcher, tick } from "svelte" |
|||
|
|||
import icons from "./icons.js" |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
|
|||
export let value = "" |
|||
export let maxIconsPerPage = 30 |
|||
|
|||
let searchTerm = "" |
|||
let selectedLetter = "A" |
|||
|
|||
let currentPage = 1 |
|||
let filteredIcons = findIconByTerm(selectedLetter) |
|||
|
|||
$: dispatch("change", value) |
|||
|
|||
const alphabet = [ |
|||
"A", |
|||
"B", |
|||
"C", |
|||
"D", |
|||
"E", |
|||
"F", |
|||
"G", |
|||
"H", |
|||
"I", |
|||
"J", |
|||
"K", |
|||
"L", |
|||
"M", |
|||
"N", |
|||
"O", |
|||
"P", |
|||
"Q", |
|||
"R", |
|||
"S", |
|||
"T", |
|||
"U", |
|||
"V", |
|||
"W", |
|||
"X", |
|||
"Y", |
|||
"Z", |
|||
] |
|||
let buttonAnchor, dropdown |
|||
let loading = false |
|||
|
|||
function findIconByTerm(term) { |
|||
const r = new RegExp(`\^${term}`, "i") |
|||
return icons.filter(i => r.test(i.label)) |
|||
} |
|||
|
|||
async function switchLetter(letter) { |
|||
currentPage = 1 |
|||
searchTerm = "" |
|||
loading = true |
|||
selectedLetter = letter |
|||
filteredIcons = findIconByTerm(letter) |
|||
await tick() //svg icons do not update without tick |
|||
loading = false |
|||
} |
|||
|
|||
async function findIconOnPage() { |
|||
loading = true |
|||
const iconIdx = filteredIcons.findIndex(i => i.value === value) |
|||
if (iconIdx !== -1) { |
|||
currentPage = Math.ceil(iconIdx / maxIconsPerPage) |
|||
} |
|||
await tick() //svg icons do not update without tick |
|||
loading = false |
|||
} |
|||
|
|||
async function setSelectedUI() { |
|||
if (value) { |
|||
const letter = displayValue.substring(0, 1) |
|||
await switchLetter(letter) |
|||
await findIconOnPage() |
|||
} |
|||
} |
|||
|
|||
async function pageClick(next) { |
|||
loading = true |
|||
if (next && currentPage < totalPages) { |
|||
currentPage++ |
|||
} else if (!next && currentPage > 1) { |
|||
currentPage-- |
|||
} |
|||
await tick() //svg icons do not update without tick |
|||
loading = false |
|||
} |
|||
|
|||
async function searchForIcon(e) { |
|||
currentPage = 1 |
|||
loading = true |
|||
filteredIcons = findIconByTerm(searchTerm) |
|||
await tick() //svg icons do not update without tick |
|||
loading = false |
|||
} |
|||
|
|||
$: displayValue = value ? value.substring(7) : "Pick Icon" |
|||
|
|||
$: totalPages = Math.ceil(filteredIcons.length / maxIconsPerPage) |
|||
$: pageEndIdx = maxIconsPerPage * currentPage |
|||
$: pagedIcons = filteredIcons.slice(pageEndIdx - maxIconsPerPage, pageEndIdx) |
|||
|
|||
$: pagerText = `Page ${currentPage} of ${totalPages}` |
|||
</script> |
|||
|
|||
<div bind:this={buttonAnchor}> |
|||
<Button secondary on:click={dropdown.show}>{displayValue}</Button> |
|||
</div> |
|||
<DropdownMenu |
|||
bind:this={dropdown} |
|||
on:open={setSelectedUI} |
|||
anchor={buttonAnchor}> |
|||
<div class="container"> |
|||
<div class="search-area"> |
|||
<div class="alphabet-area"> |
|||
{#each alphabet as letter, idx} |
|||
<span |
|||
class="letter" |
|||
class:letter-selected={letter === selectedLetter} |
|||
on:click={() => switchLetter(letter)}> |
|||
{letter} |
|||
</span> |
|||
{#if idx !== alphabet.length - 1}<span>-</span>{/if} |
|||
{/each} |
|||
</div> |
|||
<div class="search-input"> |
|||
<div class="input-wrapper"> |
|||
<Input bind:value={searchTerm} thin placeholder="Search Icon" /> |
|||
</div> |
|||
<Button secondary on:click={searchForIcon}>Search</Button> |
|||
</div> |
|||
<div class="page-area"> |
|||
<div class="pager"> |
|||
<span on:click={() => pageClick(false)}> |
|||
<i class="page-btn fas fa-chevron-left" /> |
|||
</span> |
|||
<span>{pagerText}</span> |
|||
<span on:click={() => pageClick(true)}> |
|||
<i class="page-btn fas fa-chevron-right" /> |
|||
</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
{#if pagedIcons.length > 0} |
|||
<div class="icon-area"> |
|||
{#if !loading} |
|||
{#each pagedIcons as icon} |
|||
<div |
|||
class="icon-container" |
|||
class:selected={value === icon.value} |
|||
on:click={() => (value = icon.value)}> |
|||
<div class="icon-preview"> |
|||
<i class={`${icon.value} fa-3x`} /> |
|||
</div> |
|||
<div class="icon-label">{icon.label}</div> |
|||
</div> |
|||
{/each} |
|||
{/if} |
|||
</div> |
|||
{:else} |
|||
<div class="no-icons"> |
|||
<h5> |
|||
{`There is no icons for this ${searchTerm ? 'search' : 'page'}`} |
|||
</h5> |
|||
</div> |
|||
{/if} |
|||
</div> |
|||
</DropdownMenu> |
|||
|
|||
<style> |
|||
.container { |
|||
width: 610px; |
|||
height: 350px; |
|||
display: flex; |
|||
flex-direction: column; |
|||
padding: 10px 0px 10px 15px; |
|||
overflow-x: hidden; |
|||
} |
|||
|
|||
.search-area { |
|||
flex: 0 0 80px; |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.icon-area { |
|||
flex: 1; |
|||
display: grid; |
|||
grid-template-columns: repeat(5, 1fr); |
|||
grid-gap: 5px; |
|||
justify-content: flex-start; |
|||
overflow-y: auto; |
|||
overflow-x: hidden; |
|||
padding-right: 10px; |
|||
} |
|||
|
|||
.no-icons { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
.alphabet-area { |
|||
display: flex; |
|||
flex-flow: row wrap; |
|||
padding-bottom: 10px; |
|||
padding-right: 15px; |
|||
justify-content: space-around; |
|||
} |
|||
|
|||
.loading-container { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
.search-input { |
|||
display: flex; |
|||
flex-flow: row nowrap; |
|||
width: 100%; |
|||
padding-right: 15px; |
|||
} |
|||
|
|||
.input-wrapper { |
|||
width: 510px; |
|||
margin-right: 5px; |
|||
} |
|||
|
|||
.page-area { |
|||
padding: 10px; |
|||
display: flex; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.letter { |
|||
color: var(--blue); |
|||
} |
|||
|
|||
.letter:hover { |
|||
cursor: pointer; |
|||
text-decoration: underline; |
|||
} |
|||
|
|||
.letter-selected { |
|||
text-decoration: underline; |
|||
} |
|||
|
|||
.icon-container { |
|||
height: 100px; |
|||
display: flex; |
|||
justify-content: center; |
|||
flex-direction: column; |
|||
border: var(--border-dark); |
|||
} |
|||
|
|||
.icon-container:hover { |
|||
cursor: pointer; |
|||
background: var(--grey-2); |
|||
} |
|||
|
|||
.selected { |
|||
background: var(--grey-3); |
|||
} |
|||
|
|||
.icon-preview { |
|||
flex: 1; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
.icon-label { |
|||
flex: 0 0 20px; |
|||
text-align: center; |
|||
font-size: 12px; |
|||
} |
|||
|
|||
.page-btn { |
|||
color: var(--blue); |
|||
} |
|||
|
|||
.page-btn:hover { |
|||
cursor: pointer; |
|||
} |
|||
</style> |
|||
<script> |
|||
import { DropdownMenu, Button, Input } from "@budibase/bbui" |
|||
import { createEventDispatcher, tick } from "svelte" |
|||
|
|||
import icons from "./icons.js" |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
|
|||
export let value = "" |
|||
export let maxIconsPerPage = 30 |
|||
|
|||
let searchTerm = "" |
|||
let selectedLetter = "A" |
|||
|
|||
let currentPage = 1 |
|||
let filteredIcons = findIconByTerm(selectedLetter) |
|||
|
|||
$: dispatch("change", value) |
|||
|
|||
const alphabet = [ |
|||
"A", |
|||
"B", |
|||
"C", |
|||
"D", |
|||
"E", |
|||
"F", |
|||
"G", |
|||
"H", |
|||
"I", |
|||
"J", |
|||
"K", |
|||
"L", |
|||
"M", |
|||
"N", |
|||
"O", |
|||
"P", |
|||
"Q", |
|||
"R", |
|||
"S", |
|||
"T", |
|||
"U", |
|||
"V", |
|||
"W", |
|||
"X", |
|||
"Y", |
|||
"Z", |
|||
] |
|||
let buttonAnchor, dropdown |
|||
let loading = false |
|||
|
|||
function findIconByTerm(term) { |
|||
const r = new RegExp(`\^${term}`, "i") |
|||
return icons.filter(i => r.test(i.label)) |
|||
} |
|||
|
|||
async function switchLetter(letter) { |
|||
currentPage = 1 |
|||
searchTerm = "" |
|||
loading = true |
|||
selectedLetter = letter |
|||
filteredIcons = findIconByTerm(letter) |
|||
await tick() //svg icons do not update without tick |
|||
loading = false |
|||
} |
|||
|
|||
async function findIconOnPage() { |
|||
loading = true |
|||
const iconIdx = filteredIcons.findIndex(i => i.value === value) |
|||
if (iconIdx !== -1) { |
|||
currentPage = Math.ceil(iconIdx / maxIconsPerPage) |
|||
} |
|||
await tick() //svg icons do not update without tick |
|||
loading = false |
|||
} |
|||
|
|||
async function setSelectedUI() { |
|||
if (value) { |
|||
const letter = displayValue.substring(0, 1) |
|||
await switchLetter(letter) |
|||
await findIconOnPage() |
|||
} |
|||
} |
|||
|
|||
async function pageClick(next) { |
|||
loading = true |
|||
if (next && currentPage < totalPages) { |
|||
currentPage++ |
|||
} else if (!next && currentPage > 1) { |
|||
currentPage-- |
|||
} |
|||
await tick() //svg icons do not update without tick |
|||
loading = false |
|||
} |
|||
|
|||
async function searchForIcon(e) { |
|||
currentPage = 1 |
|||
loading = true |
|||
filteredIcons = findIconByTerm(searchTerm) |
|||
await tick() //svg icons do not update without tick |
|||
loading = false |
|||
} |
|||
|
|||
$: displayValue = value ? value.substring(7) : "Pick Icon" |
|||
|
|||
$: totalPages = Math.ceil(filteredIcons.length / maxIconsPerPage) |
|||
$: pageEndIdx = maxIconsPerPage * currentPage |
|||
$: pagedIcons = filteredIcons.slice(pageEndIdx - maxIconsPerPage, pageEndIdx) |
|||
|
|||
$: pagerText = `Page ${currentPage} of ${totalPages}` |
|||
</script> |
|||
|
|||
<div bind:this={buttonAnchor}> |
|||
<Button secondary small on:click={dropdown.show}>{displayValue}</Button> |
|||
</div> |
|||
<DropdownMenu |
|||
bind:this={dropdown} |
|||
on:open={setSelectedUI} |
|||
anchor={buttonAnchor}> |
|||
<div class="container"> |
|||
<div class="search-area"> |
|||
<div class="alphabet-area"> |
|||
{#each alphabet as letter, idx} |
|||
<span |
|||
class="letter" |
|||
class:letter-selected={letter === selectedLetter} |
|||
on:click={() => switchLetter(letter)}> |
|||
{letter} |
|||
</span> |
|||
{#if idx !== alphabet.length - 1}<span>-</span>{/if} |
|||
{/each} |
|||
</div> |
|||
<div class="search-input"> |
|||
<div class="input-wrapper"> |
|||
<Input bind:value={searchTerm} thin placeholder="Search Icon" /> |
|||
</div> |
|||
<Button secondary on:click={searchForIcon}>Search</Button> |
|||
</div> |
|||
<div class="page-area"> |
|||
<div class="pager"> |
|||
<span on:click={() => pageClick(false)}> |
|||
<i class="page-btn fas fa-chevron-left" /> |
|||
</span> |
|||
<span>{pagerText}</span> |
|||
<span on:click={() => pageClick(true)}> |
|||
<i class="page-btn fas fa-chevron-right" /> |
|||
</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
{#if pagedIcons.length > 0} |
|||
<div class="icon-area"> |
|||
{#if !loading} |
|||
{#each pagedIcons as icon} |
|||
<div |
|||
class="icon-container" |
|||
class:selected={value === icon.value} |
|||
on:click={() => (value = icon.value)}> |
|||
<div class="icon-preview"> |
|||
<i class={`${icon.value} fa-3x`} /> |
|||
</div> |
|||
<div class="icon-label">{icon.label}</div> |
|||
</div> |
|||
{/each} |
|||
{/if} |
|||
</div> |
|||
{:else} |
|||
<div class="no-icons"> |
|||
<h5> |
|||
{`There is no icons for this ${searchTerm ? 'search' : 'page'}`} |
|||
</h5> |
|||
</div> |
|||
{/if} |
|||
</div> |
|||
</DropdownMenu> |
|||
|
|||
<style> |
|||
.container { |
|||
width: 610px; |
|||
height: 350px; |
|||
display: flex; |
|||
flex-direction: column; |
|||
padding: 10px 0px 10px 15px; |
|||
overflow-x: hidden; |
|||
} |
|||
|
|||
.search-area { |
|||
flex: 0 0 80px; |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.icon-area { |
|||
flex: 1; |
|||
display: grid; |
|||
grid-template-columns: repeat(5, 1fr); |
|||
grid-gap: 5px; |
|||
justify-content: flex-start; |
|||
overflow-y: auto; |
|||
overflow-x: hidden; |
|||
padding-right: 10px; |
|||
} |
|||
|
|||
.no-icons { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
.alphabet-area { |
|||
display: flex; |
|||
flex-flow: row wrap; |
|||
padding-bottom: 10px; |
|||
padding-right: 15px; |
|||
justify-content: space-around; |
|||
} |
|||
|
|||
.loading-container { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
.search-input { |
|||
display: flex; |
|||
flex-flow: row nowrap; |
|||
width: 100%; |
|||
padding-right: 15px; |
|||
} |
|||
|
|||
.input-wrapper { |
|||
width: 510px; |
|||
margin-right: 5px; |
|||
} |
|||
|
|||
.page-area { |
|||
padding: 10px; |
|||
display: flex; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.letter { |
|||
color: var(--blue); |
|||
} |
|||
|
|||
.letter:hover { |
|||
cursor: pointer; |
|||
text-decoration: underline; |
|||
} |
|||
|
|||
.letter-selected { |
|||
text-decoration: underline; |
|||
} |
|||
|
|||
.icon-container { |
|||
height: 100px; |
|||
display: flex; |
|||
justify-content: center; |
|||
flex-direction: column; |
|||
border: var(--border-dark); |
|||
} |
|||
|
|||
.icon-container:hover { |
|||
cursor: pointer; |
|||
background: var(--grey-2); |
|||
} |
|||
|
|||
.selected { |
|||
background: var(--grey-3); |
|||
} |
|||
|
|||
.icon-preview { |
|||
flex: 1; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
.icon-label { |
|||
flex: 0 0 20px; |
|||
text-align: center; |
|||
font-size: 12px; |
|||
} |
|||
|
|||
.page-btn { |
|||
color: var(--blue); |
|||
} |
|||
|
|||
.page-btn:hover { |
|||
cursor: pointer; |
|||
} |
|||
</style> |
|||
|
|||
@ -1,50 +0,0 @@ |
|||
<script> |
|||
export let item |
|||
</script> |
|||
|
|||
<div data-cy={item.name} class="item-item" on:click> |
|||
<div class="item-icon"><i class={item.icon} /></div> |
|||
<div class="item-text"> |
|||
<div class="item-name">{item.name}</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<style> |
|||
.item-item { |
|||
display: flex; |
|||
flex-direction: column; |
|||
cursor: pointer; |
|||
padding: 12px 16px 16px 16px; |
|||
height: 80px; |
|||
justify-content: center; |
|||
align-items: center; |
|||
background-color: var(--grey-1); |
|||
border-radius: 5px; |
|||
} |
|||
|
|||
.item-item:hover { |
|||
background: var(--grey-2); |
|||
transition: all 0.3s; |
|||
} |
|||
|
|||
.item-icon { |
|||
border-radius: 3px; |
|||
display: flex; |
|||
} |
|||
|
|||
.item-text { |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.item-name { |
|||
font-size: 14px; |
|||
font-weight: 400; |
|||
text-align: center; |
|||
} |
|||
|
|||
i { |
|||
font-size: 24px; |
|||
color: var(--grey-7); |
|||
} |
|||
</style> |
|||
@ -1,52 +0,0 @@ |
|||
<script> |
|||
import { createEventDispatcher } from "svelte" |
|||
import { store } from "builderStore" |
|||
import Item from "./Item.svelte" |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
|
|||
export let list |
|||
|
|||
let category = list |
|||
|
|||
const handleClick = item => { |
|||
if (item.children && item.children.length > 0) { |
|||
list = item |
|||
} else { |
|||
dispatch("selectItem", item) |
|||
} |
|||
} |
|||
|
|||
const goBack = () => { |
|||
list = category |
|||
} |
|||
</script> |
|||
|
|||
{#if !list.isCategory} |
|||
<button class="back-button" on:click={goBack}>Back</button> |
|||
{/if} |
|||
{#each list.children as item} |
|||
{#if !item.showOnPages || item.showOnPages.includes($store.currentPageName)} |
|||
<Item {item} on:click={() => handleClick(item)} /> |
|||
{/if} |
|||
{/each} |
|||
|
|||
<style> |
|||
.back-button { |
|||
grid-column: 1 / span 2; |
|||
font-size: 14px; |
|||
text-align: center; |
|||
height: 36px; |
|||
border-radius: 5px; |
|||
border: solid 1px var(--grey-3); |
|||
background: white; |
|||
cursor: pointer; |
|||
font-weight: 500; |
|||
font-family: Inter; |
|||
transition: all 0.3ms; |
|||
} |
|||
|
|||
.back-button:hover { |
|||
background: var(--grey-1); |
|||
} |
|||
</style> |
|||
@ -1,88 +1,94 @@ |
|||
<script> |
|||
import { DataList } from "@budibase/bbui" |
|||
import { createEventDispatcher } from "svelte" |
|||
import { store, backendUiStore } from "builderStore" |
|||
import fetchBindableProperties from "builderStore/fetchBindableProperties" |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
|
|||
export let value = "" |
|||
|
|||
$: urls = getUrls() |
|||
|
|||
const handleBlur = () => dispatch("change", value) |
|||
|
|||
// this will get urls of all screens, but only |
|||
// choose detail screens that are usable in the current context |
|||
// and substitute the :id param for the actual {{ ._id }} binding |
|||
const getUrls = () => { |
|||
const urls = [ |
|||
...$store.screens |
|||
.filter(screen => !screen.props._component.endsWith("/rowdetail")) |
|||
.map(screen => ({ |
|||
name: screen.props._instanceName, |
|||
url: screen.route, |
|||
sort: screen.props._component, |
|||
})), |
|||
] |
|||
|
|||
const bindableProperties = fetchBindableProperties({ |
|||
componentInstanceId: $store.currentComponentInfo._id, |
|||
components: $store.components, |
|||
screen: $store.currentPreviewItem, |
|||
tables: $backendUiStore.tables, |
|||
}) |
|||
|
|||
const detailScreens = $store.screens.filter(screen => |
|||
screen.props._component.endsWith("/rowdetail") |
|||
) |
|||
|
|||
for (let detailScreen of detailScreens) { |
|||
const idBinding = bindableProperties.find(p => { |
|||
if ( |
|||
p.type === "context" && |
|||
p.runtimeBinding.endsWith("._id") && |
|||
p.table |
|||
) { |
|||
const tableId = |
|||
typeof p.table === "string" ? p.table : p.table.tableId |
|||
return tableId === detailScreen.props.table |
|||
} |
|||
return false |
|||
}) |
|||
|
|||
if (idBinding) { |
|||
urls.push({ |
|||
name: detailScreen.props._instanceName, |
|||
url: detailScreen.route.replace( |
|||
":id", |
|||
`{{ ${idBinding.runtimeBinding} }}` |
|||
), |
|||
sort: detailScreen.props._component, |
|||
}) |
|||
} |
|||
} |
|||
|
|||
return urls |
|||
} |
|||
</script> |
|||
|
|||
<div> |
|||
<DataList editable secondary thin on:blur={handleBlur} on:change bind:value> |
|||
<option value="" /> |
|||
{#each urls as url} |
|||
<option value={url.url}>{url.name}</option> |
|||
{/each} |
|||
</DataList> |
|||
</div> |
|||
|
|||
<style> |
|||
div { |
|||
flex: 1 1 auto; |
|||
display: flex; |
|||
flex-direction: row; |
|||
} |
|||
div :global(> div) { |
|||
flex: 1 1 auto; |
|||
} |
|||
</style> |
|||
<script> |
|||
import { DataList } from "@budibase/bbui" |
|||
import { createEventDispatcher } from "svelte" |
|||
import { store, backendUiStore } from "builderStore" |
|||
import fetchBindableProperties from "builderStore/fetchBindableProperties" |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
|
|||
export let value = "" |
|||
|
|||
$: urls = getUrls() |
|||
|
|||
const handleBlur = () => dispatch("change", value) |
|||
|
|||
// this will get urls of all screens, but only |
|||
// choose detail screens that are usable in the current context |
|||
// and substitute the :id param for the actual {{ ._id }} binding |
|||
const getUrls = () => { |
|||
const urls = [ |
|||
...$store.screens |
|||
.filter(screen => !screen.props._component.endsWith("/rowdetail")) |
|||
.map(screen => ({ |
|||
name: screen.props._instanceName, |
|||
url: screen.route, |
|||
sort: screen.props._component, |
|||
})), |
|||
] |
|||
|
|||
const bindableProperties = fetchBindableProperties({ |
|||
componentInstanceId: $store.currentComponentInfo._id, |
|||
components: $store.components, |
|||
screen: $store.currentPreviewItem, |
|||
tables: $backendUiStore.tables, |
|||
}) |
|||
|
|||
const detailScreens = $store.screens.filter(screen => |
|||
screen.props._component.endsWith("/rowdetail") |
|||
) |
|||
|
|||
for (let detailScreen of detailScreens) { |
|||
const idBinding = bindableProperties.find(p => { |
|||
if ( |
|||
p.type === "context" && |
|||
p.runtimeBinding.endsWith("._id") && |
|||
p.table |
|||
) { |
|||
const tableId = |
|||
typeof p.table === "string" ? p.table : p.table.tableId |
|||
return tableId === detailScreen.props.table |
|||
} |
|||
return false |
|||
}) |
|||
|
|||
if (idBinding) { |
|||
urls.push({ |
|||
name: detailScreen.props._instanceName, |
|||
url: detailScreen.route.replace( |
|||
":id", |
|||
`{{ ${idBinding.runtimeBinding} }}` |
|||
), |
|||
sort: detailScreen.props._component, |
|||
}) |
|||
} |
|||
} |
|||
|
|||
return urls |
|||
} |
|||
</script> |
|||
|
|||
<div> |
|||
<DataList |
|||
editable |
|||
secondary |
|||
extraThin |
|||
on:blur={handleBlur} |
|||
on:change |
|||
bind:value> |
|||
<option value="" /> |
|||
{#each urls as url} |
|||
<option value={url.url}>{url.name}</option> |
|||
{/each} |
|||
</DataList> |
|||
</div> |
|||
|
|||
<style> |
|||
div { |
|||
flex: 1 1 auto; |
|||
display: flex; |
|||
flex-direction: row; |
|||
} |
|||
div :global(> div) { |
|||
flex: 1 1 auto; |
|||
} |
|||
</style> |
|||
|
|||
@ -1,163 +1,163 @@ |
|||
<script> |
|||
import { Button, Icon, DropdownMenu, Spacer, Heading } from "@budibase/bbui" |
|||
import { createEventDispatcher } from "svelte" |
|||
import { store, backendUiStore } from "builderStore" |
|||
import fetchBindableProperties from "../../builderStore/fetchBindableProperties" |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
let anchorRight, dropdownRight |
|||
|
|||
export let value = {} |
|||
|
|||
function handleSelected(selected) { |
|||
dispatch("change", selected) |
|||
dropdownRight.hide() |
|||
} |
|||
|
|||
$: tables = $backendUiStore.tables.map(m => ({ |
|||
label: m.name, |
|||
name: `all_${m._id}`, |
|||
tableId: m._id, |
|||
type: "table", |
|||
})) |
|||
|
|||
$: views = $backendUiStore.tables.reduce((acc, cur) => { |
|||
let viewsArr = Object.entries(cur.views).map(([key, value]) => ({ |
|||
label: key, |
|||
name: key, |
|||
...value, |
|||
type: "view", |
|||
})) |
|||
return [...acc, ...viewsArr] |
|||
}, []) |
|||
|
|||
$: bindableProperties = fetchBindableProperties({ |
|||
componentInstanceId: $store.currentComponentInfo._id, |
|||
components: $store.components, |
|||
screen: $store.currentPreviewItem, |
|||
tables: $backendUiStore.tables, |
|||
}) |
|||
|
|||
$: links = bindableProperties |
|||
.filter(x => x.fieldSchema.type === "link") |
|||
.map(property => ({ |
|||
label: property.readableBinding, |
|||
fieldName: property.fieldSchema.name, |
|||
name: `all_${property.fieldSchema.tableId}`, |
|||
tableId: property.fieldSchema.tableId, |
|||
type: "link", |
|||
})) |
|||
</script> |
|||
|
|||
<div |
|||
class="dropdownbutton" |
|||
bind:this={anchorRight} |
|||
on:click={dropdownRight.show}> |
|||
<span>{value.label ? value.label : 'Table / View'}</span> |
|||
<Icon name="arrowdown" /> |
|||
</div> |
|||
<DropdownMenu bind:this={dropdownRight} anchor={anchorRight}> |
|||
<div class="dropdown"> |
|||
<div class="title"> |
|||
<Heading extraSmall>Tables</Heading> |
|||
</div> |
|||
<ul> |
|||
{#each tables as table} |
|||
<li |
|||
class:selected={value === table} |
|||
on:click={() => handleSelected(table)}> |
|||
{table.label} |
|||
</li> |
|||
{/each} |
|||
</ul> |
|||
<hr /> |
|||
<div class="title"> |
|||
<Heading extraSmall>Views</Heading> |
|||
</div> |
|||
<ul> |
|||
{#each views as view} |
|||
<li |
|||
class:selected={value === view} |
|||
on:click={() => handleSelected(view)}> |
|||
{view.label} |
|||
</li> |
|||
{/each} |
|||
</ul> |
|||
<hr /> |
|||
<div class="title"> |
|||
<Heading extraSmall>Relationships</Heading> |
|||
</div> |
|||
<ul> |
|||
{#each links as link} |
|||
<li |
|||
class:selected={value === link} |
|||
on:click={() => handleSelected(link)}> |
|||
{link.label} |
|||
</li> |
|||
{/each} |
|||
</ul> |
|||
</div> |
|||
</DropdownMenu> |
|||
|
|||
<style> |
|||
.dropdownbutton { |
|||
background-color: var(--grey-2); |
|||
border: var(--border-transparent); |
|||
padding: var(--spacing-m); |
|||
border-radius: var(--border-radius-m); |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
overflow: hidden; |
|||
flex: 1 1 auto; |
|||
} |
|||
.dropdownbutton:hover { |
|||
cursor: pointer; |
|||
background-color: var(--grey-3); |
|||
} |
|||
.dropdownbutton span { |
|||
white-space: nowrap; |
|||
text-overflow: ellipsis; |
|||
overflow: hidden; |
|||
flex: 1 1 auto; |
|||
text-align: left; |
|||
font-size: var(--font-size-xs); |
|||
} |
|||
.dropdownbutton :global(svg) { |
|||
margin: -4px 0; |
|||
} |
|||
|
|||
.dropdown { |
|||
padding: var(--spacing-m) 0; |
|||
z-index: 99999999; |
|||
} |
|||
.title { |
|||
padding: 0 var(--spacing-m) var(--spacing-xs) var(--spacing-m); |
|||
} |
|||
|
|||
hr { |
|||
margin: var(--spacing-m) 0 var(--spacing-xl) 0; |
|||
} |
|||
|
|||
ul { |
|||
list-style: none; |
|||
padding-left: 0px; |
|||
margin: 0px; |
|||
} |
|||
|
|||
li { |
|||
cursor: pointer; |
|||
margin: 0px; |
|||
padding: var(--spacing-s) var(--spacing-m); |
|||
font-size: var(--font-size-xs); |
|||
} |
|||
|
|||
.selected { |
|||
background-color: var(--grey-4); |
|||
} |
|||
|
|||
li:hover { |
|||
background-color: var(--grey-4); |
|||
} |
|||
</style> |
|||
<script> |
|||
import { Button, Icon, DropdownMenu, Spacer, Heading } from "@budibase/bbui" |
|||
import { createEventDispatcher } from "svelte" |
|||
import { store, backendUiStore } from "builderStore" |
|||
import fetchBindableProperties from "../../builderStore/fetchBindableProperties" |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
let anchorRight, dropdownRight |
|||
|
|||
export let value = {} |
|||
|
|||
function handleSelected(selected) { |
|||
dispatch("change", selected) |
|||
dropdownRight.hide() |
|||
} |
|||
|
|||
$: tables = $backendUiStore.tables.map(m => ({ |
|||
label: m.name, |
|||
name: `all_${m._id}`, |
|||
tableId: m._id, |
|||
type: "table", |
|||
})) |
|||
|
|||
$: views = $backendUiStore.tables.reduce((acc, cur) => { |
|||
let viewsArr = Object.entries(cur.views).map(([key, value]) => ({ |
|||
label: key, |
|||
name: key, |
|||
...value, |
|||
type: "view", |
|||
})) |
|||
return [...acc, ...viewsArr] |
|||
}, []) |
|||
|
|||
$: bindableProperties = fetchBindableProperties({ |
|||
componentInstanceId: $store.currentComponentInfo._id, |
|||
components: $store.components, |
|||
screen: $store.currentPreviewItem, |
|||
tables: $backendUiStore.tables, |
|||
}) |
|||
|
|||
$: links = bindableProperties |
|||
.filter(x => x.fieldSchema?.type === "link") |
|||
.map(property => ({ |
|||
label: property.readableBinding, |
|||
fieldName: property.fieldSchema.name, |
|||
name: `all_${property.fieldSchema.tableId}`, |
|||
tableId: property.fieldSchema.tableId, |
|||
type: "link", |
|||
})) |
|||
</script> |
|||
|
|||
<div |
|||
class="dropdownbutton" |
|||
bind:this={anchorRight} |
|||
on:click={dropdownRight.show}> |
|||
<span>{value.label ? value.label : 'Table / View'}</span> |
|||
<Icon name="arrowdown" /> |
|||
</div> |
|||
<DropdownMenu bind:this={dropdownRight} anchor={anchorRight}> |
|||
<div class="dropdown"> |
|||
<div class="title"> |
|||
<Heading extraSmall>Tables</Heading> |
|||
</div> |
|||
<ul> |
|||
{#each tables as table} |
|||
<li |
|||
class:selected={value === table} |
|||
on:click={() => handleSelected(table)}> |
|||
{table.label} |
|||
</li> |
|||
{/each} |
|||
</ul> |
|||
<hr /> |
|||
<div class="title"> |
|||
<Heading extraSmall>Views</Heading> |
|||
</div> |
|||
<ul> |
|||
{#each views as view} |
|||
<li |
|||
class:selected={value === view} |
|||
on:click={() => handleSelected(view)}> |
|||
{view.label} |
|||
</li> |
|||
{/each} |
|||
</ul> |
|||
<hr /> |
|||
<div class="title"> |
|||
<Heading extraSmall>Relationships</Heading> |
|||
</div> |
|||
<ul> |
|||
{#each links as link} |
|||
<li |
|||
class:selected={value === link} |
|||
on:click={() => handleSelected(link)}> |
|||
{link.label} |
|||
</li> |
|||
{/each} |
|||
</ul> |
|||
</div> |
|||
</DropdownMenu> |
|||
|
|||
<style> |
|||
.dropdownbutton { |
|||
background-color: var(--grey-2); |
|||
border: var(--border-transparent); |
|||
padding: var(--spacing-s) var(--spacing-m); |
|||
border-radius: var(--border-radius-m); |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
overflow: hidden; |
|||
flex: 1 1 auto; |
|||
} |
|||
.dropdownbutton:hover { |
|||
cursor: pointer; |
|||
background-color: var(--grey-3); |
|||
} |
|||
.dropdownbutton span { |
|||
white-space: nowrap; |
|||
text-overflow: ellipsis; |
|||
overflow: hidden; |
|||
flex: 1 1 auto; |
|||
text-align: left; |
|||
font-size: var(--font-size-xs); |
|||
} |
|||
.dropdownbutton :global(svg) { |
|||
margin: -4px 0; |
|||
} |
|||
|
|||
.dropdown { |
|||
padding: var(--spacing-m) 0; |
|||
z-index: 99999999; |
|||
} |
|||
.title { |
|||
padding: 0 var(--spacing-m) var(--spacing-xs) var(--spacing-m); |
|||
} |
|||
|
|||
hr { |
|||
margin: var(--spacing-m) 0 var(--spacing-xl) 0; |
|||
} |
|||
|
|||
ul { |
|||
list-style: none; |
|||
padding-left: 0px; |
|||
margin: 0px; |
|||
} |
|||
|
|||
li { |
|||
cursor: pointer; |
|||
margin: 0px; |
|||
padding: var(--spacing-s) var(--spacing-m); |
|||
font-size: var(--font-size-xs); |
|||
} |
|||
|
|||
.selected { |
|||
background-color: var(--grey-4); |
|||
} |
|||
|
|||
li:hover { |
|||
background-color: var(--grey-4); |
|||
} |
|||
</style> |
|||
|
|||
@ -1,193 +0,0 @@ |
|||
<script> |
|||
import ComponentsHierarchy from "./ComponentsHierarchy.svelte" |
|||
import ComponentsHierarchyChildren from "./ComponentsHierarchyChildren.svelte" |
|||
import PageLayout from "./PageLayout.svelte" |
|||
import PagesList from "./PagesList.svelte" |
|||
import { store } from "builderStore" |
|||
import NewScreenModal from "./NewScreenModal.svelte" |
|||
import CurrentItemPreview from "./AppPreview/CurrentItemPreview.svelte" |
|||
import SettingsView from "./SettingsView.svelte" |
|||
import ComponentsPaneSwitcher from "./ComponentsPaneSwitcher.svelte" |
|||
import ConfirmDialog from "components/common/ConfirmDialog.svelte" |
|||
import { last } from "lodash/fp" |
|||
import { AddIcon } from "components/common/Icons" |
|||
import { Modal } from "@budibase/bbui" |
|||
|
|||
let newScreenPicker |
|||
let confirmDeleteDialog |
|||
let componentToDelete = "" |
|||
let settingsView |
|||
let modal |
|||
|
|||
const settings = () => { |
|||
settingsView.show() |
|||
} |
|||
|
|||
const lastPartOfName = c => (c ? last(c.split("/")) : "") |
|||
</script> |
|||
|
|||
<div class="root"> |
|||
<div class="ui-nav"> |
|||
<div class="pages-list-container"> |
|||
<div class="nav-header"> |
|||
<span class="navigator-title">Navigator</span> |
|||
<span class="components-nav-page">Pages</span> |
|||
</div> |
|||
<div class="nav-items-container"> |
|||
<PagesList /> |
|||
</div> |
|||
</div> |
|||
<PageLayout layout={$store.pages[$store.currentPageName]} /> |
|||
<div class="components-list-container"> |
|||
<div class="nav-group-header"> |
|||
<span class="components-nav-header" style="margin-top: 0;"> |
|||
Screens |
|||
</span> |
|||
<div> |
|||
<button on:click={modal.show}> |
|||
<AddIcon /> |
|||
</button> |
|||
</div> |
|||
</div> |
|||
<div class="nav-items-container"> |
|||
<ComponentsHierarchy screens={$store.screens} /> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="preview-pane"> |
|||
<CurrentItemPreview /> |
|||
</div> |
|||
{#if $store.currentFrontEndType === 'screen' || $store.currentFrontEndType === 'page'} |
|||
<div class="components-pane"> |
|||
<ComponentsPaneSwitcher /> |
|||
</div> |
|||
{/if} |
|||
</div> |
|||
|
|||
<Modal bind:this={modal}> |
|||
<NewScreenModal /> |
|||
</Modal> |
|||
|
|||
<SettingsView bind:this={settingsView} /> |
|||
|
|||
<style> |
|||
button { |
|||
cursor: pointer; |
|||
outline: none; |
|||
border: none; |
|||
border-radius: 5px; |
|||
width: 20px; |
|||
padding-bottom: 10px; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
padding: 0; |
|||
} |
|||
|
|||
.root { |
|||
display: grid; |
|||
grid-template-columns: 300px 1fr 300px; |
|||
height: 100%; |
|||
width: 100%; |
|||
background: var(--grey-1); |
|||
} |
|||
|
|||
.ui-nav { |
|||
grid-column: 1; |
|||
background-color: var(--white); |
|||
height: calc(100vh - 49px); |
|||
padding: 0; |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.preview-pane { |
|||
grid-column: 2; |
|||
margin: 40px; |
|||
background: #fff; |
|||
border-radius: 5px; |
|||
box-shadow: 0 0px 6px rgba(0, 0, 0, 0.05); |
|||
} |
|||
|
|||
.components-pane { |
|||
grid-column: 3; |
|||
background-color: var(--white); |
|||
height: calc(100vh - 49px); |
|||
} |
|||
|
|||
.components-nav-page { |
|||
font-size: 13px; |
|||
color: var(--ink); |
|||
text-transform: uppercase; |
|||
padding-left: 20px; |
|||
margin-top: 20px; |
|||
font-weight: 600; |
|||
opacity: 0.4; |
|||
} |
|||
|
|||
.components-nav-header { |
|||
font-size: 13px; |
|||
color: var(--ink); |
|||
text-transform: uppercase; |
|||
margin-top: 20px; |
|||
font-weight: 600; |
|||
opacity: 0.4; |
|||
} |
|||
|
|||
.nav-header { |
|||
display: flex; |
|||
flex-direction: column; |
|||
margin-top: 20px; |
|||
} |
|||
|
|||
.nav-items-container { |
|||
padding: 1rem 0rem 0rem 0rem; |
|||
} |
|||
|
|||
.nav-group-header { |
|||
display: flex; |
|||
padding: 0px 20px 0px 20px; |
|||
font-size: 0.9rem; |
|||
font-weight: bold; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
} |
|||
|
|||
.nav-group-header > div:nth-child(1) { |
|||
padding: 0rem 0.5rem 0rem 0rem; |
|||
vertical-align: bottom; |
|||
grid-column-start: icon; |
|||
margin-right: 5px; |
|||
} |
|||
|
|||
.nav-group-header > span:nth-child(3) { |
|||
margin-left: 5px; |
|||
vertical-align: bottom; |
|||
grid-column-start: title; |
|||
margin-top: auto; |
|||
} |
|||
|
|||
.nav-group-header > div:nth-child(3) { |
|||
vertical-align: bottom; |
|||
grid-column-start: button; |
|||
cursor: pointer; |
|||
color: var(--blue); |
|||
} |
|||
|
|||
.nav-group-header > div:nth-child(3):hover { |
|||
color: var(--blue); |
|||
} |
|||
|
|||
.navigator-title { |
|||
font-size: 14px; |
|||
color: var(--ink); |
|||
font-weight: 600; |
|||
text-transform: uppercase; |
|||
padding: 0 20px 20px 20px; |
|||
line-height: 1rem !important; |
|||
} |
|||
|
|||
.components-list-container { |
|||
padding: 10px 0px 0 0; |
|||
} |
|||
</style> |
|||
File diff suppressed because it is too large
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue