@evergis/react 4.0.142 → 4.0.144
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.
- package/dist/components/Dashboard/containers/StructuredDataContainer/types.d.ts +12 -1
- package/dist/components/Dashboard/elements/ElementTable/components/AttachmentsCell.d.ts +9 -0
- package/dist/components/Dashboard/elements/ElementTable/components/TableHeadRow.d.ts +9 -0
- package/dist/components/Dashboard/elements/ElementTable/constants.d.ts +2 -0
- package/dist/components/Dashboard/elements/ElementTable/hooks/useAttachmentsCell.d.ts +26 -0
- package/dist/components/Dashboard/elements/ElementTable/hooks/useColumnResize.d.ts +20 -0
- package/dist/components/Dashboard/elements/ElementTable/hooks/useTableView.d.ts +7 -0
- package/dist/components/Dashboard/elements/ElementTable/styled.d.ts +38 -3
- package/dist/components/Dashboard/elements/ElementTable/types.d.ts +14 -0
- package/dist/components/Dashboard/elements/ElementTable/utils/columnStyle.d.ts +11 -0
- package/dist/components/Dashboard/hooks/useDataSources.d.ts +0 -5
- package/dist/components/Dashboard/types.d.ts +34 -0
- package/dist/index.js +254 -52
- package/dist/index.js.map +1 -1
- package/dist/react.esm.js +254 -52
- package/dist/react.esm.js.map +1 -1
- package/package.json +2 -2
package/dist/react.esm.js
CHANGED
|
@@ -7687,33 +7687,10 @@ const useDataSources = ({ type: widgetType, config, attributes, filters, layerPa
|
|
|
7687
7687
|
});
|
|
7688
7688
|
return newDataSources;
|
|
7689
7689
|
}, []);
|
|
7690
|
-
const zoomToLayersExtent = useCallback(async (layers) => {
|
|
7691
|
-
const newExtent = await api.layers.getBulkExtents({ srId: 4326 }, layers?.map(({ layerName, conditions, parameters }) => ({
|
|
7692
|
-
layerName,
|
|
7693
|
-
conditions,
|
|
7694
|
-
parameters,
|
|
7695
|
-
})));
|
|
7696
|
-
const { coordinates } = newExtent.overall || {};
|
|
7697
|
-
if (!coordinates) {
|
|
7698
|
-
return;
|
|
7699
|
-
}
|
|
7700
|
-
const hasEmptyCoordinate = coordinates.some(([x, y]) => !x && !y);
|
|
7701
|
-
if (hasEmptyCoordinate) {
|
|
7702
|
-
return;
|
|
7703
|
-
}
|
|
7704
|
-
/* const bbox = new Bbox(coordinates[0], coordinates[1], map.crs);
|
|
7705
|
-
const resolution = Math.max(bbox.width / painter.width, bbox.height / painter.height);
|
|
7706
|
-
|
|
7707
|
-
animateTo({
|
|
7708
|
-
position: bbox.center,
|
|
7709
|
-
resolution: map.getAdjustedResolution(resolution),
|
|
7710
|
-
});*/
|
|
7711
|
-
}, [api.layers]);
|
|
7712
7690
|
return {
|
|
7713
7691
|
getDataSourcePromises,
|
|
7714
7692
|
getUpdatingDataSources,
|
|
7715
7693
|
getUpdatedDataSources,
|
|
7716
|
-
zoomToLayersExtent,
|
|
7717
7694
|
};
|
|
7718
7695
|
};
|
|
7719
7696
|
|
|
@@ -11428,10 +11405,17 @@ const toSchemaAttribute = (params) => {
|
|
|
11428
11405
|
attributeName,
|
|
11429
11406
|
// Тип принадлежит источнику: конфиг задаёт его только там, где источника нет.
|
|
11430
11407
|
type: source?.type ?? description?.type ?? DEFAULT_ATTRIBUTE_TYPE,
|
|
11408
|
+
// Уточнение типа — целиком за конфигом: у атрибута источника поля `subType` нет.
|
|
11409
|
+
subType: description?.subType,
|
|
11431
11410
|
alias: description?.alias ?? source?.alias ?? attributeName,
|
|
11432
11411
|
description: description?.description ?? source?.description,
|
|
11433
11412
|
isEditable: resolveIsEditable(params),
|
|
11434
11413
|
stringFormat: resolveStringFormat(description, source),
|
|
11414
|
+
// Раскладка колонки живёт только в конфиге контейнера: источник о ширине таблицы не знает.
|
|
11415
|
+
width: description?.width,
|
|
11416
|
+
resizable: description?.resizable,
|
|
11417
|
+
multiline: description?.multiline,
|
|
11418
|
+
style: description?.style,
|
|
11435
11419
|
};
|
|
11436
11420
|
};
|
|
11437
11421
|
/**
|
|
@@ -15462,6 +15446,8 @@ const TIME_FORMAT = /hh:mm/;
|
|
|
15462
15446
|
* закрытие редактора по клику снаружи обязано этот контейнер пропускать.
|
|
15463
15447
|
*/
|
|
15464
15448
|
const PORTAL_ROOT_SELECTOR = "#portal-root";
|
|
15449
|
+
/** Ниже этой ширины колонку не ужать мышью: уже неё в ячейке не остаётся места под значение. */
|
|
15450
|
+
const MIN_COLUMN_WIDTH = 48;
|
|
15465
15451
|
|
|
15466
15452
|
/**
|
|
15467
15453
|
* Собственный бокс таблицы. Без размеров в `options` представления узел ничего не меняет:
|
|
@@ -15518,10 +15504,13 @@ const TableHead = styled.thead.withConfig({ displayName: "TableHead", componentI
|
|
|
15518
15504
|
z-index: 1;
|
|
15519
15505
|
`;
|
|
15520
15506
|
const TableHeadCell = styled.th.withConfig({ displayName: "TableHeadCell", componentId: "sc-1m6xil2" }) `
|
|
15507
|
+
/* Относительное позиционирование держит захват границы колонки (TableResizeHandle). */
|
|
15508
|
+
position: relative;
|
|
15521
15509
|
padding: 0.375rem 0.5rem;
|
|
15522
15510
|
text-align: left;
|
|
15523
15511
|
font-weight: 600;
|
|
15524
15512
|
white-space: nowrap;
|
|
15513
|
+
overflow: hidden;
|
|
15525
15514
|
color: ${({ theme }) => theme.palette.textSecondary};
|
|
15526
15515
|
/* Рамка принадлежит ячейке (рамки раздельные), поэтому едет вместе с липкой шапкой. */
|
|
15527
15516
|
border-bottom: 1px solid ${({ theme }) => theme.palette.elementDeep};
|
|
@@ -15532,10 +15521,37 @@ const TableHeadCell = styled.th.withConfig({ displayName: "TableHeadCell", compo
|
|
|
15532
15521
|
user-select: none;
|
|
15533
15522
|
`}
|
|
15534
15523
|
`;
|
|
15524
|
+
/**
|
|
15525
|
+
* Содержимое заголовка. Псевдоним обрезается многоточием, значок сортировки не сжимается:
|
|
15526
|
+
* у колонки с заданной шириной длинный псевдоним иначе выдавил бы значок за край ячейки.
|
|
15527
|
+
*/
|
|
15535
15528
|
const TableHeadContent = styled.span.withConfig({ displayName: "TableHeadContent", componentId: "sc-8hzcop" }) `
|
|
15536
|
-
display:
|
|
15529
|
+
display: flex;
|
|
15537
15530
|
align-items: center;
|
|
15538
15531
|
gap: 0.25rem;
|
|
15532
|
+
min-width: 0;
|
|
15533
|
+
overflow: hidden;
|
|
15534
|
+
text-overflow: ellipsis;
|
|
15535
|
+
`;
|
|
15536
|
+
/**
|
|
15537
|
+
* Захват правой границы колонки. Лежит поверх края ячейки и ловит указатель сам, поэтому
|
|
15538
|
+
* перетаскивание не доходит до заголовка и не переключает сортировку.
|
|
15539
|
+
*
|
|
15540
|
+
* Ширина в пикселах, а не в rem: это зона попадания курсора, и от размера шрифта она не зависит.
|
|
15541
|
+
*/
|
|
15542
|
+
const TableResizeHandle = styled.span.withConfig({ displayName: "TableResizeHandle", componentId: "sc-1g0q105" }) `
|
|
15543
|
+
position: absolute;
|
|
15544
|
+
top: 0;
|
|
15545
|
+
right: 0;
|
|
15546
|
+
bottom: 0;
|
|
15547
|
+
width: 8px;
|
|
15548
|
+
cursor: col-resize;
|
|
15549
|
+
touch-action: none;
|
|
15550
|
+
user-select: none;
|
|
15551
|
+
|
|
15552
|
+
:hover {
|
|
15553
|
+
background: ${({ theme }) => theme.palette.elementDeep};
|
|
15554
|
+
}
|
|
15539
15555
|
`;
|
|
15540
15556
|
/**
|
|
15541
15557
|
* Место под значок сортировки. Занято всегда, пока сортировка включена, а сам значок только
|
|
@@ -15543,7 +15559,7 @@ const TableHeadContent = styled.span.withConfig({ displayName: "TableHeadContent
|
|
|
15543
15559
|
* клик по заголовку, вместе с телом. Оба направления рисуются значком в 1rem, поэтому
|
|
15544
15560
|
* переключение asc/des ширину тоже не трогает.
|
|
15545
15561
|
*/
|
|
15546
|
-
const TableSortSlot = styled.span.withConfig({ displayName: "TableSortSlot", componentId: "sc-
|
|
15562
|
+
const TableSortSlot = styled.span.withConfig({ displayName: "TableSortSlot", componentId: "sc-1ceykgw" }) `
|
|
15547
15563
|
display: inline-flex;
|
|
15548
15564
|
visibility: ${({ $active }) => ($active ? "visible" : "hidden")};
|
|
15549
15565
|
`;
|
|
@@ -15552,36 +15568,54 @@ const TableSortSlot = styled.span.withConfig({ displayName: "TableSortSlot", com
|
|
|
15552
15568
|
* её нижнюю кромку и сквозь прозрачную шапку не проступают. Величину пишет {@link useHeadOverlap},
|
|
15553
15569
|
* без неё отсечка нулевая и разметка не меняется.
|
|
15554
15570
|
*/
|
|
15555
|
-
const TableBody = styled.tbody.withConfig({ displayName: "TableBody", componentId: "sc-
|
|
15571
|
+
const TableBody = styled.tbody.withConfig({ displayName: "TableBody", componentId: "sc-c9drzt" }) `
|
|
15556
15572
|
clip-path: inset(var(${HEAD_OVERLAP_VARIABLE}, 0px) 0 0 0);
|
|
15557
15573
|
`;
|
|
15558
15574
|
/**
|
|
15559
15575
|
* Строка. Разделитель рисуют её ячейки: собственная рамка строки при раздельных рамках не
|
|
15560
15576
|
* рисуется вовсе, а при схлопнутых её рисовала бы таблица — и отсечка тела её бы не резала.
|
|
15561
15577
|
*/
|
|
15562
|
-
const TableRow = styled.tr.withConfig({ displayName: "TableRow", componentId: "sc-
|
|
15578
|
+
const TableRow = styled.tr.withConfig({ displayName: "TableRow", componentId: "sc-155nkv8" }) `
|
|
15563
15579
|
td {
|
|
15564
15580
|
border-bottom: 1px solid ${({ theme }) => theme.palette.elementDeep};
|
|
15565
15581
|
}
|
|
15566
15582
|
`;
|
|
15567
|
-
const TableCellWrapper = styled.td.withConfig({ displayName: "TableCellWrapper", componentId: "sc-
|
|
15583
|
+
const TableCellWrapper = styled.td.withConfig({ displayName: "TableCellWrapper", componentId: "sc-7ueth1" }) `
|
|
15568
15584
|
padding: 0.125rem 0.25rem;
|
|
15569
15585
|
vertical-align: middle;
|
|
15570
15586
|
/* Предел снимает заданная ширина: с колонками по содержимому обрезать значение незачем. */
|
|
15571
15587
|
max-width: var(--table-cell-max-width, 20rem);
|
|
15572
15588
|
`;
|
|
15573
15589
|
/** Колонка действий: узкая, не растягивается содержимым. */
|
|
15574
|
-
const TableActionsCell = styled(TableCellWrapper).withConfig({ displayName: "TableActionsCell", componentId: "sc-
|
|
15590
|
+
const TableActionsCell = styled(TableCellWrapper).withConfig({ displayName: "TableActionsCell", componentId: "sc-1m7p53b" }) `
|
|
15575
15591
|
width: 2rem;
|
|
15576
15592
|
text-align: right;
|
|
15577
15593
|
`;
|
|
15578
|
-
|
|
15594
|
+
/**
|
|
15595
|
+
* Как значение ведёт себя, когда не помещается в колонку: режется многоточием в одну строку
|
|
15596
|
+
* или переносится по словам, растягивая строку таблицы вниз (`multiline` атрибута).
|
|
15597
|
+
*
|
|
15598
|
+
* Перенос разрешаем и посреди слова: колонка бывает уже одного длинного слова (ссылка, артикул),
|
|
15599
|
+
* и без этого оно вылезло бы за заданную ширину, сделав её бессмысленной.
|
|
15600
|
+
*/
|
|
15601
|
+
const cellWrapMixin = css `
|
|
15602
|
+
${({ $multiline }) => $multiline
|
|
15603
|
+
? css `
|
|
15604
|
+
white-space: normal;
|
|
15605
|
+
overflow-wrap: anywhere;
|
|
15606
|
+
`
|
|
15607
|
+
: css `
|
|
15608
|
+
overflow: hidden;
|
|
15609
|
+
text-overflow: ellipsis;
|
|
15610
|
+
white-space: nowrap;
|
|
15611
|
+
`}
|
|
15612
|
+
`;
|
|
15613
|
+
const CellText = styled.div.withConfig({ displayName: "CellText", componentId: "sc-19uakee" }) `
|
|
15579
15614
|
padding: 0.375rem 0.25rem;
|
|
15580
15615
|
/* Та же высота строки, что у редактора — иначе строка подпрыгивала бы по вертикали. */
|
|
15581
15616
|
line-height: 1.25rem;
|
|
15582
|
-
|
|
15583
|
-
|
|
15584
|
-
white-space: nowrap;
|
|
15617
|
+
|
|
15618
|
+
${cellWrapMixin};
|
|
15585
15619
|
`;
|
|
15586
15620
|
/**
|
|
15587
15621
|
* Редактируемая ячейка вне фокуса. Показывает значение по `stringFormat`, а редактор
|
|
@@ -15590,7 +15624,7 @@ const CellText = styled.div.withConfig({ displayName: "CellText", componentId: "
|
|
|
15590
15624
|
* Кнопка, а не просто текст, именно ради фокуса: она — таб-стоп ячейки, и Tab по таблице
|
|
15591
15625
|
* открывает редактор с курсором внутри.
|
|
15592
15626
|
*/
|
|
15593
|
-
const CellButton = styled.button.withConfig({ displayName: "CellButton", componentId: "sc-
|
|
15627
|
+
const CellButton = styled.button.withConfig({ displayName: "CellButton", componentId: "sc-16lgve4" }) `
|
|
15594
15628
|
width: 100%;
|
|
15595
15629
|
padding: 0.375rem 0.25rem;
|
|
15596
15630
|
line-height: 1.25rem;
|
|
@@ -15601,15 +15635,14 @@ const CellButton = styled.button.withConfig({ displayName: "CellButton", compone
|
|
|
15601
15635
|
color: inherit;
|
|
15602
15636
|
text-align: left;
|
|
15603
15637
|
cursor: text;
|
|
15604
|
-
|
|
15605
|
-
|
|
15606
|
-
white-space: nowrap;
|
|
15638
|
+
|
|
15639
|
+
${cellWrapMixin};
|
|
15607
15640
|
|
|
15608
15641
|
:hover {
|
|
15609
15642
|
border-color: ${({ theme }) => theme.palette.elementDeep};
|
|
15610
15643
|
}
|
|
15611
15644
|
`;
|
|
15612
|
-
const CellPlaceholder = styled.span.withConfig({ displayName: "CellPlaceholder", componentId: "sc-
|
|
15645
|
+
const CellPlaceholder = styled.span.withConfig({ displayName: "CellPlaceholder", componentId: "sc-1jnsjad" }) `
|
|
15613
15646
|
color: ${({ theme }) => theme.palette.textSecondary};
|
|
15614
15647
|
`;
|
|
15615
15648
|
/**
|
|
@@ -15618,20 +15651,21 @@ const CellPlaceholder = styled.span.withConfig({ displayName: "CellPlaceholder",
|
|
|
15618
15651
|
* Копия остаётся в потоке и держит ширину колонки ровно такой, какой она была до клика, —
|
|
15619
15652
|
* иначе таблица дёргалась бы на каждый вход в правку и выход из неё.
|
|
15620
15653
|
*/
|
|
15621
|
-
const CellEditor = styled.div.withConfig({ displayName: "CellEditor", componentId: "sc-
|
|
15654
|
+
const CellEditor = styled.div.withConfig({ displayName: "CellEditor", componentId: "sc-ee08kh" }) `
|
|
15622
15655
|
position: relative;
|
|
15623
15656
|
min-height: 2rem;
|
|
15624
15657
|
`;
|
|
15625
|
-
const CellGhost = styled.span.withConfig({ displayName: "CellGhost", componentId: "sc-
|
|
15658
|
+
const CellGhost = styled.span.withConfig({ displayName: "CellGhost", componentId: "sc-1p10qm6" }) `
|
|
15626
15659
|
display: block;
|
|
15627
15660
|
padding: 0.375rem 0.25rem;
|
|
15628
15661
|
/* Рамка повторяет CellButton — с ней высота ячейки в правке совпадает с высотой на чтении. */
|
|
15629
15662
|
border: 1px solid transparent;
|
|
15630
15663
|
line-height: 1.25rem;
|
|
15631
|
-
white-space: nowrap;
|
|
15632
15664
|
visibility: hidden;
|
|
15665
|
+
|
|
15666
|
+
${cellWrapMixin};
|
|
15633
15667
|
`;
|
|
15634
|
-
const CellField = styled.div.withConfig({ displayName: "CellField", componentId: "sc-
|
|
15668
|
+
const CellField = styled.div.withConfig({ displayName: "CellField", componentId: "sc-cnkcp6" }) `
|
|
15635
15669
|
position: absolute;
|
|
15636
15670
|
inset: 0;
|
|
15637
15671
|
display: flex;
|
|
@@ -15646,11 +15680,97 @@ const CellField = styled.div.withConfig({ displayName: "CellField", componentId:
|
|
|
15646
15680
|
min-width: 0;
|
|
15647
15681
|
}
|
|
15648
15682
|
`;
|
|
15649
|
-
|
|
15683
|
+
/**
|
|
15684
|
+
* Ячейка со вложениями. Список идёт колонкой, кнопка добавления — под ним, поэтому строка растёт
|
|
15685
|
+
* ровно на столько, сколько файлов в ней лежит.
|
|
15686
|
+
*
|
|
15687
|
+
* Отступы те же, что у текстовой ячейки (`CellText`): в одном ряду с обычными колонками значения
|
|
15688
|
+
* обязаны стоять на одной линии.
|
|
15689
|
+
*/
|
|
15690
|
+
const AttachmentsCellBox = styled.div.withConfig({ displayName: "AttachmentsCellBox", componentId: "sc-9n5afp" }) `
|
|
15691
|
+
display: flex;
|
|
15692
|
+
flex-direction: column;
|
|
15693
|
+
align-items: flex-start;
|
|
15694
|
+
gap: 0.25rem;
|
|
15695
|
+
padding: 0.375rem 0.25rem;
|
|
15696
|
+
`;
|
|
15697
|
+
const TableEmpty = styled.div.withConfig({ displayName: "TableEmpty", componentId: "sc-1c196zb" }) `
|
|
15650
15698
|
padding: 0.75rem 0.25rem;
|
|
15651
15699
|
color: ${({ theme }) => theme.palette.textSecondary};
|
|
15652
15700
|
`;
|
|
15653
15701
|
|
|
15702
|
+
/**
|
|
15703
|
+
* Ячейка с вложениями: разбор значения, просмотр, скачивание и правка списка.
|
|
15704
|
+
*
|
|
15705
|
+
* Значение атрибута — строка: файлы приезжают в ней списком JSON, поэтому и обратно в черновик
|
|
15706
|
+
* кладём строку, а не массив — иначе тип ячейки менялся бы от самой правки.
|
|
15707
|
+
*
|
|
15708
|
+
* Добавление здесь только по ссылке. Загрузка с диска требует ресурса-родителя и файлового
|
|
15709
|
+
* api контейнера вложений, а у колонки таблицы ни того, ни другого нет.
|
|
15710
|
+
*/
|
|
15711
|
+
const useAttachmentsCell = ({ attribute, row, canEdit, onChange }) => {
|
|
15712
|
+
const [previewIndex, setPreviewIndex] = useState(null);
|
|
15713
|
+
const [isLinkDialogOpen, setLinkDialogOpen] = useState(false);
|
|
15714
|
+
const { attributeName, isEditable } = attribute;
|
|
15715
|
+
const value = row.properties[attributeName];
|
|
15716
|
+
const items = useMemo(() => parseAttachments(value), [value]);
|
|
15717
|
+
const previewImages = useAttachmentPreviewImages({ items, active: previewIndex !== null });
|
|
15718
|
+
const downloadByIndex = useAttachmentDownload(items);
|
|
15719
|
+
// Те же два условия, что и у обычной ячейки: режим таблицы и разрешение самого атрибута.
|
|
15720
|
+
const editable = canEdit && isEditable;
|
|
15721
|
+
const persist = useCallback((next) => onChange(row.key, attributeName, JSON.stringify(next)), [attributeName, onChange, row.key]);
|
|
15722
|
+
const onPreview = useCallback((link) => {
|
|
15723
|
+
const index = items.findIndex(item => item.link === link);
|
|
15724
|
+
if (index >= 0) {
|
|
15725
|
+
setPreviewIndex(index);
|
|
15726
|
+
}
|
|
15727
|
+
}, [items]);
|
|
15728
|
+
const onClosePreview = useCallback(() => setPreviewIndex(null), []);
|
|
15729
|
+
const onDownload = useCallback((_image, index) => downloadByIndex(index), [downloadByIndex]);
|
|
15730
|
+
const onDelete = useCallback((link) => persist(items.filter(item => item.link !== link)), [items, persist]);
|
|
15731
|
+
const onOpenLinkDialog = useCallback(() => setLinkDialogOpen(true), []);
|
|
15732
|
+
const onCloseLinkDialog = useCallback(() => setLinkDialogOpen(false), []);
|
|
15733
|
+
const onAddByLink = useCallback((url) => persist([
|
|
15734
|
+
...items,
|
|
15735
|
+
{
|
|
15736
|
+
link: url,
|
|
15737
|
+
name: getFileNameFromUrl(url),
|
|
15738
|
+
mimeType: getMimeTypeFromUrl(url),
|
|
15739
|
+
date: new Date().toISOString(),
|
|
15740
|
+
isExternal: true,
|
|
15741
|
+
},
|
|
15742
|
+
]), [items, persist]);
|
|
15743
|
+
return {
|
|
15744
|
+
items,
|
|
15745
|
+
editable,
|
|
15746
|
+
previewIndex,
|
|
15747
|
+
previewImages,
|
|
15748
|
+
isLinkDialogOpen,
|
|
15749
|
+
onPreview,
|
|
15750
|
+
onClosePreview,
|
|
15751
|
+
onDownload,
|
|
15752
|
+
onDelete,
|
|
15753
|
+
onOpenLinkDialog,
|
|
15754
|
+
onCloseLinkDialog,
|
|
15755
|
+
onAddByLink,
|
|
15756
|
+
};
|
|
15757
|
+
};
|
|
15758
|
+
|
|
15759
|
+
/**
|
|
15760
|
+
* Колонка со вложениями: список файлов прямо в ячейке.
|
|
15761
|
+
*
|
|
15762
|
+
* Тип у атрибута строковый, вложениями его делает `subType: "Attachments"` в схеме контейнера —
|
|
15763
|
+
* поэтому ветка стоит раньше разбора типа и обычных редакторов ячейки.
|
|
15764
|
+
*/
|
|
15765
|
+
const AttachmentsCell = memo(({ attribute, row, canEdit, onChange }) => {
|
|
15766
|
+
const { t } = useGlobalContext();
|
|
15767
|
+
const { items, editable, previewIndex, previewImages, isLinkDialogOpen, onPreview, onClosePreview, onDownload, onDelete, onOpenLinkDialog, onCloseLinkDialog, onAddByLink, } = useAttachmentsCell({ attribute, row, canEdit, onChange });
|
|
15768
|
+
return (jsxs(AttachmentsCellBox, { children: [!items.length && !editable && jsx(CellPlaceholder, { children: "\u2014" }), jsx(AttachmentsList, { items: items, isEdit: editable, onPreview: onPreview, onDelete: onDelete }), editable && (jsx(IconButton, { kind: "link", tabIndex: 0, title: t("attachments.fromLink", { ns: "common", defaultValue: "По ссылке" }), onClick: onOpenLinkDialog })), jsx(AttachmentLinkDialog, { isOpen: isLinkDialogOpen, onClose: onCloseLinkDialog, onSubmit: onAddByLink }), previewIndex !== null && (jsx(Preview, { images: previewImages, initialIndex: previewIndex, isOpen: true, onClose: onClosePreview, onDownload: onDownload, errorTitleText: t("attachments.resourceUnavailable", {
|
|
15769
|
+
ns: "common",
|
|
15770
|
+
defaultValue: "Ресурс недоступен",
|
|
15771
|
+
}) }, previewIndex))] }));
|
|
15772
|
+
});
|
|
15773
|
+
|
|
15654
15774
|
/**
|
|
15655
15775
|
* Значение ячейки не изменилось.
|
|
15656
15776
|
*
|
|
@@ -15749,7 +15869,7 @@ const useCellEditing = () => {
|
|
|
15749
15869
|
const TableCell = memo(({ attribute, row, canEdit, onChange }) => {
|
|
15750
15870
|
const { t, language } = useGlobalContext();
|
|
15751
15871
|
const { editing, buttonProps, editorProps } = useCellEditing();
|
|
15752
|
-
const { attributeName, type, isEditable, stringFormat } = attribute;
|
|
15872
|
+
const { attributeName, type, subType, isEditable, stringFormat, multiline } = attribute;
|
|
15753
15873
|
const value = row.properties[attributeName];
|
|
15754
15874
|
const handleChange = useCallback((next) => {
|
|
15755
15875
|
// Пустые правки отбрасываем: иначе строка становилась бы «изменённой» от простого
|
|
@@ -15768,16 +15888,21 @@ const TableCell = memo(({ attribute, row, canEdit, onChange }) => {
|
|
|
15768
15888
|
const withTime = useMemo(() => TIME_FORMAT.test(stringFormat?.format ?? ""), [stringFormat?.format]);
|
|
15769
15889
|
// `canEdit` — режим таблицы, `isEditable` — разрешение конкретного атрибута: правка требует обоих.
|
|
15770
15890
|
const editable = canEdit && isEditable && EDITABLE_ATTRIBUTE_TYPES.includes(type);
|
|
15891
|
+
// Вложения — это строка с JSON, а не текст: разбирать и рисовать её нужно раньше любых
|
|
15892
|
+
// редакторов по типу, иначе в ячейке оказался бы сырой список файлов.
|
|
15893
|
+
if (subType === StringSubType.Attachments) {
|
|
15894
|
+
return jsx(AttachmentsCell, { attribute: attribute, row: row, canEdit: canEdit, onChange: onChange });
|
|
15895
|
+
}
|
|
15771
15896
|
// Логическое значение и на чтение показываем галкой, а не словами «true»/«false» —
|
|
15772
15897
|
// так же, как остальной дашборд рисует булевы атрибуты.
|
|
15773
15898
|
if (type === AttributeType.Boolean) {
|
|
15774
15899
|
return jsx(Checkbox, { checked: !!value, disabled: !editable, onChange: () => handleChange(!value) });
|
|
15775
15900
|
}
|
|
15776
15901
|
if (!editable) {
|
|
15777
|
-
return jsx(CellText, { title: formatted, children: formatted || jsx(CellPlaceholder, { children: "\u2014" }) });
|
|
15902
|
+
return (jsx(CellText, { "$multiline": multiline, title: formatted, children: formatted || jsx(CellPlaceholder, { children: "\u2014" }) }));
|
|
15778
15903
|
}
|
|
15779
15904
|
if (!editing) {
|
|
15780
|
-
return (jsx(CellButton, { type: "button", ...buttonProps, children: formatted || jsx(CellPlaceholder, { children: "\u2014" }) }));
|
|
15905
|
+
return (jsx(CellButton, { type: "button", "$multiline": multiline, ...buttonProps, children: formatted || jsx(CellPlaceholder, { children: "\u2014" }) }));
|
|
15781
15906
|
}
|
|
15782
15907
|
const renderEditor = () => {
|
|
15783
15908
|
if (type === AttributeType.DateTime) {
|
|
@@ -15788,9 +15913,31 @@ const TableCell = memo(({ attribute, row, canEdit, onChange }) => {
|
|
|
15788
15913
|
}
|
|
15789
15914
|
return (jsx(Input, { autoFocus: true, value: value === null || value === undefined ? "" : String(value), width: "100%", onChange: ({ target }) => handleChange(target.value) }));
|
|
15790
15915
|
};
|
|
15791
|
-
return (jsxs(CellEditor, { ...editorProps, children: [jsx(CellGhost, { children: formatted || "—" }), jsx(CellField, { children: renderEditor() })] }));
|
|
15916
|
+
return (jsxs(CellEditor, { ...editorProps, children: [jsx(CellGhost, { "$multiline": multiline, children: formatted || "—" }), jsx(CellField, { children: renderEditor() })] }));
|
|
15792
15917
|
});
|
|
15793
15918
|
|
|
15919
|
+
/**
|
|
15920
|
+
* Инлайн-размер ячейки колонки с заданной шириной.
|
|
15921
|
+
*
|
|
15922
|
+
* Раскладка таблицы автоматическая (ширину колонок выбирает содержимое), и одной `width` ей мало —
|
|
15923
|
+
* она для неё лишь пожелание. Жёстко колонку держат все три предела сразу, поэтому и ставим их
|
|
15924
|
+
* втроём, на каждую ячейку колонки.
|
|
15925
|
+
*
|
|
15926
|
+
* Ширины нет — стиля нет: колонка остаётся на попечении раскладки, как и была.
|
|
15927
|
+
*/
|
|
15928
|
+
const getColumnStyle = (width) => width == null ? undefined : { width, minWidth: width, maxWidth: width };
|
|
15929
|
+
|
|
15930
|
+
/**
|
|
15931
|
+
* Шапка таблицы: заголовки колонок, сортировка кликом и захват границы для ручной ширины.
|
|
15932
|
+
*
|
|
15933
|
+
* Захват рисуется по разрешению атрибута (`resizable`) и не смотрит на режим правки контейнера:
|
|
15934
|
+
* ширина колонки — это вид, а не данные.
|
|
15935
|
+
*/
|
|
15936
|
+
const TableHeadRow = ({ columns, sort, sortEnabled, columnWidths, withActionsColumn, onSortToggle, onResizeStart, onResizeMove, onResizeEnd, }) => (jsxs("tr", { children: [columns.map(({ attributeName, alias, description, resizable }) => {
|
|
15937
|
+
const sorted = sort?.attributeName === attributeName;
|
|
15938
|
+
return (jsxs(TableHeadCell, { title: description || alias, style: getColumnStyle(columnWidths[attributeName]), "$sortable": sortEnabled, onClick: sortEnabled ? () => onSortToggle(attributeName) : undefined, children: [jsxs(TableHeadContent, { children: [alias, sortEnabled && (jsx(TableSortSlot, { "$active": sorted, children: jsx(Icon, { kind: sorted && sort?.direction === "desc" ? "sorting_des" : "sorting_asc" }) }))] }), resizable && (jsx(TableResizeHandle, { onPointerDown: event => onResizeStart(attributeName, event), onPointerMove: onResizeMove, onPointerUp: onResizeEnd, onPointerCancel: onResizeEnd }))] }, attributeName));
|
|
15939
|
+
}), withActionsColumn && jsx(TableHeadCell, {})] }));
|
|
15940
|
+
|
|
15794
15941
|
const ContainerLoading = () => (jsx(Flex, { alignContent: "center", justifyContent: "center", width: "100%", children: jsx(CircularProgress, { diameter: 1.5, mono: true }) }));
|
|
15795
15942
|
|
|
15796
15943
|
/**
|
|
@@ -15894,6 +16041,53 @@ const getTableBoxStyle = (width, height) => {
|
|
|
15894
16041
|
};
|
|
15895
16042
|
};
|
|
15896
16043
|
|
|
16044
|
+
/**
|
|
16045
|
+
* Ручное изменение ширины колонок.
|
|
16046
|
+
*
|
|
16047
|
+
* Растянутые ширины живут только здесь: тянут их в рантайме, а `width` атрибута в конфиге задаёт
|
|
16048
|
+
* лишь стартовое значение — записывать растянутое обратно в конфиг незачем.
|
|
16049
|
+
*
|
|
16050
|
+
* Разрешение (`resizable`) от `options.editMode` не зависит: ширина колонки — это вид таблицы,
|
|
16051
|
+
* а не правка данных, и тянуть её должно быть можно и на таблице только для чтения.
|
|
16052
|
+
*
|
|
16053
|
+
* Стартовую ширину берём с самой ячейки, а не из конфига: у колонки без `width` её выбрала
|
|
16054
|
+
* раскладка таблицы, и тянуть надо ровно от того, что видно.
|
|
16055
|
+
*/
|
|
16056
|
+
const useColumnResize = (columns) => {
|
|
16057
|
+
const [widths, setWidths] = useState({});
|
|
16058
|
+
const dragRef = useRef(null);
|
|
16059
|
+
const onResizeStart = useCallback((attributeName, event) => {
|
|
16060
|
+
const handle = event.currentTarget;
|
|
16061
|
+
// Захват границы — не клик по заголовку: иначе каждое перетаскивание переключало бы сортировку.
|
|
16062
|
+
event.preventDefault();
|
|
16063
|
+
event.stopPropagation();
|
|
16064
|
+
dragRef.current = {
|
|
16065
|
+
attributeName,
|
|
16066
|
+
startX: event.clientX,
|
|
16067
|
+
startWidth: handle.closest("th")?.getBoundingClientRect().width ?? MIN_COLUMN_WIDTH,
|
|
16068
|
+
};
|
|
16069
|
+
handle.setPointerCapture(event.pointerId);
|
|
16070
|
+
}, []);
|
|
16071
|
+
const onResizeMove = useCallback((event) => {
|
|
16072
|
+
const drag = dragRef.current;
|
|
16073
|
+
if (!drag) {
|
|
16074
|
+
return;
|
|
16075
|
+
}
|
|
16076
|
+
const next = Math.max(MIN_COLUMN_WIDTH, Math.round(drag.startWidth + event.clientX - drag.startX));
|
|
16077
|
+
setWidths(current => current[drag.attributeName] === next ? current : { ...current, [drag.attributeName]: next });
|
|
16078
|
+
}, []);
|
|
16079
|
+
const onResizeEnd = useCallback((event) => {
|
|
16080
|
+
const handle = event.currentTarget;
|
|
16081
|
+
dragRef.current = null;
|
|
16082
|
+
if (handle.hasPointerCapture(event.pointerId)) {
|
|
16083
|
+
handle.releasePointerCapture(event.pointerId);
|
|
16084
|
+
}
|
|
16085
|
+
}, []);
|
|
16086
|
+
// Растянутая ширина перебивает конфигурационную: последнее слово за тем, кто тянул.
|
|
16087
|
+
const columnWidths = useMemo(() => columns.reduce((acc, { attributeName, width }) => ({ ...acc, [attributeName]: widths[attributeName] ?? width }), {}), [columns, widths]);
|
|
16088
|
+
return { columnWidths, onResizeStart, onResizeMove, onResizeEnd };
|
|
16089
|
+
};
|
|
16090
|
+
|
|
15897
16091
|
/**
|
|
15898
16092
|
* Вид таблицы: колонки плюс локальная сортировка.
|
|
15899
16093
|
*
|
|
@@ -15906,6 +16100,9 @@ const getTableBoxStyle = (width, height) => {
|
|
|
15906
16100
|
*
|
|
15907
16101
|
* `width`/`height` — размер собственного бокса таблицы: он ограничивает её саму и прокручивает
|
|
15908
16102
|
* то, что не влезло. Место, отведённое контейнеру, задаётся его же опциями и живёт отдельно.
|
|
16103
|
+
*
|
|
16104
|
+
* Ширины колонок — тоже вид: конфиг задаёт стартовую, растянутая мышью живёт в
|
|
16105
|
+
* {@link useColumnResize} и в конфиг не возвращается.
|
|
15909
16106
|
*/
|
|
15910
16107
|
const useTableView = (elementConfig) => {
|
|
15911
16108
|
const context = useContext(StructuredDataContext);
|
|
@@ -15916,6 +16113,7 @@ const useTableView = (elementConfig) => {
|
|
|
15916
16113
|
const columns = useMemo(() => schema ?? [], [schema]);
|
|
15917
16114
|
const sortedRows = useMemo(() => sortRows(rows ?? [], sort, schema ?? []), [rows, schema, sort]);
|
|
15918
16115
|
const sizeCss = useMemo(() => getTableBoxStyle(width, height), [height, width]);
|
|
16116
|
+
const { columnWidths, onResizeStart, onResizeMove, onResizeEnd } = useColumnResize(columns);
|
|
15919
16117
|
const onSortToggle = useCallback((attributeName) => setSort(current => getNextSort(current, attributeName)), []);
|
|
15920
16118
|
return {
|
|
15921
16119
|
context,
|
|
@@ -15926,7 +16124,11 @@ const useTableView = (elementConfig) => {
|
|
|
15926
16124
|
sizeCss,
|
|
15927
16125
|
// Ширина задана — колонки по содержимому: лишнее уходит в прокрутку бокса, а не в многоточие.
|
|
15928
16126
|
contentWidth: width != null,
|
|
16127
|
+
columnWidths,
|
|
15929
16128
|
onSortToggle,
|
|
16129
|
+
onResizeStart,
|
|
16130
|
+
onResizeMove,
|
|
16131
|
+
onResizeEnd,
|
|
15930
16132
|
};
|
|
15931
16133
|
};
|
|
15932
16134
|
|
|
@@ -15938,7 +16140,7 @@ const useTableView = (elementConfig) => {
|
|
|
15938
16140
|
*/
|
|
15939
16141
|
const ElementTable = memo(({ elementConfig }) => {
|
|
15940
16142
|
const { t } = useGlobalContext();
|
|
15941
|
-
const { context, columns, rows, sort, sortEnabled, sizeCss, contentWidth, onSortToggle } = useTableView(elementConfig);
|
|
16143
|
+
const { context, columns, rows, sort, sortEnabled, sizeCss, contentWidth, columnWidths, onSortToggle, onResizeStart, onResizeMove, onResizeEnd, } = useTableView(elementConfig);
|
|
15942
16144
|
// Узел таблицы держим состоянием, а не ref: отсечка должна встать сразу, как он появится.
|
|
15943
16145
|
const [table, setTable] = useState(null);
|
|
15944
16146
|
useHeadOverlap(table);
|
|
@@ -15954,10 +16156,10 @@ const ElementTable = memo(({ elementConfig }) => {
|
|
|
15954
16156
|
defaultValue: "Схема данных не задана",
|
|
15955
16157
|
}) }));
|
|
15956
16158
|
}
|
|
15957
|
-
return (jsx(TableBox, { "$sizeCss": sizeCss, children: jsxs(TableWrapper, { ref: setTable, "$contentWidth": contentWidth, children: [jsx(TableHead, { children:
|
|
15958
|
-
|
|
15959
|
-
|
|
15960
|
-
|
|
16159
|
+
return (jsx(TableBox, { "$sizeCss": sizeCss, children: jsxs(TableWrapper, { ref: setTable, "$contentWidth": contentWidth, children: [jsx(TableHead, { children: jsx(TableHeadRow, { columns: columns, sort: sort, sortEnabled: sortEnabled, columnWidths: columnWidths, withActionsColumn: context.canDelete, onSortToggle: onSortToggle, onResizeStart: onResizeStart, onResizeMove: onResizeMove, onResizeEnd: onResizeEnd }) }), jsx(TableBody, { children: rows.map(row => (jsxs(TableRow, { children: [columns.map(attribute => (jsx(TableCellWrapper, {
|
|
16160
|
+
// Стиль колонки из конфига идёт последним: шрифт задан для значений, и ширину
|
|
16161
|
+
// он не трогает — своих размеров у него нет.
|
|
16162
|
+
style: { ...getColumnStyle(columnWidths[attribute.attributeName]), ...attribute.style }, children: jsx(TableCell, { attribute: attribute, row: row, canEdit: context.canEdit, onChange: context.onCellChange }) }, attribute.attributeName))), context.canDelete && (jsx(TableActionsCell, { children: jsx(IconButton, { kind: "delete", title: t("actions.delete", { ns: "common", defaultValue: "Удалить" }), onClick: () => context.onRowDelete(row.key) }) }))] }, row.key))) })] }) }));
|
|
15961
16163
|
});
|
|
15962
16164
|
|
|
15963
16165
|
const TooltipIcon = styled(Icon).withConfig({ displayName: "TooltipIcon", componentId: "sc-1lkxudm" }) `
|