Merge pull request #1232 from Budibase/feature/standard-components-transitions

Adds transition setting in the design panel on container component
This commit is contained in:
Kevin Åberg Kultalahti 2021-03-03 13:12:48 +01:00 committed by GitHub
commit ef84e2abcb
13 changed files with 116 additions and 99 deletions

View File

@ -285,6 +285,7 @@ export const getFrontendStore = () => {
_id: uuid(), _id: uuid(),
_component: definition.component, _component: definition.component,
_styles: { normal: {}, hover: {}, active: {} }, _styles: { normal: {}, hover: {}, active: {} },
_transition: "",
_instanceName: `New ${definition.name}`, _instanceName: `New ${definition.name}`,
...cloneDeep(props), ...cloneDeep(props),
...extras, ...extras,
@ -487,6 +488,15 @@ export const getFrontendStore = () => {
selected._styles = { normal: {}, hover: {}, active: {} } selected._styles = { normal: {}, hover: {}, active: {} }
await store.actions.preview.saveSelected() await store.actions.preview.saveSelected()
}, },
updateTransition: async transition => {
const selected = get(selectedComponent)
if (transition == null || transition === "") {
selected._transition = ""
} else {
selected._transition = transition
}
await store.actions.preview.saveSelected()
},
updateProp: async (name, value) => { updateProp: async (name, value) => {
let component = get(selectedComponent) let component = get(selectedComponent)
if (!name || !component) { if (!name || !component) {

View File

@ -14,6 +14,7 @@ export class Component extends BaseStructure {
active: {}, active: {},
selected: {}, selected: {},
}, },
_transition: "",
_instanceName: "", _instanceName: "",
_children: [], _children: [],
} }
@ -39,6 +40,11 @@ export class Component extends BaseStructure {
return this return this
} }
transition(transition) {
this._json._transition = transition
return this
}
// Shorthand for custom props "type" // Shorthand for custom props "type"
type(type) { type(type) {
this._json.type = type this._json.type = type

View File

@ -1,11 +1,8 @@
<script> <script>
import { goto } from "@sveltech/routify"
import { store, backendUiStore, allScreens } from "builderStore" import { store, backendUiStore, allScreens } from "builderStore"
import { Input, Select, ModalContent, Toggle } 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 from "analytics" import analytics from "analytics"
import { onMount } from "svelte"
import api from "builderStore/api"
const CONTAINER = "@budibase/standard-components/container" const CONTAINER = "@budibase/standard-components/container"
@ -19,9 +16,6 @@
$: templates = getTemplates($store, $backendUiStore.tables) $: templates = getTemplates($store, $backendUiStore.tables)
$: route = !route && $allScreens.length === 0 ? "*" : route $: route = !route && $allScreens.length === 0 ? "*" : route
$: baseComponents = Object.values($store.components)
.filter(componentDefinition => componentDefinition.baseComponent)
.map(c => c._component)
$: { $: {
if (templates && templateIndex === undefined) { if (templates && templateIndex === undefined) {
templateIndex = 0 templateIndex = 0
@ -59,6 +53,7 @@
if (routeError) return false if (routeError) return false
draftScreen.props._instanceName = name draftScreen.props._instanceName = name
draftScreen.props._transition = "fade"
draftScreen.props._component = baseComponent draftScreen.props._component = baseComponent
draftScreen.routing = { route, roleId } draftScreen.routing = { route, roleId }

View File

@ -1,5 +1,5 @@
<script> <script>
import { TextArea, DetailSummary, Button } from "@budibase/bbui" import { TextArea, DetailSummary, Button, Select } from "@budibase/bbui"
import PropertyGroup from "./PropertyControls/PropertyGroup.svelte" import PropertyGroup from "./PropertyControls/PropertyGroup.svelte"
import FlatButtonGroup from "./PropertyControls/FlatButtonGroup" import FlatButtonGroup from "./PropertyControls/FlatButtonGroup"
import { allStyles } from "./componentStyles" import { allStyles } from "./componentStyles"
@ -8,6 +8,7 @@
export let componentInstance = {} export let componentInstance = {}
export let onStyleChanged = () => {} export let onStyleChanged = () => {}
export let onCustomStyleChanged = () => {} export let onCustomStyleChanged = () => {}
export let onUpdateTransition = () => {}
export let onResetStyles = () => {} export let onResetStyles = () => {}
let selectedCategory = "normal" let selectedCategory = "normal"
@ -23,16 +24,22 @@
{ value: "active", text: "Active" }, { value: "active", text: "Active" },
] ]
const transitions = [
'none', 'fade', 'blur', 'fly', 'scale' // slide is hidden because it does not seem to result in any effect
]
const capitalize = ([first,...rest]) => first.toUpperCase() + rest.join('');
$: groups = componentDefinition?.styleable ? Object.keys(allStyles) : [] $: groups = componentDefinition?.styleable ? Object.keys(allStyles) : []
</script> </script>
<div class="design-view-container"> <div class="container">
<div class="design-view-state-categories"> <div class="state-categories">
<FlatButtonGroup value={selectedCategory} {buttonProps} {onChange} /> <FlatButtonGroup value={selectedCategory} {buttonProps} {onChange} />
</div> </div>
<div class="positioned-wrapper"> <div class="positioned-wrapper">
<div class="design-view-property-groups"> <div class="property-groups">
{#if groups.length > 0} {#if groups.length > 0}
{#each groups as groupName} {#each groups as groupName}
<PropertyGroup <PropertyGroup
@ -64,10 +71,19 @@
{/if} {/if}
</div> </div>
</div> </div>
{#if componentDefinition?.transitionable}
<div class="transitions">
<Select value={componentInstance._transition} on:change={event => onUpdateTransition(event.target.value)} name="transition" label="Transition" secondary thin>
{#each transitions as transition}
<option value={transition}>{capitalize(transition)}</option>
{/each}
</Select>
</div>
{/if}
</div> </div>
<style> <style>
.design-view-container { .container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
@ -81,7 +97,7 @@
min-height: 0; min-height: 0;
} }
.design-view-property-groups { .property-groups {
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
min-height: 0; min-height: 0;
@ -104,4 +120,8 @@
min-height: 120px; min-height: 120px;
font-size: var(--font-size-xs); font-size: var(--font-size-xs);
} }
option {
text-transform: capitalize;
}
</style> </style>

View File

@ -24,29 +24,9 @@
const onStyleChanged = store.actions.components.updateStyle const onStyleChanged = store.actions.components.updateStyle
const onCustomStyleChanged = store.actions.components.updateCustomStyle const onCustomStyleChanged = store.actions.components.updateCustomStyle
const onUpdateTransition = store.actions.components.updateTransition
const onResetStyles = store.actions.components.resetStyles const onResetStyles = store.actions.components.resetStyles
function walkProps(component, action) {
action(component)
if (component.children) {
for (let child of component.children) {
walkProps(child, action)
}
}
}
function flattenComponents(props) {
const components = []
props.forEach(comp =>
walkProps(comp, c => {
if ("_component" in c) {
components.push(c)
}
})
)
return components
}
function setAssetProps(name, value) { function setAssetProps(name, value) {
const selectedAsset = get(currentAsset) const selectedAsset = get(currentAsset)
store.update(state => { store.update(state => {
@ -62,10 +42,6 @@
}) })
store.actions.preview.saveSelected() store.actions.preview.saveSelected()
} }
function getProps(obj, keys) {
return keys.map((key, i) => [key, obj[key], obj.props._id + i])
}
</script> </script>
<CategoryTab <CategoryTab
@ -84,6 +60,7 @@
componentDefinition={definition} componentDefinition={definition}
{onStyleChanged} {onStyleChanged}
{onCustomStyleChanged} {onCustomStyleChanged}
{onUpdateTransition}
{onResetStyles} /> {onResetStyles} />
{:else if selectedCategory.value === 'settings'} {:else if selectedCategory.value === 'settings'}
<SettingsView <SettingsView

View File

@ -34,12 +34,14 @@
$: id = definition._id $: id = definition._id
$: updateComponentProps(definition, $context) $: updateComponentProps(definition, $context)
$: styles = definition._styles $: styles = definition._styles
$: transition = definition._transition
// Update component context // Update component context
$: componentStore.set({ $: componentStore.set({
id, id,
children: children.length, children: children.length,
styles: { ...styles, id }, styles: { ...styles, id },
transition
}) })
// Gets the component constructor for the specified component // Gets the component constructor for the specified component

View File

@ -18,19 +18,6 @@
<!-- Ensure to fully remount when screen changes --> <!-- Ensure to fully remount when screen changes -->
{#key screenDefinition?._id} {#key screenDefinition?._id}
<Provider key="url" data={params}> <Provider key="url" data={params}>
<div in:fade> <Component definition={screenDefinition} />
<Component definition={screenDefinition} />
</div>
</Provider> </Provider>
{/key} {/key}
<style>
div {
flex: 1 1 auto;
align-self: stretch;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
}
</style>

View File

@ -7,6 +7,7 @@ import {
builderStore, builderStore,
} from "./store" } from "./store"
import { styleable } from "./utils/styleable" import { styleable } from "./utils/styleable"
import transition from "./utils/transition"
import { linkable } from "./utils/linkable" import { linkable } from "./utils/linkable"
import Provider from "./components/Provider.svelte" import Provider from "./components/Provider.svelte"
import { ActionTypes } from "./constants" import { ActionTypes } from "./constants"
@ -19,6 +20,7 @@ export default {
screenStore, screenStore,
builderStore, builderStore,
styleable, styleable,
transition,
linkable, linkable,
Provider, Provider,
ActionTypes, ActionTypes,

View File

@ -1,42 +1,33 @@
import { writable, derived } from "svelte/store" import { writable } from "svelte/store"
import { generate } from "shortid"
const NOTIFICATION_TIMEOUT = 3000 const NOTIFICATION_TIMEOUT = 3000
const createNotificationStore = () => { const createNotificationStore = () => {
const _notifications = writable([]) const timeoutIds = new Set()
let block = false const _notifications = writable([], () => {
return () => {
const send = (message, type = "default") => { // clear all the timers
if (block) { timeoutIds.forEach(timeoutId => {
return clearTimeout(timeoutId)
} })
_notifications.update(state => { _notifications.set([])
return [...state, { id: generate(), type, message }]
})
}
const blockNotifications = (timeout = 1000) => {
block = true
setTimeout(() => (block = false), timeout)
}
const notifications = derived(_notifications, ($_notifications, set) => {
set($_notifications)
if ($_notifications.length > 0) {
const timeout = setTimeout(() => {
_notifications.update(state => {
state.shift()
return state
})
set($_notifications)
}, NOTIFICATION_TIMEOUT)
return () => {
clearTimeout(timeout)
}
} }
}) })
const { subscribe } = notifications
const send = (message, type = "default") => {
let _id = id()
_notifications.update(state => {
return [...state, { id: _id, type, message }]
})
const timeoutId = setTimeout(() => {
_notifications.update(state => {
return state.filter(({ id }) => id !== _id)
})
}, NOTIFICATION_TIMEOUT)
timeoutIds.add(timeoutId)
}
const { subscribe } = _notifications
return { return {
subscribe, subscribe,
@ -45,8 +36,16 @@ const createNotificationStore = () => {
warning: msg => send(msg, "warning"), warning: msg => send(msg, "warning"),
info: msg => send(msg, "info"), info: msg => send(msg, "info"),
success: msg => send(msg, "success"), success: msg => send(msg, "success"),
blockNotifications,
} }
} }
function id() {
return (
"_" +
Math.random()
.toString(36)
.substr(2, 9)
)
}
export const notificationStore = createNotificationStore() export const notificationStore = createNotificationStore()

View File

@ -0,0 +1,16 @@
import { fade, blur, scale, fly } from "svelte/transition"
// Default options
const transitions = new Map([
["fade", { tn: fade, opt: {} }],
["blur", { tn: blur, opt: {} }],
// This one seems to not result in any effect
// ["slide", { tn: slide, opt: {} }],
["scale", { tn: scale, opt: {} }],
["fly", { tn: fly, opt: { y: 80 } }],
])
export default function transition(node, { type, options = {} }) {
const { tn, opt } = transitions.get(type) || { tn: () => {}, opt: {} }
return tn(node, { ...opt, ...options })
}

View File

@ -21,6 +21,7 @@ exports.createHomeScreen = () => ({
active: {}, active: {},
selected: {}, selected: {},
}, },
_transition: "fade",
type: "div", type: "div",
_children: [ _children: [
{ {
@ -69,6 +70,7 @@ exports.createLoginScreen = app => ({
active: {}, active: {},
selected: {}, selected: {},
}, },
_transition: "fade",
type: "div", type: "div",
_children: [ _children: [
{ {

View File

@ -5,6 +5,7 @@
"icon": "ri-layout-column-line", "icon": "ri-layout-column-line",
"hasChildren": true, "hasChildren": true,
"styleable": true, "styleable": true,
"transitionable": true,
"settings": [ "settings": [
{ {
"type": "select", "type": "select",

View File

@ -1,62 +1,62 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
const { styleable } = getContext("sdk") const { styleable, transition } = getContext("sdk")
const component = getContext("component") const component = getContext("component")
export let type = "div" export let type = "div"
</script> </script>
{#if type === 'div'} {#if type === 'div'}
<div use:styleable={$component.styles}> <div in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot /> <slot />
</div> </div>
{:else if type === 'header'} {:else if type === 'header'}
<header use:styleable={$component.styles}> <header in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot /> <slot />
</header> </header>
{:else if type === 'main'} {:else if type === 'main'}
<main use:styleable={$component.styles}> <main in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot /> <slot />
</main> </main>
{:else if type === 'footer'} {:else if type === 'footer'}
<footer use:styleable={$component.styles}> <footer in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot /> <slot />
</footer> </footer>
{:else if type === 'aside'} {:else if type === 'aside'}
<aside use:styleable={$component.styles}> <aside in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot /> <slot />
</aside> </aside>
{:else if type === 'summary'} {:else if type === 'summary'}
<summary use:styleable={$component.styles}> <summary in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot /> <slot />
</summary> </summary>
{:else if type === 'details'} {:else if type === 'details'}
<details use:styleable={$component.styles}> <details in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot /> <slot />
</details> </details>
{:else if type === 'article'} {:else if type === 'article'}
<article use:styleable={$component.styles}> <article in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot /> <slot />
</article> </article>
{:else if type === 'nav'} {:else if type === 'nav'}
<nav use:styleable={$component.styles}> <nav in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot /> <slot />
</nav> </nav>
{:else if type === 'mark'} {:else if type === 'mark'}
<mark use:styleable={$component.styles}> <mark in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot /> <slot />
</mark> </mark>
{:else if type === 'figure'} {:else if type === 'figure'}
<figure use:styleable={$component.styles}> <figure in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot /> <slot />
</figure> </figure>
{:else if type === 'figcaption'} {:else if type === 'figcaption'}
<figcaption use:styleable={$component.styles}> <figcaption in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot /> <slot />
</figcaption> </figcaption>
{:else if type === 'paragraph'} {:else if type === 'paragraph'}
<p use:styleable={$component.styles}> <p in:transition={{type: $component.transition}} use:styleable={$component.styles}>
<slot /> <slot />
</p> </p>
{/if} {/if}