windmill-components 1.60.0 → 1.60.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -35,6 +35,12 @@ export let oldArgName = undefined;
35
35
  let resource_type = undefined;
36
36
  const dispatch = createEventDispatcher();
37
37
  let drawer;
38
+ function handleKeyUp(event) {
39
+ const key = event.key;
40
+ if (key === 'Enter') {
41
+ dispatch('save');
42
+ }
43
+ }
38
44
  export function openDrawer() {
39
45
  drawer.openDrawer();
40
46
  resource_type = property.format?.substring(5);
@@ -66,6 +72,8 @@ else {
66
72
  }
67
73
  </script>
68
74
 
75
+ <svelte:window on:keyup={handleKeyUp} />
76
+
69
77
  <Drawer bind:this={drawer} placement="right">
70
78
  <DrawerContent on:close={clearModal} title="Add an argument">
71
79
  <div class="flex flex-col gap-6">
@@ -90,18 +90,18 @@ $: result && rerender();
90
90
  {#if Array.isArray(result) && result.every(isObject)}
91
91
  <div class="border border-gray-300 shadow-sm divide-y divide-gray-300 flex flex-col h-full">
92
92
  {#if search !== 'Disabled'}
93
- <div class="px-4 py-2">
93
+ <div class="px-2 py-1">
94
94
  <div class="flex items-center">
95
- <div>
95
+ <div class="grow max-w-[300px]">
96
96
  <DebouncedInput placeholder="Search..." bind:value={searchValue} />
97
97
  </div>
98
98
  </div>
99
99
  </div>
100
100
  {/if}
101
101
 
102
- <div class="overflow-auto flex-1 w-full">
103
- <table class="divide-y divide-gray-300 w-full border-b border-b-gray-200">
104
- <thead class="bg-gray-50 text-left">
102
+ <div class="overflow-x-auto flex-1 w-full">
103
+ <table class="relative w-full border-b border-b-gray-200">
104
+ <thead class="sticky top-0 bg-gray-50 text-left">
105
105
  {#each $table.getHeaderGroups() as headerGroup}
106
106
  <tr class="divide-x">
107
107
  {#each headerGroup.headers as header}
@@ -109,16 +109,22 @@ $: result && rerender();
109
109
  {@const context = header?.getContext()}
110
110
  {#if context}
111
111
  {@const component = renderCell(header.column.columnDef.header, context)}
112
- <th class="px-4 py-4 text-sm font-semibold">
113
- {#if !header.isPlaceholder && component}
114
- <svelte:component this={component} />
115
- {/if}
112
+ <th class="!p-0">
113
+ <span class="block px-4 py-4 text-sm font-semibold border-b">
114
+ {#if !header.isPlaceholder && component}
115
+ <svelte:component this={component} />
116
+ {/if}
117
+ </span>
116
118
  </th>
117
119
  {/if}
118
120
  {/if}
119
121
  {/each}
120
122
  {#if actionButtons.length > 0}
121
- <th class="px-4 py-4 text-sm font-semibold">Actions</th>
123
+ <th class="!p-0">
124
+ <span class="block px-4 py-4 text-sm font-semibold border-b">
125
+ Actions
126
+ </span>
127
+ </th>
122
128
  {/if}
123
129
  </tr>
124
130
  {/each}
@@ -1,5 +1,6 @@
1
1
  <script>import Button from '../../../common/button/Button.svelte';
2
2
  import { faDownload } from '@fortawesome/free-solid-svg-icons';
3
+ import { ChevronLeft, ChevronRight } from 'lucide-svelte';
3
4
  import { tableOptions } from './tableOptions';
4
5
  export let result;
5
6
  export let paginationEnabled = false;
@@ -15,26 +16,30 @@ function downloadResultAsJSON() {
15
16
  }
16
17
  </script>
17
18
 
18
- <div class="px-4 py-2 text-xs flex flex-row gap-2 items-center justify-between">
19
+ <div class="px-2 py-1 text-xs flex flex-row gap-2 items-center justify-between">
19
20
  {#if paginationEnabled && result.length > (tableOptions.initialState?.pagination?.pageSize ?? 25)}
20
21
  <div class="flex items-center gap-2 flex-row">
21
22
  <Button
22
23
  size="xs"
23
24
  variant="border"
24
25
  color="light"
26
+ btnClasses="!py-1 !pl-1"
25
27
  on:click={() => $table.previousPage()}
26
28
  disabled={!$table.getCanPreviousPage()}
27
29
  >
30
+ <ChevronLeft size={14} />
28
31
  Previous
29
32
  </Button>
30
33
  <Button
31
34
  size="xs"
32
35
  variant="border"
33
36
  color="light"
37
+ btnClasses="!py-1 !pr-1"
34
38
  on:click={() => $table.nextPage()}
35
39
  disabled={!$table.getCanNextPage()}
36
40
  >
37
41
  Next
42
+ <ChevronRight size={14} />
38
43
  </Button>
39
44
  {$table.getState().pagination.pageIndex + 1} of {$table.getPageCount()}
40
45
  </div>
@@ -46,6 +51,7 @@ function downloadResultAsJSON() {
46
51
  size="xs"
47
52
  variant="border"
48
53
  color="light"
54
+ btnClasses="!py-1"
49
55
  on:click={downloadResultAsJSON}
50
56
  startIcon={{ icon: faDownload }}
51
57
  >
@@ -24,6 +24,7 @@ import { getContext } from 'svelte';
24
24
  import { Icon } from 'svelte-awesome';
25
25
  import { Pane, Splitpanes } from 'svelte-splitpanes';
26
26
  import { appToHubUrl, classNames, copyToClipboard, sendUserToast } from '../../../utils';
27
+ import { toStatic } from '../utils';
27
28
  import AppExportButton from './AppExportButton.svelte';
28
29
  import PanelSection from './settingsPanel/common/PanelSection.svelte';
29
30
  async function hash(message) {
@@ -48,16 +49,6 @@ let publishDrawerOpen = false;
48
49
  function closeSaveDrawer() {
49
50
  saveDrawerOpen = false;
50
51
  }
51
- function toStatic() {
52
- const newApp = JSON.parse(JSON.stringify($app));
53
- newApp.grid.forEach((x) => {
54
- let c = x.data;
55
- if (c.componentInput?.type == 'runnable') {
56
- c.componentInput.value = $staticExporter[x.id]();
57
- }
58
- });
59
- return { app: newApp, summary: $summary };
60
- }
61
52
  async function computeTriggerables() {
62
53
  const allTriggers = await Promise.all($app.grid
63
54
  .flatMap((x) => {
@@ -383,16 +374,23 @@ $: selectedJobId && testJobLoader?.watchJob(selectedJobId);
383
374
  displayName: 'JSON',
384
375
  icon: faFileExport,
385
376
  action: () => {
386
- appExport.open()
377
+ appExport.open($app)
387
378
  }
388
379
  },
389
380
  {
390
381
  displayName: 'Publish to Hub',
391
382
  icon: faGlobe,
392
383
  action: () => {
393
- const url = appToHubUrl(toStatic())
384
+ const url = appToHubUrl(toStatic($app, $staticExporter, $summary))
394
385
  window.open(url.toString(), '_blank')
395
386
  }
387
+ },
388
+ {
389
+ displayName: 'Hub compatible JSON',
390
+ icon: faFileExport,
391
+ action: () => {
392
+ appExport.open(toStatic($app, $staticExporter, $summary).app)
393
+ }
396
394
  }
397
395
  ]}
398
396
  >
@@ -409,7 +407,7 @@ $: selectedJobId && testJobLoader?.watchJob(selectedJobId);
409
407
  <span class="hidden md:inline">Debug Runs</span>
410
408
  </Button>
411
409
  </span>
412
- <AppExportButton bind:this={appExport} app={$app} />
410
+ <AppExportButton bind:this={appExport} />
413
411
  <Button
414
412
  on:click={() => (publishDrawerOpen = true)}
415
413
  color="light"
@@ -6,10 +6,11 @@ import { Highlight } from 'svelte-highlight';
6
6
  import json from 'svelte-highlight/languages/json';
7
7
  import { Button } from '../../common';
8
8
  let jsonViewerDrawer;
9
- export function open() {
9
+ let app = undefined;
10
+ export function open(app_l) {
11
+ app = app_l;
10
12
  jsonViewerDrawer?.toggleDrawer();
11
13
  }
12
- export let app;
13
14
  </script>
14
15
 
15
16
  <Drawer bind:this={jsonViewerDrawer} size="800px">
@@ -25,7 +26,7 @@ export let app;
25
26
  >
26
27
  Copy content
27
28
  </Button>
28
- <Highlight language={json} code={JSON.stringify(app, null, 4)} />
29
+ <Highlight language={json} code={JSON.stringify(app ?? {}, null, 4)} />
29
30
  </div>
30
31
  </DrawerContent>
31
32
  </Drawer>
@@ -2,8 +2,7 @@ import { SvelteComponentTyped } from "svelte";
2
2
  import type { App } from '../types';
3
3
  declare const __propDef: {
4
4
  props: {
5
- open?: (() => void) | undefined;
6
- app: App;
5
+ open?: ((app_l: App) => void) | undefined;
7
6
  };
8
7
  events: {
9
8
  [evt: string]: CustomEvent<any>;
@@ -14,6 +13,6 @@ export type AppExportButtonProps = typeof __propDef.props;
14
13
  export type AppExportButtonEvents = typeof __propDef.events;
15
14
  export type AppExportButtonSlots = typeof __propDef.slots;
16
15
  export default class AppExportButton extends SvelteComponentTyped<AppExportButtonProps, AppExportButtonEvents, AppExportButtonSlots> {
17
- get open(): () => void;
16
+ get open(): (app_l: App) => void;
18
17
  }
19
18
  export {};
@@ -220,10 +220,9 @@ const display = {
220
220
  value: `<img
221
221
  src="https://images.unsplash.com/photo-1554629947-334ff61d85dc?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;ixlib=rb-1.2.1&amp;auto=format&amp;fit=crop&amp;w=1024&amp;h=1280&amp;q=80"
222
222
  >
223
- <div class="absolute top-4 left-2 text-white">
223
+ <h1 class="absolute top-4 left-2 text-white">
224
224
  Hello \${ctx.username}
225
- </div>
226
- `,
225
+ </h1>`,
227
226
  },
228
227
  configuration: {},
229
228
  card: false
@@ -68,7 +68,7 @@ $: {
68
68
  }
69
69
  </script>
70
70
 
71
- <div class="h-full flex flex-col gap-4">
71
+ <div class="min-h-full flex flex-col gap-4">
72
72
  <PanelSection title="Inline scripts" smallPadding>
73
73
  <div class="flex flex-col gap-2 w-full">
74
74
  {#if runnablesByName.length > 0}
@@ -1,16 +1,253 @@
1
1
  export function defaultCode(component, language) {
2
- if (component === 'tablecomponent' && language === 'deno') {
3
- return `export async function main(x: string) {
4
- return [
5
- { foo: x, bar: 42 },
6
- { foo: "static", bar: 84 }]
7
- }`;
8
- }
9
- else if (component === 'tablecomponent' && language === 'python3') {
10
- return `def main(x: str):
11
- return [
12
- { "foo": x, "bar": 42 },
13
- { "foo": "static", "bar": 84 }]`;
14
- }
15
- return undefined;
2
+ return DEFAULT_CODES[component]?.[language];
16
3
  }
4
+ const DEFAULT_CODES = {
5
+ tablecomponent: {
6
+ deno: `export async function main() {
7
+ return [
8
+ {
9
+ "id": 1,
10
+ "name": "A cell with a long name",
11
+ "age": 42
12
+ },
13
+ {
14
+ "id": 2,
15
+ "name": "A briefer cell",
16
+ "age": 84
17
+ }
18
+ ]
19
+ }`,
20
+ python3: `def main():
21
+ return [
22
+ {
23
+ "id": 1,
24
+ "name": "A cell with a long name",
25
+ "age": 42
26
+ },
27
+ {
28
+ "id": 2,
29
+ "name": "A briefer cell",
30
+ "age": 84
31
+ }
32
+ ]`,
33
+ },
34
+ textcomponent: {
35
+ deno: `export async function main() {
36
+ return "foo"
37
+ }`,
38
+ python3: `def main():
39
+ return "foo"`,
40
+ },
41
+ barchartcomponent: {
42
+ deno: `export async function main() {
43
+ return {
44
+ "data": [
45
+ 25,
46
+ 50,
47
+ 25
48
+ ],
49
+ "labels": [
50
+ "Bar",
51
+ "Charts",
52
+ "<3"
53
+ ]
54
+ }
55
+ }`,
56
+ python3: `def main():
57
+ return {
58
+ "data": [
59
+ 25,
60
+ 50,
61
+ 25
62
+ ],
63
+ "labels": [
64
+ "Bar",
65
+ "Charts",
66
+ "<3"
67
+ ]
68
+ }`,
69
+ },
70
+ displaycomponent: {
71
+ deno: `export async function main() {
72
+ return {
73
+ "foo": 42
74
+ }
75
+ }`,
76
+ python3: `def main():
77
+ return {
78
+ "foo": 42
79
+ }`,
80
+ },
81
+ htmlcomponent: {
82
+ deno: `export async function main() {
83
+ return \`<img
84
+ src="https://images.unsplash.com/photo-1554629947-334ff61d85dc?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;ixlib=rb-1.2.1&amp;auto=format&amp;fit=crop&amp;w=1024&amp;h=1280&amp;q=80"
85
+ >
86
+ <h1 class="absolute top-4 left-2 text-white">
87
+ Hello world
88
+ </h1>\`
89
+ }`,
90
+ python3: `def main():
91
+ return '''<img
92
+ src="https://images.unsplash.com/photo-1554629947-334ff61d85dc?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;ixlib=rb-1.2.1&amp;auto=format&amp;fit=crop&amp;w=1024&amp;h=1280&amp;q=80"
93
+ >
94
+ <h1 class="absolute top-4 left-2 text-white">
95
+ Hello world
96
+ </h1>'''`,
97
+ },
98
+ piechartcomponent: {
99
+ deno: `export async function main() {
100
+ return {
101
+ "data": [
102
+ 25,
103
+ 50,
104
+ 25
105
+ ],
106
+ "labels": [
107
+ "Pie",
108
+ "Charts",
109
+ "<3"
110
+ ]
111
+ }
112
+ }`,
113
+ python3: `def main():
114
+ return {
115
+ "data": [
116
+ 25,
117
+ 50,
118
+ 25
119
+ ],
120
+ "labels": [
121
+ "Pie",
122
+ "Charts",
123
+ "<3"
124
+ ]
125
+ }`,
126
+ },
127
+ scatterchartcomponent: {
128
+ deno: `export async function main() {
129
+ return [
130
+ {
131
+ "label": "foo",
132
+ "data": [
133
+ { "x": 25, "y": 50 },
134
+ { "x": 23, "y": 23 },
135
+ { "x": 12, "y": 37 }
136
+ ],
137
+ "backgroundColor": "rgb(255, 12, 137)"
138
+ },
139
+ {
140
+ "label": "bar",
141
+ "data": [
142
+ { "x": 32, "y": 32 },
143
+ { "x": 25, "y": 42 },
144
+ { "x": 3, "y": 27 }
145
+ ],
146
+ "backgroundColor": "orange"
147
+ }
148
+ ]
149
+ }`,
150
+ python3: `def main():
151
+ return [
152
+ {
153
+ "label": "foo",
154
+ "data": [
155
+ { "x": 25, "y": 50 },
156
+ { "x": 23, "y": 23 },
157
+ { "x": 12, "y": 37 }
158
+ ],
159
+ "backgroundColor": "rgb(255, 12, 137)"
160
+ },
161
+ {
162
+ "label": "bar",
163
+ "data": [
164
+ { "x": 32, "y": 32 },
165
+ { "x": 25, "y": 42 },
166
+ { "x": 3, "y": 27 }
167
+ ],
168
+ "backgroundColor": "orange"
169
+ }
170
+ ]`,
171
+ },
172
+ timeseriescomponent: {
173
+ deno: `export async function main() {
174
+ return [
175
+ {
176
+ "label": "foo",
177
+ "data": [
178
+ {
179
+ "x": "2021-11-06 23:39:30",
180
+ "y": 50
181
+ },
182
+ {
183
+ "x": "2021-11-07 01:00:28",
184
+ "y": 60
185
+ },
186
+ {
187
+ "x": "2021-11-07 09:00:28",
188
+ "y": 20
189
+ }
190
+ ],
191
+ "backgroundColor": "rgb(255, 12, 137)"
192
+ },
193
+ {
194
+ "label": "bar",
195
+ "data": [
196
+ {
197
+ "x": "2021-11-06 23:39:30",
198
+ "y": 20
199
+ },
200
+ {
201
+ "x": "2021-11-07 01:00:28",
202
+ "y": 13
203
+ },
204
+ {
205
+ "x": "2021-11-07 09:00:28",
206
+ "y": 45
207
+ }
208
+ ],
209
+ "backgroundColor": "orange"
210
+ }
211
+ ]
212
+ }`,
213
+ python3: `def main():
214
+ return [
215
+ {
216
+ "label": "foo",
217
+ "data": [
218
+ {
219
+ "x": "2021-11-06 23:39:30",
220
+ "y": 50
221
+ },
222
+ {
223
+ "x": "2021-11-07 01:00:28",
224
+ "y": 60
225
+ },
226
+ {
227
+ "x": "2021-11-07 09:00:28",
228
+ "y": 20
229
+ }
230
+ ],
231
+ "backgroundColor": "rgb(255, 12, 137)"
232
+ },
233
+ {
234
+ "label": "bar",
235
+ "data": [
236
+ {
237
+ "x": "2021-11-06 23:39:30",
238
+ "y": 20
239
+ },
240
+ {
241
+ "x": "2021-11-07 01:00:28",
242
+ "y": 13
243
+ },
244
+ {
245
+ "x": "2021-11-07 09:00:28",
246
+ "y": 45
247
+ }
248
+ ],
249
+ "backgroundColor": "orange"
250
+ }
251
+ ]`,
252
+ },
253
+ };
@@ -1,6 +1,6 @@
1
1
  import type { Schema } from '../../common';
2
2
  import type { AppInput, InputType, ResultAppInput, StaticAppInput } from './inputType';
3
- import type { AppComponent } from './types';
3
+ import type { App, AppComponent } from './types';
4
4
  export declare function loadSchema(workspace: string, path: string, runType: 'script' | 'flow' | 'hubscript'): Promise<Schema>;
5
5
  export declare function schemaToInputsSpec(schema: Schema, defaultUserInput: boolean): Record<string, StaticAppInput>;
6
6
  export declare const displayData: Record<AppComponent['type'], {
@@ -12,3 +12,7 @@ export declare function fieldTypeToTsType(inputType: InputType): string;
12
12
  export declare function isScriptByNameDefined(appInput: AppInput | undefined): boolean;
13
13
  export declare function isScriptByPathDefined(appInput: AppInput | undefined): boolean;
14
14
  export declare function clearResultAppInput(appInput: ResultAppInput): ResultAppInput;
15
+ export declare function toStatic(app: App, staticExporter: Record<string, () => any>, summary: string): {
16
+ app: App;
17
+ summary: string;
18
+ };
@@ -188,3 +188,13 @@ export function clearResultAppInput(appInput) {
188
188
  }
189
189
  return appInput;
190
190
  }
191
+ export function toStatic(app, staticExporter, summary) {
192
+ const newApp = JSON.parse(JSON.stringify(app));
193
+ newApp.grid.forEach((x) => {
194
+ let c = x.data;
195
+ if (c.componentInput?.type == 'runnable') {
196
+ c.componentInput.value = staticExporter[x.id]();
197
+ }
198
+ });
199
+ return { app: newApp, summary };
200
+ }
@@ -2,6 +2,7 @@
2
2
  import { page } from '$app/stores';
3
3
  import { displayDate, displayDaysAgo, forLater, msToSec, truncateHash, truncateRev } from '../../utils';
4
4
  import { faCalendar, faCircle, faClock, faFastForward, faHourglassHalf, faRobot, faSearch, faTimes, faUser, faBarsStaggered } from '@fortawesome/free-solid-svg-icons';
5
+ import { CalendarClock } from 'lucide-svelte';
5
6
  import Icon from 'svelte-awesome';
6
7
  import { check } from 'svelte-awesome/icons';
7
8
  import { Badge } from '../common';
@@ -122,55 +123,21 @@ let scheduleEditor;
122
123
  >
123
124
  </div>
124
125
  {/if}
125
- <div>
126
- {#if job && job.parent_job}
127
- {#if job.is_flow_step}
128
- <Icon class="text-gray-700" data={faBarsStaggered} scale={SMALL_ICON_SCALE} /><span
129
- class="mx-2"
130
- >
131
- Step of flow <a href={`/run/${job.parent_job}`}>{truncateRev(job.parent_job, 6)}</a
132
- ></span
133
- >
134
- {:else}
135
- <Icon class="text-gray-700" data={faRobot} scale={SMALL_ICON_SCALE} /><span
136
- class="mx-2"
137
- >
138
- Triggered by parent <a href={`/run/${job.parent_job}`}>{job.parent_job}</a></span
139
- >
140
- {/if}
141
- {:else if job && job.schedule_path}
142
- <Icon class="text-gray-700" data={faCalendar} scale={SMALL_ICON_SCALE} />
143
- <span class="mx-2"
144
- >Triggered by the schedule: <button
145
- class="break-words text-sm text-blue-600 font-normal"
146
- on:click={() =>
147
- scheduleEditor?.openEdit(job.schedule_path ?? '', job.job_kind == 'flow')}
148
- >{job.schedule_path}</button
149
- ></span
150
- >
151
- {/if}
152
- </div>
153
126
  </div>
154
127
  <div class="text-gray-500 text-xs text-left place-self-start flex flex-col gap-1">
155
- <div>
156
- <Icon class="text-gray-700" data={faClock} scale={SMALL_ICON_SCALE} /><span class="mx-2">
157
- Created {displayDaysAgo(job.created_at ?? '')}</span
158
- >
159
- </div>
160
128
  {#if 'started_at' in job && job.started_at}
161
129
  <div>
162
130
  <Icon class="text-gray-700" data={faClock} scale={SMALL_ICON_SCALE} /><span
163
- class="mx-2"
131
+ class="mx-1.5"
164
132
  >
165
133
  Started {displayDaysAgo(job.started_at ?? '')}</span
166
134
  >
167
135
  </div>
168
136
  {/if}
169
137
  {#if 'scheduled_for' in job && !job.running && job.scheduled_for && forLater(job.scheduled_for)}
170
- <div>
171
- <Icon class="text-gray-700" data={faCalendar} scale={SMALL_ICON_SCALE} /><span
172
- class="mx-2"
173
- >
138
+ <div class="inline-flex gap-1">
139
+ <CalendarClock size={13} class="-ml-0.5" />
140
+ <span>
174
141
  <span class="bg-blue-200 text-gray-700 text-xs rounded px-1 ">Scheduled</span>
175
142
  for {displayDate(job.scheduled_for ?? '')}
176
143
  </span>
@@ -186,6 +153,34 @@ let scheduleEditor;
186
153
  </span>
187
154
  </div>
188
155
  {/if}
156
+ <div>
157
+ {#if job && job.parent_job}
158
+ {#if job.is_flow_step}
159
+ <Icon class="text-gray-700" data={faBarsStaggered} scale={SMALL_ICON_SCALE} /><span
160
+ class="mx-1"
161
+ >
162
+ Step of flow <a href={`/run/${job.parent_job}`}>{truncateRev(job.parent_job, 6)}</a
163
+ ></span
164
+ >
165
+ {:else}
166
+ <Icon class="text-gray-700" data={faRobot} scale={SMALL_ICON_SCALE} /><span
167
+ class="mx-1"
168
+ >
169
+ Parent <a href={`/run/${job.parent_job}`}>{job.parent_job}</a></span
170
+ >
171
+ {/if}
172
+ {:else if job && job.schedule_path}
173
+ <Icon class="text-gray-700" data={faCalendar} scale={SMALL_ICON_SCALE} />
174
+ <span class="mx-1"
175
+ >Schedule <button
176
+ class="break-words text-blue-400 font-normal truncate text-xs"
177
+ on:click={() =>
178
+ scheduleEditor?.openEdit(job.schedule_path ?? '', job.job_kind == 'flow')}
179
+ >{job.schedule_path}</button
180
+ ></span
181
+ >
182
+ {/if}
183
+ </div>
189
184
  </div>
190
185
  </div>
191
186
  </div>