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:
commit
ef84e2abcb
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
// clear all the timers
|
||||||
|
timeoutIds.forEach(timeoutId => {
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
})
|
||||||
|
_notifications.set([])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const send = (message, type = "default") => {
|
const send = (message, type = "default") => {
|
||||||
if (block) {
|
let _id = id()
|
||||||
return
|
|
||||||
}
|
|
||||||
_notifications.update(state => {
|
_notifications.update(state => {
|
||||||
return [...state, { id: generate(), type, message }]
|
return [...state, { id: _id, type, message }]
|
||||||
})
|
})
|
||||||
}
|
const timeoutId = setTimeout(() => {
|
||||||
|
|
||||||
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 => {
|
_notifications.update(state => {
|
||||||
state.shift()
|
return state.filter(({ id }) => id !== _id)
|
||||||
return state
|
|
||||||
})
|
})
|
||||||
set($_notifications)
|
|
||||||
}, NOTIFICATION_TIMEOUT)
|
}, NOTIFICATION_TIMEOUT)
|
||||||
return () => {
|
timeoutIds.add(timeoutId)
|
||||||
clearTimeout(timeout)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
const { subscribe } = _notifications
|
||||||
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()
|
||||||
|
|
|
@ -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 })
|
||||||
|
}
|
|
@ -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: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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}
|
||||||
|
|
Loading…
Reference in New Issue