poe-svelte-ui-lib 1.4.8 → 1.5.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.
@@ -10,9 +10,12 @@
10
10
  id = crypto.randomUUID(),
11
11
  wrapperClass = '',
12
12
  label = { name: '', class: '' },
13
- body = [],
13
+ body = $bindable(),
14
14
  header = [],
15
15
  footer = '',
16
+ stashData = false,
17
+ type = 'table',
18
+ rowsAmmount = 10,
16
19
  outline = false,
17
20
  cursor = null,
18
21
  loader,
@@ -22,6 +25,8 @@
22
25
  onClick,
23
26
  }: ITableProps<any> = $props()
24
27
 
28
+ let dataBuffer: any[] = $state([])
29
+
25
30
  /* Сортировка */
26
31
  let sortState: {
27
32
  key: string | null
@@ -33,6 +38,13 @@
33
38
 
34
39
  let isAutoscroll = $state(false)
35
40
 
41
+ const logTypeOptions = [
42
+ { id: crypto.randomUUID(), name: 'Error', value: 'error', color: 'bg-(--red-color)' },
43
+ { id: crypto.randomUUID(), name: 'Warning', value: 'warning', color: 'bg-(--yellow-color)' },
44
+ { id: crypto.randomUUID(), name: 'Info', value: 'info', color: 'bg-(--gray-color)' },
45
+ ]
46
+ let logType = $state(['error', 'warning'])
47
+
36
48
  /* Сортировка столбцов */
37
49
  const sortRows = (key: string) => {
38
50
  if (sortState.key === key) {
@@ -87,8 +99,9 @@
87
99
  container.scrollTop = container.scrollHeight
88
100
  }
89
101
  }
102
+
90
103
  $effect(() => {
91
- if (body.length > 0) {
104
+ if (autoscroll && dataBuffer && dataBuffer.length > 0) {
92
105
  scrollToBottom()
93
106
  }
94
107
  })
@@ -141,12 +154,78 @@
141
154
  return !!src
142
155
  }
143
156
 
157
+ $effect(() => {
158
+ if (body && type == 'logger') {
159
+ dataBuffer = [
160
+ ...dataBuffer,
161
+ {
162
+ type: Object.entries(body)[0][1] as string,
163
+ color: `<div class='size-6 rounded-full ${logTypeOptions.find((o) => o.value == body.logLevel)?.color}'></div>`,
164
+ data: Object.entries(body)[1][1] as string,
165
+ },
166
+ ]
167
+
168
+ if (dataBuffer.length > rowsAmmount * 5) {
169
+ dataBuffer = dataBuffer.slice(-(rowsAmmount * 5))
170
+ }
171
+
172
+ body = null
173
+ }
174
+ })
175
+
176
+ $effect(() => {
177
+ if (body && stashData && type == 'table') {
178
+ dataBuffer = [...dataBuffer, body]
179
+ if (dataBuffer.length > rowsAmmount) {
180
+ dataBuffer = dataBuffer.slice(-rowsAmmount)
181
+ }
182
+
183
+ body = null
184
+ }
185
+ })
186
+
187
+ $effect(() => {
188
+ const currentType = type
189
+ if (type === 'logger') {
190
+ header = [
191
+ {
192
+ key: 'color',
193
+ label: { name: 'Type' },
194
+ width: '3rem',
195
+ } as ITableHeader<any>,
196
+ {
197
+ key: 'data',
198
+ label: { name: 'Data' },
199
+ width: 'calc(100% - 3rem)',
200
+ } as ITableHeader<any>,
201
+ ]
202
+ }
203
+ return () => {
204
+ dataBuffer = []
205
+ }
206
+ })
207
+
144
208
  onMount(() => {
145
209
  if (autoscroll) {
146
210
  container?.addEventListener('scroll', handleAutoScroll)
147
211
  scrollToBottom()
148
212
  }
149
213
 
214
+ if (type === 'logger') {
215
+ header = [
216
+ {
217
+ key: 'color',
218
+ label: { name: 'Type' },
219
+ width: '3rem',
220
+ } as ITableHeader<any>,
221
+ {
222
+ key: 'data',
223
+ label: { name: 'Data' },
224
+ width: 'calc(100% - 3rem)',
225
+ } as ITableHeader<any>,
226
+ ]
227
+ }
228
+
150
229
  return () => {
151
230
  if (autoscroll) {
152
231
  container?.removeEventListener('scroll', handleAutoScroll)
@@ -155,13 +234,51 @@
155
234
  })
156
235
  </script>
157
236
 
158
- <div id={`${id}-${crypto.randomUUID().slice(0, 6)}`} class={twMerge(`bg-blue flex h-full w-full flex-col overflow-hidden`, wrapperClass)}>
237
+ <div
238
+ id={`${id}-${crypto.randomUUID().slice(0, 6)}`}
239
+ class={twMerge(`bg-blue flex h-full w-full gap-2 items-center flex-col overflow-hidden`, wrapperClass)}
240
+ >
159
241
  {#if label.name}
160
242
  <h5 class={twMerge(`w-full px-4 text-center`, label.class)}>{label.name}</h5>
161
243
  {/if}
162
244
 
245
+ {#if type == 'logger'}
246
+ <div id={`${id}-${crypto.randomUUID().slice(0, 6)}`} class="flex w-[50%] justify-center rounded-full">
247
+ {#each logTypeOptions as option, index}
248
+ <button
249
+ id={crypto.randomUUID()}
250
+ class={twMerge(`m-0 inline-block min-w-0 flex-1 cursor-pointer items-center px-2 py-1 font-semibold shadow-sm transition-all duration-300
251
+ select-none hover:shadow-md
252
+ ${
253
+ logType.includes(option.value) && logType !== null
254
+ ? 'z-10 py-1 shadow-[0_0_10px_var(--shadow-color)] hover:shadow-[0_0_15px_var(--shadow-color)]'
255
+ : ''
256
+ }
257
+ ${logTypeOptions.length > 0 && index === 0 ? 'rounded-l-2xl' : ''} ${
258
+ index === logTypeOptions.length - 1 ? 'rounded-r-2xl' : ''
259
+ } ${option.color}`)}
260
+ onclick={() => {
261
+ if (logType.includes(option.value)) {
262
+ logType = logType.filter((type) => type !== option.value)
263
+ } else {
264
+ logType.push(option.value)
265
+ }
266
+ }}
267
+ >
268
+ <span class="flex flex-row items-center justify-center gap-4">
269
+ {#if option}
270
+ <div class="flex-1">
271
+ {option.name}
272
+ </div>
273
+ {/if}
274
+ </span>
275
+ </button>
276
+ {/each}
277
+ </div>
278
+ {/if}
279
+
163
280
  <div
164
- class="flex h-full flex-col overflow-hidden rounded-xl border shadow-sm transition duration-200 hover:shadow-md {outline
281
+ class="flex h-full w-full flex-col overflow-hidden rounded-xl border shadow-sm transition duration-200 hover:shadow-md {outline
165
282
  ? ' border-(--border-color)'
166
283
  : 'border-transparent'} "
167
284
  >
@@ -193,126 +310,129 @@
193
310
  {/each}
194
311
  </div>
195
312
 
196
- <!-- Table Body с прокруткой -->
197
- <div class="flex-1 overflow-y-auto bg-(--container-color)/50" bind:this={container} onscroll={handleScroll}>
198
- <div class="grid min-w-0" style={`grid-template-columns: ${header.map((c) => c.width || 'minmax(0, 1fr)').join(' ')};`}>
199
- {#each body as row, index (row)}
200
- {#each header as column, j (column)}
201
- <div
202
- class="relative flex w-full min-w-0 items-center px-2 py-1 wrap-break-word
313
+ {#if body || dataBuffer}
314
+ {@const rows = type == 'logger' ? dataBuffer.filter((str) => logType.includes(str.type)).slice(-rowsAmmount) : stashData ? dataBuffer : body}
315
+ <!-- Table Body с прокруткой -->
316
+ <div class="flex-1 overflow-y-auto bg-(--container-color)/50" bind:this={container} onscroll={handleScroll}>
317
+ <div class="grid min-w-0" style={`grid-template-columns: ${header.map((c) => c.width || 'minmax(0, 1fr)').join(' ')};`}>
318
+ {#each rows as row, index (row)}
319
+ {#each header as column, j (column)}
320
+ <div
321
+ class="relative flex w-full min-w-0 items-center px-2 py-1 wrap-break-word
203
322
  {index % 2 ? 'bg-(--back-color)/40' : 'bg-[#edeef3] dark:bg-[#1f2a3a]'}
204
323
  {column.align === 'center'
205
- ? 'flex justify-center text-center'
206
- : column.align === 'right'
207
- ? 'flex justify-end text-right'
208
- : 'flex justify-start text-left'}
324
+ ? 'flex justify-center text-center'
325
+ : column.align === 'right'
326
+ ? 'flex justify-end text-right'
327
+ : 'flex justify-start text-left'}
209
328
  border-t
210
329
  {j !== 0 ? ' border-l ' : ''}
211
330
  {outline ? 'border-(--border-color)' : 'border-transparent'} "
212
- >
213
- {#if column.buttons}
214
- <div class="flex w-full flex-col gap-1">
215
- {#each column.buttons as button (button)}
216
- <button
217
- class="{twMerge(`cursor-pointer rounded-full
331
+ >
332
+ {#if column.buttons}
333
+ <div class="flex w-full flex-col gap-1">
334
+ {#each column.buttons as button (button)}
335
+ <button
336
+ class="{twMerge(`cursor-pointer rounded-full
218
337
  px-4 py-1 font-semibold shadow-sm transition-shadow duration-200 outline-none select-none hover:shadow-md
219
338
  ${typeof button.class === 'function' ? button.class(row) : button.class}`)} bg-(--bg-color)"
220
- onclick={() => buttonClick(row, button)}
221
- >
222
- {typeof button.name === 'function' ? button.name(row) : button.name}
223
- </button>
224
- {/each}
225
- </div>
226
- {:else if column.image?.src || column.image?.defaultIcon}
227
- <div
228
- class="flex items-center justify-center"
229
- style={`width: ${column.image.width || '5rem'}; height: ${column.image.height || '5rem'};`}
230
- >
231
- {#if hasImage(column, row)}
232
- <img
233
- src={typeof column.image?.src === 'function' ? column.image.src(row) : column.image?.src || ''}
234
- alt={column.image.alt ?? 'Image'}
235
- class={twMerge(`h-full w-full object-cover ${column.image.class || ''}`)}
236
- loading="lazy"
237
- />
238
- {:else if column.image.defaultIcon}
239
- {#if typeof column.image.defaultIcon === 'string'}
240
- {@html column.image.defaultIcon}
339
+ onclick={() => buttonClick(row, button)}
340
+ >
341
+ {typeof button.name === 'function' ? button.name(row) : button.name}
342
+ </button>
343
+ {/each}
344
+ </div>
345
+ {:else if column.image?.src || column.image?.defaultIcon}
346
+ <div
347
+ class="flex items-center justify-center"
348
+ style={`width: ${column.image.width || '5rem'}; height: ${column.image.height || '5rem'};`}
349
+ >
350
+ {#if hasImage(column, row)}
351
+ <img
352
+ src={typeof column.image?.src === 'function' ? column.image.src(row) : column.image?.src || ''}
353
+ alt={column.image.alt ?? 'Image'}
354
+ class={twMerge(`h-full w-full object-cover ${column.image.class || ''}`)}
355
+ loading="lazy"
356
+ />
357
+ {:else if column.image.defaultIcon}
358
+ {#if typeof column.image.defaultIcon === 'string'}
359
+ {@html column.image.defaultIcon}
360
+ {:else}
361
+ <column.image.defaultIcon />
362
+ {/if}
363
+ {/if}
364
+ </div>
365
+ {:else}
366
+ <div
367
+ class=" w-full max-w-full wrap-break-word {column.overflow?.truncated ? 'truncate' : ' whitespace-normal'}"
368
+ onmouseenter={column.overflow?.truncated ? (e) => showTooltip(e, row[column.key], column.overflow?.formatting) : undefined}
369
+ onmouseleave={column.overflow?.truncated ? hideTooltip : undefined}
370
+ onmousemove={column.overflow?.truncated
371
+ ? (e) => {
372
+ tooltip.x = e.clientX
373
+ tooltip.y = e.clientY
374
+ }
375
+ : undefined}
376
+ role="columnheader"
377
+ tabindex={null}
378
+ >
379
+ {#if column.overflow?.modal}
380
+ <button
381
+ class="w-full cursor-pointer overflow-hidden text-left text-ellipsis whitespace-nowrap"
382
+ onclick={(e) => {
383
+ e.stopPropagation()
384
+ showModal(row[column.key], column.overflow?.formatting)
385
+ }}
386
+ >
387
+ {@html row[column.key]}
388
+ </button>
241
389
  {:else}
242
- <column.image.defaultIcon />
390
+ {@html row[column.key]}
243
391
  {/if}
244
- {/if}
245
- </div>
246
- {:else}
247
- <div
248
- class=" w-full max-w-full wrap-break-word {column.overflow?.truncated ? 'truncate' : ' whitespace-normal'}"
249
- onmouseenter={column.overflow?.truncated ? (e) => showTooltip(e, row[column.key], column.overflow?.formatting) : undefined}
250
- onmouseleave={column.overflow?.truncated ? hideTooltip : undefined}
251
- onmousemove={column.overflow?.truncated
252
- ? (e) => {
253
- tooltip.x = e.clientX
254
- tooltip.y = e.clientY
255
- }
256
- : undefined}
257
- role="columnheader"
258
- tabindex={null}
259
- >
260
- {#if column.overflow?.modal}
392
+ </div>
393
+ <!-- {#if column.overflow?.truncated}
394
+ <div class="whitespace-nowrap">{row[column.key].slice(-5)}</div>
395
+ {/if} -->
396
+
397
+ {#if column.overflow?.copy}
261
398
  <button
262
- class="w-full cursor-pointer overflow-hidden text-left text-ellipsis whitespace-nowrap"
399
+ class="mx-2 flex cursor-pointer border-none bg-transparent text-2xl"
263
400
  onclick={(e) => {
264
- e.stopPropagation()
265
- showModal(row[column.key], column.overflow?.formatting)
401
+ e.preventDefault()
402
+ navigator.clipboard.writeText(row[column.key])
403
+ copiedCell = { x: column.key as string, y: index }
404
+ setTimeout(() => (copiedCell = { x: '', y: -1 }), 1000)
266
405
  }}
406
+ aria-label="Копировать текст"
267
407
  >
268
- {@html row[column.key]}
408
+ <div class=" size-5 text-sm [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full">
409
+ {#if copiedCell.y === index && copiedCell.x === column.key}
410
+ <div
411
+ class="absolute top-1/2 right-3.5 -translate-y-1/2 transform rounded-md bg-(--green-color) px-1.5 py-1 shadow-lg"
412
+ transition:fade={{ duration: 200 }}
413
+ >
414
+
415
+ </div>
416
+ {:else}
417
+ <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
418
+ <g fill="none" stroke="currentColor" stroke-width="1.5">
419
+ <path
420
+ d="M6 11c0-2.828 0-4.243.879-5.121C7.757 5 9.172 5 12 5h3c2.828 0 4.243 0 5.121.879C21 6.757 21 8.172 21 11v5c0 2.828 0 4.243-.879 5.121C19.243 22 17.828 22 15 22h-3c-2.828 0-4.243 0-5.121-.879C6 20.243 6 18.828 6 16z"
421
+ />
422
+ <path d="M6 19a3 3 0 0 1-3-3v-6c0-3.771 0-5.657 1.172-6.828S7.229 2 11 2h4a3 3 0 0 1 3 3" />
423
+ </g>
424
+ </svg>
425
+ {/if}
426
+ </div>
269
427
  </button>
270
- {:else}
271
- {@html row[column.key]}
272
428
  {/if}
273
- </div>
274
- <!-- {#if column.overflow?.truncated}
275
- <div class="whitespace-nowrap">{row[column.key].slice(-5)}</div>
276
- {/if} -->
277
-
278
- {#if column.overflow?.copy}
279
- <button
280
- class="mx-2 flex cursor-pointer border-none bg-transparent text-2xl"
281
- onclick={(e) => {
282
- e.preventDefault()
283
- navigator.clipboard.writeText(row[column.key])
284
- copiedCell = { x: column.key as string, y: index }
285
- setTimeout(() => (copiedCell = { x: '', y: -1 }), 1000)
286
- }}
287
- aria-label="Копировать текст"
288
- >
289
- <div class=" size-5 text-sm [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full">
290
- {#if copiedCell.y === index && copiedCell.x === column.key}
291
- <div
292
- class="absolute top-1/2 right-3.5 -translate-y-1/2 transform rounded-md bg-(--green-color) px-1.5 py-1 shadow-lg"
293
- transition:fade={{ duration: 200 }}
294
- >
295
-
296
- </div>
297
- {:else}
298
- <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
299
- <g fill="none" stroke="currentColor" stroke-width="1.5">
300
- <path
301
- d="M6 11c0-2.828 0-4.243.879-5.121C7.757 5 9.172 5 12 5h3c2.828 0 4.243 0 5.121.879C21 6.757 21 8.172 21 11v5c0 2.828 0 4.243-.879 5.121C19.243 22 17.828 22 15 22h-3c-2.828 0-4.243 0-5.121-.879C6 20.243 6 18.828 6 16z"
302
- />
303
- <path d="M6 19a3 3 0 0 1-3-3v-6c0-3.771 0-5.657 1.172-6.828S7.229 2 11 2h4a3 3 0 0 1 3 3" />
304
- </g>
305
- </svg>
306
- {/if}
307
- </div>
308
- </button>
309
429
  {/if}
310
- {/if}
311
- </div>
430
+ </div>
431
+ {/each}
312
432
  {/each}
313
- {/each}
433
+ </div>
314
434
  </div>
315
- </div>
435
+ {/if}
316
436
 
317
437
  {#if tooltip.show}
318
438
  <div
@@ -1,4 +1,4 @@
1
1
  import type { ITableProps } from '../types';
2
- declare const Table: import("svelte").Component<ITableProps<any>, {}, "modalData">;
2
+ declare const Table: import("svelte").Component<ITableProps<any>, {}, "body" | "modalData">;
3
3
  type Table = ReturnType<typeof Table>;
4
4
  export default Table;
@@ -24,7 +24,7 @@
24
24
  const DeviceVariables = getContext<{ id: string; value: string; name: string }[]>('DeviceVariables')
25
25
  let VARIABLE_OPTIONS = $derived(DeviceVariables && Array.isArray(DeviceVariables) ? DeviceVariables : [])
26
26
 
27
- let defaultIcon = $derived({ isModalOpen: false, columnIndex: 0, column: component.properties.header[0] })
27
+ let defaultIcon = $derived({ isModalOpen: false, columnIndex: 0, column: component.properties.header ? component.properties.header[0] : '' })
28
28
 
29
29
  const initialColor = $derived(
30
30
  $optionsStore.COLOR_OPTIONS.find((c) =>
@@ -95,6 +95,16 @@
95
95
  value={$optionsStore.ACCESS_OPTION.find((o) => o.value === component.access)}
96
96
  onUpdate={(option) => onPropertyChange({ access: option.value })}
97
97
  />
98
+ <UI.Select
99
+ label={{ name: $t('constructor.props.table.type') }}
100
+ type="buttons"
101
+ options={$optionsStore.TABLE_TYPE_OPTIONS}
102
+ value={$optionsStore.TABLE_TYPE_OPTIONS.find((o) => o.value === component.properties.type)}
103
+ onUpdate={(option) => {
104
+ updateProperty('type', option.value as string, component, onPropertyChange)
105
+ if (option.value === 'logger') updateProperty('stashData', true, component, onPropertyChange)
106
+ }}
107
+ />
98
108
  </div>
99
109
  <div class="flex w-1/3 flex-col px-2">
100
110
  <UI.Select
@@ -111,6 +121,14 @@
111
121
  options={[{ id: crypto.randomUUID(), value: 0, class: '' }]}
112
122
  onChange={(value) => updateProperty('outline', value, component, onPropertyChange)}
113
123
  />
124
+ <UI.Switch
125
+ label={{ name: $t('constructor.props.table.stashData') }}
126
+ value={component.properties.stashData}
127
+ options={[{ id: crypto.randomUUID(), value: 0, class: '', disabled: component.properties.type === 'logger' }]}
128
+ onChange={(value) => {
129
+ updateProperty('stashData', value, component, onPropertyChange)
130
+ }}
131
+ />
114
132
  </div>
115
133
  <div class="flex w-1/3 flex-col px-2">
116
134
  <UI.Input
@@ -125,6 +143,12 @@
125
143
  options={$optionsStore.TEXT_ALIGN_OPTIONS}
126
144
  onUpdate={(option) => updateProperty('label.class', twMerge(component.properties.label.class, option.value), component, onPropertyChange)}
127
145
  />
146
+ <UI.Input
147
+ label={{ name: $t('constructor.props.table.buffersize') }}
148
+ type="number"
149
+ value={component.properties.rowsAmmount}
150
+ onUpdate={(value) => updateProperty('rowsAmmount', value as string, component, onPropertyChange)}
151
+ />
128
152
  </div>
129
153
  </div>
130
154
 
@@ -155,10 +179,9 @@
155
179
  </div>
156
180
 
157
181
  {#each component.properties.header as column, columnIndex (columnIndex)}
158
- <div class="mr-2 flex items-end justify-around gap-6">
182
+ <div class="mr-2 grid grid-cols-[minmax(5rem,10rem)_1fr_minmax(5rem,10rem)_minmax(10rem,21rem)_6rem_6rem_2rem_2rem] items-end gap-6">
159
183
  <UI.Input
160
184
  label={{ name: $t('constructor.props.table.columns.key') }}
161
- wrapperClass="w-170"
162
185
  value={column.key}
163
186
  help={{ regExp: /^[0-9a-zA-Z_-]{0,16}$/ }}
164
187
  onUpdate={(value) => {
@@ -175,7 +198,6 @@
175
198
  />
176
199
  <UI.Input
177
200
  label={{ name: $t('constructor.props.table.columns.width') }}
178
- wrapperClass="w-150"
179
201
  type="number"
180
202
  value={Number(column.width.replace('%', ''))}
181
203
  onUpdate={(value) => updateTableHeader(columnIndex, 'width', `${value}%`)}
@@ -188,14 +210,12 @@
188
210
  onUpdate={(option) => updateTableHeader(columnIndex, 'align', option.value)}
189
211
  />
190
212
  <UI.Switch
191
- wrapperClass="w-30"
192
213
  label={{ name: $t('constructor.props.table.columns.sortable'), class: 'px-0' }}
193
214
  options={[{ id: crypto.randomUUID(), value: 0, class: '' }]}
194
215
  value={column.sortable}
195
216
  onChange={(value) => updateTableHeader(columnIndex, 'sortable', value)}
196
217
  />
197
218
  <UI.Switch
198
- wrapperClass="w-30"
199
219
  label={{ name: $t('constructor.props.copy'), class: 'px-0' }}
200
220
  options={[{ id: crypto.randomUUID(), value: 0, class: '' }]}
201
221
  value={column.overflow?.copy}
@@ -314,6 +334,12 @@
314
334
  updateProperty('options', options, component, onPropertyChange)
315
335
  }}
316
336
  />
337
+ <UI.Switch
338
+ label={{ name: $t('constructor.props.outline') }}
339
+ value={component.properties.outline}
340
+ options={[{ id: crypto.randomUUID(), value: 0, class: '' }]}
341
+ onChange={(value) => updateProperty('outline', value, component, onPropertyChange)}
342
+ />
317
343
  </div>
318
344
  <div class="flex w-1/3 flex-col px-2">
319
345
  <UI.Select
@@ -333,19 +359,37 @@
333
359
  value={component.properties.label.class}
334
360
  onUpdate={(value) => updateProperty('label.class', value as string, component, onPropertyChange)}
335
361
  />
336
- </div>
337
-
338
- <div class="flex w-1/3 flex-col px-2">
339
362
  <UI.Input
340
363
  label={{ name: $t('constructor.props.footer') }}
341
364
  value={component.properties.footer}
342
365
  onUpdate={(value) => updateProperty('footer', value as string, component, onPropertyChange)}
343
366
  />
367
+ </div>
368
+
369
+ <div class="flex w-1/3 flex-col px-2">
370
+ <UI.Select
371
+ label={{ name: $t('constructor.props.table.type') }}
372
+ type="buttons"
373
+ options={$optionsStore.TABLE_TYPE_OPTIONS}
374
+ value={$optionsStore.TABLE_TYPE_OPTIONS.find((o) => o.value === component.properties.type)}
375
+ onUpdate={(option) => {
376
+ updateProperty('type', option.value as string, component, onPropertyChange)
377
+ if (option.value === 'logger') updateProperty('stashData', true, component, onPropertyChange)
378
+ }}
379
+ />
344
380
  <UI.Switch
345
- label={{ name: $t('constructor.props.outline') }}
346
- value={component.properties.outline}
347
- options={[{ id: crypto.randomUUID(), value: 0, class: '' }]}
348
- onChange={(value) => updateProperty('outline', value, component, onPropertyChange)}
381
+ label={{ name: $t('constructor.props.table.stashData') }}
382
+ value={component.properties.stashData}
383
+ options={[{ id: crypto.randomUUID(), value: 0, class: '', disabled: component.properties.type === 'logger' }]}
384
+ onChange={(value) => {
385
+ updateProperty('stashData', value, component, onPropertyChange)
386
+ }}
387
+ />
388
+ <UI.Input
389
+ label={{ name: $t('constructor.props.table.buffersize') }}
390
+ type="number"
391
+ value={component.properties.rowsAmmount}
392
+ onUpdate={(value) => updateProperty('rowsAmmount', value as string, component, onPropertyChange)}
349
393
  />
350
394
  </div>
351
395
  </div>
@@ -380,10 +424,9 @@
380
424
  {#each component.properties.header as column, columnIndex (columnIndex)}
381
425
  <div class="rounded-2xl border border-(--border-color) p-2">
382
426
  <div class="mb-5">
383
- <div class="mr-2 flex items-end justify-around gap-6">
427
+ <div class="mr-2 grid grid-cols-[minmax(5rem,10rem)_1fr_minmax(5rem,10rem)_minmax(10rem,21rem)_6rem_6rem_2rem_2rem] items-end gap-6">
384
428
  <UI.Input
385
429
  label={{ name: $t('constructor.props.table.columns.key') }}
386
- wrapperClass="w-150"
387
430
  value={column.key}
388
431
  help={{ regExp: /^[0-9a-zA-Z_-]{0,16}$/ }}
389
432
  onUpdate={(value) => {
@@ -400,21 +443,25 @@
400
443
  />
401
444
  <UI.Input
402
445
  label={{ name: $t('constructor.props.table.columns.width'), class: 'px-0' }}
403
- wrapperClass="w-150"
404
446
  type="number"
405
447
  value={Number(column.width.replace('%', ''))}
406
448
  onUpdate={(value) => updateTableHeader(columnIndex, 'width', `${value}%`)}
407
449
  />
450
+ <UI.Select
451
+ label={{ name: $t('constructor.props.align.content') }}
452
+ type="buttons"
453
+ value={$optionsStore.ALIGN_OPTIONS.find((a) => (a.value as string).includes(column.align))}
454
+ options={$optionsStore.ALIGN_OPTIONS}
455
+ onUpdate={(option) => updateTableHeader(columnIndex, 'align', option.value)}
456
+ />
408
457
  <UI.Switch
409
458
  label={{ name: $t('constructor.props.table.columns.sortable'), class: 'px-0' }}
410
- wrapperClass="w-30"
411
459
  options={[{ id: crypto.randomUUID(), value: 0, class: '' }]}
412
460
  value={column.sortable}
413
461
  onChange={(value) => updateTableHeader(columnIndex, 'sortable', value)}
414
462
  />
415
463
  <UI.Switch
416
464
  label={{ name: $t('constructor.props.copy'), class: 'px-0' }}
417
- wrapperClass="w-30"
418
465
  options={[{ id: crypto.randomUUID(), value: 0, class: '' }]}
419
466
  value={column.overflow?.copy}
420
467
  onChange={(value) => updateTableHeader(columnIndex, 'overflow', { copy: value, truncated: column.overflow?.truncated })}
@@ -446,22 +493,14 @@
446
493
  }}
447
494
  />
448
495
  </div>
449
- <div class="mr-2 flex items-end justify-around gap-6">
450
- <UI.Select
451
- label={{ name: $t('constructor.props.align.content') }}
452
- type="buttons"
453
- value={$optionsStore.ALIGN_OPTIONS.find((a) => (a.value as string).includes(column.align))}
454
- options={$optionsStore.ALIGN_OPTIONS}
455
- onUpdate={(option) => updateTableHeader(columnIndex, 'align', option.value)}
456
- />
496
+ <div class="mr-2 grid grid-cols-[5rem_minmax(8rem,16rem)_1fr_minmax(8rem,12rem)_minmax(8rem,12rem)] items-end justify-between gap-6">
457
497
  <UI.Switch
458
- wrapperClass="w-2/10"
459
- label={{ name: $t('constructor.props.table.columns.truncated') }}
498
+ label={{ name: $t('constructor.props.table.columns.truncated'), class: 'px-0' }}
460
499
  options={[{ id: crypto.randomUUID(), value: 0, class: '' }]}
461
500
  value={column.overflow?.truncated}
462
501
  onChange={(value) => updateTableHeader(columnIndex, 'overflow', { truncated: value, copy: column.overflow?.copy })}
463
502
  />
464
- <div class="relative mt-6 flex w-full gap-2">
503
+ <div class="relative mt-6 flex items-center w-full gap-2">
465
504
  <UI.Button
466
505
  content={{ name: $t('constructor.props.table.columns.defaultIcon') }}
467
506
  onClick={() => (defaultIcon = { isModalOpen: true, columnIndex: columnIndex, column: column })}
@@ -498,7 +537,6 @@
498
537
  label={{ name: $t('constructor.props.table.columns.image.width'), class: 'px-0' }}
499
538
  type="number"
500
539
  number={{ minNum: 0, maxNum: 1000, step: 1 }}
501
- wrapperClass="w-150"
502
540
  value={Number(column.image?.width.replace('rem', '')) ?? 0}
503
541
  onUpdate={(value) => {
504
542
  updateTableHeader(columnIndex, 'image', {
@@ -513,7 +551,6 @@
513
551
  label={{ name: $t('constructor.props.table.columns.image.height'), class: 'px-0' }}
514
552
  type="number"
515
553
  number={{ minNum: 0, maxNum: 1000, step: 1 }}
516
- wrapperClass="w-150"
517
554
  value={Number(column.image?.height.replace('rem', ''))}
518
555
  onUpdate={(value) => {
519
556
  updateTableHeader(columnIndex, 'image', {
@@ -132,6 +132,9 @@ const translations = {
132
132
  'constructor.props.joystick.axes.info': 'Поле для ввода названий осей, разделенных пробелами (2 или 3 названия)',
133
133
  'constructor.props.file.select': 'Выберите файл',
134
134
  'constructor.props.file.notselected': 'Файл не выбран',
135
+ 'constructor.props.table.type': 'Тип таблицы',
136
+ 'constructor.props.table.type.table': 'Статическая таблица',
137
+ 'constructor.props.table.type.logger': 'Таблица для логов',
135
138
  'constructor.props.table.columns': 'Колонки таблицы',
136
139
  'constructor.props.table.columns.key': 'Ключ',
137
140
  'constructor.props.table.columns.label': 'Название колонки',
@@ -148,6 +151,8 @@ const translations = {
148
151
  'constructor.props.table.addaction': 'Добавить кнопку',
149
152
  'constructor.props.table.keys': 'Перечень ключей',
150
153
  'constructor.props.table.keys.info': 'Ключи таблицы, значения которых будут возвращаться',
154
+ 'constructor.props.table.stashData': 'Накопление данных',
155
+ 'constructor.props.table.buffersize': 'Размер буфера',
151
156
  'constructor.props.icon.access': 'Доступ',
152
157
  'constructor.props.icon.common': 'Общее',
153
158
  'constructor.props.icon.scenarios': 'Сценарии',
package/dist/options.d.ts CHANGED
@@ -115,6 +115,11 @@ export declare const optionsStore: import("svelte/store").Readable<{
115
115
  value: string;
116
116
  name: string;
117
117
  }[];
118
+ TABLE_TYPE_OPTIONS: {
119
+ id: string;
120
+ value: string;
121
+ name: string;
122
+ }[];
118
123
  AUTOCOMPLETE_CONSTRUCTOR_OPTIONS: {
119
124
  id: string;
120
125
  value: string;
package/dist/options.js CHANGED
@@ -124,6 +124,10 @@ export const optionsStore = derived(t, ($t) => {
124
124
  { id: id(), value: 'square', name: $t('constructor.props.type.square') },
125
125
  { id: id(), value: 'circle', name: $t('constructor.props.type.circle') },
126
126
  ],
127
+ TABLE_TYPE_OPTIONS: [
128
+ { id: id(), value: 'table', name: $t('constructor.props.table.type.table') },
129
+ { id: id(), value: 'logger', name: $t('constructor.props.table.type.logger') },
130
+ ],
127
131
  AUTOCOMPLETE_CONSTRUCTOR_OPTIONS: [
128
132
  { id: id(), value: 'on', name: $t('constructor.props.autocomplete.on') },
129
133
  { id: id(), value: 'off', name: $t('constructor.props.autocomplete.off') },
package/dist/types.d.ts CHANGED
@@ -262,9 +262,12 @@ export interface ITableProps<T extends object> {
262
262
  name?: string;
263
263
  class?: string;
264
264
  };
265
- header: ITableHeader<T>[];
266
- body: T[];
265
+ header?: ITableHeader<T>[];
266
+ body: T[] | T | null;
267
267
  footer?: string;
268
+ type?: 'table' | 'logger';
269
+ stashData?: boolean;
270
+ rowsAmmount?: number;
268
271
  outline?: boolean;
269
272
  cursor?: string | null;
270
273
  loader?: Writable<boolean>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-svelte-ui-lib",
3
- "version": "1.4.8",
3
+ "version": "1.5.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -8,7 +8,7 @@
8
8
  "build": "vite build",
9
9
  "preview": "vite preview",
10
10
  "prepack": "svelte-kit sync && svelte-package && publint",
11
- "CheckUpdate": "npx npm-check-updates -u && npm install",
11
+ "CheckUpdate": "npx npm-check-updates -u && npm install && npm install prettier@3.6.2",
12
12
  "UpdateIconsLib": "tsx src/lib/IconsCatalog/iconsLib.ts"
13
13
  },
14
14
  "svelte": "./dist/index.js",
@@ -49,9 +49,9 @@
49
49
  "@sveltejs/vite-plugin-svelte": "^6.2.1",
50
50
  "@types/node": "^24.10.1",
51
51
  "publint": "^0.3.15",
52
- "svelte": "^5.45.4",
52
+ "svelte": "^5.45.6",
53
53
  "svelte-preprocess": "^6.0.3",
54
- "vite": "^7.2.6",
54
+ "vite": "^7.2.7",
55
55
  "vite-plugin-compression": "^0.5.1"
56
56
  }
57
57
  }