mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-24 08:01:38 +00:00
feat(frontend): Improve app components (#3672)
* fix(frontend): wip * fix(frontend): wip * fix(frontend): improve components * fix(frontend): improve components * fix(frontend): improve ranges * fix(frontend): fix build
This commit is contained in:
@@ -157,4 +157,5 @@
|
||||
|
||||
.app-editor-input {
|
||||
@apply rounded-component border border-gray-300 dark:border-gray-500 focus:border-gray-300 focus:dark:border-gray-500 focus:!ring-1 focus:!ring-blue-300;
|
||||
@apply placeholder:text-gray-400 dark:placeholder:text-gray-600;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
PanelRightOpen,
|
||||
Table2,
|
||||
Braces,
|
||||
Highlighter
|
||||
Highlighter,
|
||||
InfoIcon
|
||||
} from 'lucide-svelte'
|
||||
import Portal from 'svelte-portal'
|
||||
import ObjectViewer from './propertyPicker/ObjectViewer.svelte'
|
||||
@@ -26,7 +27,7 @@
|
||||
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
|
||||
import MapResult from './MapResult.svelte'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import Popover from './Popover.svelte'
|
||||
|
||||
export let result: any
|
||||
export let requireHtmlApproval = false
|
||||
@@ -304,13 +305,17 @@
|
||||
<div class="text-tertiary text-xs flex gap-2 z-10 items-center">
|
||||
<slot name="copilot-fix" />
|
||||
{#if !disableExpand && !noControls}
|
||||
<Tooltip
|
||||
<Popover
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/rich_display_rendering"
|
||||
customSize="115%"
|
||||
>
|
||||
The result renderer in Windmill supports rich display rendering, allowing you to
|
||||
customize the display format of your results.
|
||||
</Tooltip>
|
||||
<svelte:fragment slot="text">
|
||||
The result renderer in Windmill supports rich display rendering, allowing you to
|
||||
customize the display format of your results.
|
||||
</svelte:fragment>
|
||||
<div class="-mt-1">
|
||||
<InfoIcon size={16} />
|
||||
</div>
|
||||
</Popover>
|
||||
<button on:click={() => copyToClipboard(toJsonStr(result))} class="-mt-1">
|
||||
<ClipboardCopy size={16} />
|
||||
</button>
|
||||
|
||||
@@ -255,7 +255,7 @@
|
||||
{/each}
|
||||
|
||||
{#if render}
|
||||
<div class="relative h-full w-full">
|
||||
<div class="relative h-full w-full component-wrapper">
|
||||
<div
|
||||
on:pointermove={updateRegionOutput}
|
||||
on:wheel={updateRegionOutput}
|
||||
|
||||
@@ -207,17 +207,13 @@
|
||||
<InitializeComponent {id} />
|
||||
|
||||
{#if render}
|
||||
<div class="relative flex flex-col w-full h-full bg-gray-100">
|
||||
<div class="relative flex flex-col w-full h-full bg-gray-100 component-wrapper">
|
||||
{#if source && zoom}
|
||||
{#if pages?.length}
|
||||
<div
|
||||
bind:clientWidth={controlsWidth}
|
||||
bind:clientHeight={controlsHeight}
|
||||
class="flex {$mode !== 'preview'
|
||||
? 'w-[calc(100%-2px)] top-[1px]'
|
||||
: 'w-full top-0'} {wideView
|
||||
? 'justify-center gap-14'
|
||||
: '!justify-between'} overflow-x-auto bg-surface border mx-auto py-1"
|
||||
class="flex flex-row w-full justify-between overflow-x-auto bg-surface border-b mx-auto py-1"
|
||||
>
|
||||
<div class="flex justify-start items-center px-2 text-secondary text-sm">
|
||||
<Button
|
||||
@@ -293,15 +289,11 @@
|
||||
disabled={!doc}
|
||||
size="xs"
|
||||
color="light"
|
||||
variant="border"
|
||||
title="Download PDF"
|
||||
aria-label="Download PDF"
|
||||
btnClasses="!font-medium !px-2"
|
||||
>
|
||||
{#if wideView}
|
||||
<span class="mr-1"> Download </span>
|
||||
{/if}
|
||||
<Download size={16} />
|
||||
<Download size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
max={resolvedConfig.maxDate}
|
||||
placeholder="Type..."
|
||||
class={twMerge(
|
||||
'windmillapp w-full py-1.5 text-sm px-2 app-editor-input',
|
||||
'windmillapp w-full py-1.5 text-sm px-1 app-editor-input',
|
||||
css?.input?.class,
|
||||
'wm-date-input'
|
||||
)}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
export let configuration: RichConfigurations
|
||||
export let customCss: ComponentCustomCSS<'multiselectcomponent'> | undefined = undefined
|
||||
export let render: boolean
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
|
||||
const [floatingRef, floatingContent] = createFloatingActions({
|
||||
strategy: 'absolute',
|
||||
@@ -104,7 +105,6 @@
|
||||
}
|
||||
|
||||
let w = 0
|
||||
let h = 0
|
||||
let open = false
|
||||
</script>
|
||||
|
||||
@@ -129,68 +129,74 @@
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
<AlignWrapper {render} hFull>
|
||||
<AlignWrapper {render} hFull {verticalAlignment}>
|
||||
<div
|
||||
class="w-full h-full"
|
||||
class="w-full app-editor-input"
|
||||
on:pointerdown={(e) => {
|
||||
$selectedComponent = [id]
|
||||
|
||||
if (!e.shiftKey) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
selectedComponent.set([id])
|
||||
}}
|
||||
use:floatingRef
|
||||
bind:clientWidth={w}
|
||||
bind:clientHeight={h}
|
||||
>
|
||||
{#if !value || Array.isArray(value)}
|
||||
<div style={`height:${h}px;`}>
|
||||
<MultiSelect
|
||||
bind:outerDiv
|
||||
outerDivClass={`${resolvedConfig.allowOverflow ? '' : 'h-full'}`}
|
||||
ulSelectedClass={`${resolvedConfig.allowOverflow ? '' : 'overflow-auto max-h-full'} `}
|
||||
ulOptionsClass={'p-2 !bg-surface-secondary'}
|
||||
bind:selected={value}
|
||||
options={Array.isArray(items) ? items : []}
|
||||
placeholder={resolvedConfig.placeholder}
|
||||
allowUserOptions={resolvedConfig.create}
|
||||
on:change={(event) => {
|
||||
if (event?.detail?.type === 'removeAll') {
|
||||
outputs?.result.set([])
|
||||
} else {
|
||||
outputs?.result.set([...(value ?? [])])
|
||||
}
|
||||
<MultiSelect
|
||||
bind:outerDiv
|
||||
outerDivClass={`${resolvedConfig.allowOverflow ? '' : 'h-full'}`}
|
||||
ulSelectedClass={`${resolvedConfig.allowOverflow ? '' : 'overflow-auto max-h-full'} `}
|
||||
ulOptionsClass={'p-2 !bg-surface-secondary'}
|
||||
--sms-border={'none'}
|
||||
--sms-min-height={'32px'}
|
||||
--sms-focus-border={'none'}
|
||||
bind:selected={value}
|
||||
options={Array.isArray(items) ? items : []}
|
||||
placeholder={resolvedConfig.placeholder}
|
||||
allowUserOptions={resolvedConfig.create}
|
||||
on:change={(event) => {
|
||||
if (event?.detail?.type === 'removeAll') {
|
||||
outputs?.result.set([])
|
||||
} else {
|
||||
outputs?.result.set([...(value ?? [])])
|
||||
}
|
||||
}}
|
||||
on:open={() => {
|
||||
$selectedComponent = [id]
|
||||
open = true
|
||||
}}
|
||||
on:close={() => {
|
||||
open = false
|
||||
}}
|
||||
let:option
|
||||
>
|
||||
<!-- needed because portal doesn't work for mouseup event en mobile -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="w-full"
|
||||
on:mouseup|stopPropagation
|
||||
on:pointerdown|stopPropagation={(e) => {
|
||||
let newe = new MouseEvent('mouseup')
|
||||
e.target?.['parentElement']?.dispatchEvent(newe)
|
||||
}}
|
||||
on:open={() => {
|
||||
$selectedComponent = [id]
|
||||
open = true
|
||||
}}
|
||||
on:close={() => {
|
||||
open = false
|
||||
}}
|
||||
let:option
|
||||
>
|
||||
<!-- needed because portal doesn't work for mouseup event en mobile -->
|
||||
{option}
|
||||
</div>
|
||||
</MultiSelect>
|
||||
<Portal>
|
||||
<div use:floatingContent class="z5000" hidden={!open}>
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="w-full"
|
||||
on:mouseup|stopPropagation
|
||||
on:pointerdown|stopPropagation={(e) => {
|
||||
let newe = new MouseEvent('mouseup')
|
||||
e.target?.['parentElement']?.dispatchEvent(newe)
|
||||
}}>{option}</div
|
||||
>
|
||||
</MultiSelect>
|
||||
<Portal>
|
||||
<div use:floatingContent class="z5000" hidden={!open}>
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
bind:this={portalRef}
|
||||
class="multiselect"
|
||||
style={`min-width: ${w}px;`}
|
||||
on:click|stopPropagation
|
||||
/>
|
||||
</div>
|
||||
</Portal>
|
||||
</div>
|
||||
bind:this={portalRef}
|
||||
class="multiselect"
|
||||
style={`min-width: ${w}px;`}
|
||||
on:click|stopPropagation
|
||||
/>
|
||||
</div>
|
||||
</Portal>
|
||||
{:else}
|
||||
Value {value} is not an array
|
||||
{/if}
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
on:pointerdown|stopPropagation={() => ($selectedComponent = [id])}
|
||||
on:focus={() => ($selectedComponent = [id])}
|
||||
class={twMerge(
|
||||
'windmillapp w-full py-1.5 text-sm focus:ring-indigo-100 px-2',
|
||||
'windmillapp w-full py-1.5 px-2 text-sm app-editor-input',
|
||||
css?.input?.class ?? '',
|
||||
'wm-number-input'
|
||||
)}
|
||||
|
||||
@@ -74,6 +74,10 @@
|
||||
handles.forEach((handle) => (handle.style.cssText = css?.handles?.style ?? ''))
|
||||
}
|
||||
}
|
||||
|
||||
const format = (v, i, p) => {
|
||||
return `${v}`
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each Object.keys(components['rangecomponent'].initialData.configuration) as key (key)}
|
||||
@@ -99,13 +103,7 @@
|
||||
|
||||
<AlignWrapper {render} {verticalAlignment}>
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="flex items-center w-full gap-1 px-1">
|
||||
<span
|
||||
class={twMerge(css?.limits?.class ?? '', 'font-mono wm-slider-limits')}
|
||||
style={css?.limits?.style ?? ''}
|
||||
>
|
||||
{+(resolvedConfig.min ?? 0)}
|
||||
</span>
|
||||
<div class="flex items-center w-full gap-1">
|
||||
<div
|
||||
class={twMerge('grow', 'wm-slider-bar')}
|
||||
style="--range-handle-focus: {'#7e9abd'}; --range-handle: {'#7e9abd'}; {css?.bar?.style ??
|
||||
@@ -113,45 +111,54 @@
|
||||
on:pointerdown|stopPropagation
|
||||
>
|
||||
<RangeSlider
|
||||
id="range-slider"
|
||||
springValues={{ stiffness: 1, damping: 1 }}
|
||||
bind:slider
|
||||
bind:values
|
||||
step={resolvedConfig.step}
|
||||
min={resolvedConfig.min == undefined ? 0 : +resolvedConfig.min}
|
||||
max={resolvedConfig.max == undefined ? 1 : +resolvedConfig.max}
|
||||
range
|
||||
disabled={resolvedConfig.disabled}
|
||||
pips
|
||||
float
|
||||
first="label"
|
||||
last="label"
|
||||
step={resolvedConfig.step ?? 1}
|
||||
pipstep={(resolvedConfig.axisStep ?? 1) / (resolvedConfig.step ?? 1)}
|
||||
formatter={format}
|
||||
/>
|
||||
<!-- <RangeSlider {step} range min={min ?? 0} max={max ?? 1} bind:values /> -->
|
||||
</div>
|
||||
<span
|
||||
class={twMerge(css?.limits?.class ?? '', 'font-mono wm-slider-limits')}
|
||||
style={css?.limits?.style ?? ''}
|
||||
>
|
||||
{+(resolvedConfig.max ?? 1)}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex justify-between px-1">
|
||||
<span
|
||||
class={twMerge(
|
||||
'font-mono text-center text-sm font-medium bg-blue-100 text-blue-800 rounded px-2.5 py-0.5',
|
||||
css?.values?.class ?? '',
|
||||
'wm-slider-value'
|
||||
)}
|
||||
style={css?.values?.style ?? ''}
|
||||
>
|
||||
{values[0]}
|
||||
</span>
|
||||
<span
|
||||
class={twMerge(
|
||||
'font-mono text-center text-sm font-medium bg-blue-100 text-blue-800 rounded px-2.5 py-0.5',
|
||||
css?.values?.class ?? '',
|
||||
'wm-slider-value'
|
||||
)}
|
||||
style={css?.values?.style ?? ''}
|
||||
>
|
||||
{values[1]}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</AlignWrapper>
|
||||
|
||||
<style>
|
||||
:global(#range-slider.rangeSlider) {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
:global(.dark #range-slider.rangeSlider) {
|
||||
background-color: #3b4252;
|
||||
}
|
||||
|
||||
:global(#range-slider.rangeSlider .rangeHandle) {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
:global(#range-slider.rangeSlider .rangeFloat) {
|
||||
opacity: 1;
|
||||
background: transparent;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
:global(#range-slider.rangeSlider .rangeNub) {
|
||||
background-color: #7e9abd;
|
||||
}
|
||||
|
||||
:global(.dark #range-slider.rangeSlider > .rangePips > .pip) {
|
||||
color: #eeeeee;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -221,6 +221,7 @@
|
||||
<Select
|
||||
inAppEditor={true}
|
||||
--border-radius="0.250rem"
|
||||
--clear-icon-color="#6b7280"
|
||||
bind:filterText
|
||||
on:filter={handleFilter}
|
||||
on:clear={onClear}
|
||||
|
||||
@@ -84,8 +84,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
let width = 0
|
||||
|
||||
const spanClass =
|
||||
'text-center text-sm font-medium bg-blue-100 text-blue-800 rounded px-2.5 py-0.5'
|
||||
function computeWidth() {
|
||||
@@ -99,7 +97,6 @@
|
||||
span.className = spanClass
|
||||
span.textContent = maxValue.toString()
|
||||
document.body.appendChild(span)
|
||||
width = span?.offsetWidth
|
||||
document.body.removeChild(span)
|
||||
}
|
||||
}
|
||||
@@ -131,15 +128,7 @@
|
||||
<InitializeComponent {id} />
|
||||
|
||||
<AlignWrapper {render} hFull {verticalAlignment}>
|
||||
<div
|
||||
class="flex {resolvedConfig.vertical ? 'flex-col' : ''} items-center w-full h-full gap-1 px-1"
|
||||
>
|
||||
<span
|
||||
class={twMerge(css?.limits?.class, 'font-mono wm-slider-limits')}
|
||||
style={css?.limits?.style ?? ''}
|
||||
>
|
||||
{resolvedConfig.vertical ? +(resolvedConfig?.max ?? 0) : +(resolvedConfig?.min ?? 0)}
|
||||
</span>
|
||||
<div class="flex {resolvedConfig.vertical ? 'flex-col' : ''} items-center w-full h-full gap-1">
|
||||
<div
|
||||
class={twMerge(
|
||||
'grow',
|
||||
@@ -147,40 +136,45 @@
|
||||
'font-mono wm-slider-bar',
|
||||
resolvedConfig?.vertical ? 'h-full' : 'w-full'
|
||||
)}
|
||||
style={css?.bar?.style}
|
||||
style="--range-handle-focus: {'#7e9abd'}; --range-handle: {'#7e9abd'}; {css?.bar?.style ??
|
||||
''}"
|
||||
on:pointerdown|stopPropagation={() => ($selectedComponent = [id])}
|
||||
>
|
||||
<RangeSlider
|
||||
id="range-slider"
|
||||
springValues={{ stiffness: 1, damping: 1 }}
|
||||
vertical={resolvedConfig.vertical}
|
||||
bind:slider
|
||||
bind:values
|
||||
step={resolvedConfig.step}
|
||||
step={resolvedConfig.step ?? 1}
|
||||
pipstep={(resolvedConfig.axisStep ?? 1) / (resolvedConfig.step ?? 1)}
|
||||
min={+(resolvedConfig?.min ?? 0)}
|
||||
max={+(resolvedConfig?.max ?? 0)}
|
||||
disabled={resolvedConfig.disabled}
|
||||
pips
|
||||
float
|
||||
first="label"
|
||||
last="label"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
class={twMerge(css?.limits?.class, 'font-mono wm-slider-limits')}
|
||||
style={css?.limits?.style ?? ''}
|
||||
>
|
||||
{resolvedConfig.vertical ? +(resolvedConfig?.min ?? 0) : +(resolvedConfig?.max ?? 1)}
|
||||
</span>
|
||||
<span class="mx-2">
|
||||
<span
|
||||
class={twMerge(spanClass, css?.value?.class ?? '', 'font-mono wm-slider-value')}
|
||||
style={`${css?.value?.style ?? ''} ${width ? `width: ${width}px;` : ''}`}
|
||||
>
|
||||
{values[0]}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</AlignWrapper>
|
||||
|
||||
<style global>
|
||||
.rangeSlider.vertical {
|
||||
height: 80%;
|
||||
min-height: 10px !important;
|
||||
<style>
|
||||
:global(#range-slider.rangeSlider) {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
:global(#range-slider.rangeSlider .rangeHandle) {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
:global(#range-slider.rangeSlider .rangeFloat) {
|
||||
opacity: 1;
|
||||
background: transparent;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
'app-editor-input',
|
||||
css?.input?.class ?? '',
|
||||
resolvedConfig.disabled ? 'placeholder:text-gray-400 dark:placeholder:text-gray-600' : '',
|
||||
'wm-input',
|
||||
'wm-text-input'
|
||||
)
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
<CurrencyInput
|
||||
inputClasses={{
|
||||
formatted: twMerge(
|
||||
'px-2 w-full py-1.5 windmillapp app-editor-input h-8',
|
||||
'px-2 text-sm w-full py-1.5 windmillapp app-editor-input',
|
||||
css?.input?.class,
|
||||
'wm-currency-input'
|
||||
),
|
||||
|
||||
@@ -437,6 +437,7 @@
|
||||
id={component.id}
|
||||
configuration={component.configuration}
|
||||
customCss={component.customCss}
|
||||
verticalAlignment={component.verticalAlignment}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'formcomponent'}
|
||||
|
||||
@@ -2051,6 +2051,7 @@ This is a paragraph.
|
||||
},
|
||||
initialData: {
|
||||
componentInput: undefined,
|
||||
verticalAlignment: 'center',
|
||||
configuration: {
|
||||
items: {
|
||||
type: 'static',
|
||||
@@ -2248,6 +2249,12 @@ This is a paragraph.
|
||||
type: 'static',
|
||||
value: false,
|
||||
fieldType: 'boolean'
|
||||
},
|
||||
axisStep: {
|
||||
type: 'static',
|
||||
value: 10,
|
||||
fieldType: 'number',
|
||||
tooltip: 'Spread between each number suggestion when using the arrow keys'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2353,9 +2360,14 @@ This is a paragraph.
|
||||
type: 'static',
|
||||
value: false,
|
||||
fieldType: 'boolean',
|
||||
|
||||
tooltip:
|
||||
'Determine if the slider is disabled, or enabled (only disables interactions, and events)'
|
||||
},
|
||||
axisStep: {
|
||||
type: 'static',
|
||||
value: 10,
|
||||
fieldType: 'number',
|
||||
tooltip: 'Spread between each number suggestion when using the arrow keys'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
`relative center-center flex-col text-center font-medium text-tertiary
|
||||
border border-dashed border-gray-400 hover:border-blue-500
|
||||
focus-within:border-blue-300 hover:bg-blue-50 dark:hover:bg-frost-900
|
||||
duration-200 rounded-lg p-1`,
|
||||
duration-200 rounded-component p-1`,
|
||||
c
|
||||
)}
|
||||
on:dragover={handleDragOver}
|
||||
|
||||
@@ -213,7 +213,7 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="w-full h-full p-2 flex flex-col">
|
||||
<div class="w-full h-full p-0 flex flex-col">
|
||||
{#if $fileUploads.length > 0 && !forceDisplayUploads}
|
||||
<div class="border rounded-md flex flex-col gap-1 divide-y h-full w-full p-1">
|
||||
<div class="flex h-full overflow-y-auto flex-col">
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
import WorkflowTimeline from '../WorkflowTimeline.svelte'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
|
||||
|
||||
export let lang: Preview['language'] | undefined
|
||||
export let previewIsLoading = false
|
||||
export let previewJob: Job | undefined
|
||||
@@ -148,10 +147,12 @@
|
||||
Test to see the result here
|
||||
{/if}
|
||||
</span>
|
||||
<Tooltip documentationLink="https://www.windmill.dev/docs/core_concepts/rich_display_rendering">
|
||||
The result renderer in Windmill supports rich display rendering, allowing you to customize the display format of your results.
|
||||
<Tooltip
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/rich_display_rendering"
|
||||
>
|
||||
The result renderer in Windmill supports rich display rendering, allowing you to
|
||||
customize the display format of your results.
|
||||
</Tooltip>
|
||||
|
||||
</div>
|
||||
{/if}
|
||||
</Pane>
|
||||
|
||||
Reference in New Issue
Block a user