|
103 | 103 | }; |
104 | 104 |
|
105 | 105 | $: table = data.table; |
| 106 | + /** Keep sort in sync after client navigations (e.g. filters); onMount alone misses SPA loads. */ |
| 107 | + $: sortState.set(data.currentSort as SortState); |
106 | 108 | $: rows = writable(data.rows); |
107 | 109 | $: if ($rows) { |
108 | 110 | paginatedRows.clear(); |
|
113 | 115 | } |
114 | 116 |
|
115 | 117 | // create index map for O(1) row lookups, reactive! |
116 | | - $: rowIndexMap = new Map($paginatedRows.items.map((row, index) => [row.$id, index])); |
| 118 | + $: rowIndexMap = new Map( |
| 119 | + $paginatedRows.items.flatMap((r, index) => (r ? [[r.$id, index]] : [])) |
| 120 | + ); |
117 | 121 |
|
118 | 122 | const tableId = page.params.table; |
119 | 123 | const databaseId = page.params.database; |
|
912 | 916 |
|
913 | 917 | <svelte:fragment slot="rows" let:root let:item let:index> |
914 | 918 | {@const row = $paginatedRows.getItemAtVirtualIndex(index)} |
915 | | - {#if row === null} |
| 919 | + <!-- !row: virtualizer can briefly yield null/undefined while row data reloads (e.g. after filters). --> |
| 920 | + {#if !row} |
916 | 921 | <Spreadsheet.Row.Base |
917 | 922 | {root} |
918 | 923 | {index} |
|
936 | 941 | select={rowSelection} |
937 | 942 | hoverEffect |
938 | 943 | showSelectOnHover |
939 | | - valueWithoutHover={row.$sequence}> |
| 944 | + valueWithoutHover={row?.$sequence}> |
940 | 945 | {#each $tableColumns as { id: columnId, isEditable, hide } (columnId)} |
941 | 946 | {@const rowColumn = $columns.find((col) => col.key === columnId)} |
942 | 947 | {#if columnId === '$id' && !hide} |
|
948 | 953 | alignItems="center" |
949 | 954 | alignContent="center" |
950 | 955 | justifyContent="space-between"> |
951 | | - <Id value={row.$id} tooltipPortal tooltipDelay={200}> |
952 | | - {row.$id} |
| 956 | + <Id value={row?.$id} tooltipPortal tooltipDelay={200}> |
| 957 | + {row?.$id} |
953 | 958 | </Id> |
954 | 959 |
|
955 | 960 | <Popover let:show let:hide portal padding="none"> |
|
1009 | 1014 | {#if columnId === '$createdAt' || columnId === '$updatedAt'} |
1010 | 1015 | <DualTimeView |
1011 | 1016 | showDatetime |
1012 | | - time={row[columnId]} |
| 1017 | + time={row?.[columnId]} |
1013 | 1018 | canShowPopover={canShowDatetimePopover} /> |
1014 | 1019 | {:else if columnId === 'actions'} |
1015 | 1020 | <SheetOptions |
|
1033 | 1038 | </Button.Button> |
1034 | 1039 | {/snippet} |
1035 | 1040 | </SheetOptions> |
1036 | | - {:else if isRelationship(rowColumn)} |
1037 | | - {@const args = getDisplayNamesForTable(row[columnId])} |
| 1041 | + {:else if rowColumn && isRelationship(rowColumn)} |
| 1042 | + {@const args = getDisplayNamesForTable(row?.[columnId])} |
1038 | 1043 | {#if !isRelationshipToMany(rowColumn)} |
1039 | | - {#if row[columnId]} |
| 1044 | + {#if row?.[columnId]} |
1040 | 1045 | {@const displayValue = args |
1041 | | - .map((arg) => row[columnId]?.[arg]) |
| 1046 | + .map((arg) => row?.[columnId]?.[arg]) |
1042 | 1047 | .filter(Boolean) |
1043 | 1048 | .join(' | ')} |
1044 | 1049 |
|
|
1047 | 1052 | variant="muted" |
1048 | 1053 | on:click={() => { |
1049 | 1054 | $databaseRelatedRowSheetOptions.tableId = |
1050 | | - row[columnId]?.['$tableId']; |
| 1055 | + row?.[columnId]?.['$tableId']; |
1051 | 1056 | $databaseRelatedRowSheetOptions.rows = |
1052 | | - row[columnId]?.['$id']; |
| 1057 | + row?.[columnId]?.['$id']; |
1053 | 1058 | $databaseRelatedRowSheetOptions.show = true; |
1054 | 1059 | }}> |
1055 | 1060 | {displayValue} |
|
1067 | 1072 | size="xs" /> |
1068 | 1073 | {/if} |
1069 | 1074 | {:else} |
1070 | | - {@const itemsNum = row[columnId]?.length} |
| 1075 | + {@const itemsNum = row?.[columnId]?.length} |
1071 | 1076 | Items <Badge |
1072 | 1077 | content={itemsNum} |
1073 | 1078 | variant="secondary" |
1074 | 1079 | size="s" /> |
1075 | 1080 | {/if} |
1076 | | - {:else if isSpatialType(rowColumn) && row[columnId] !== null} |
| 1081 | + {:else if rowColumn && isSpatialType(rowColumn) && row?.[columnId] !== null} |
| 1082 | + <Typography.Text truncate> |
| 1083 | + {JSON.stringify(row?.[columnId])} |
| 1084 | + </Typography.Text> |
| 1085 | + {:else if !rowColumn} |
1077 | 1086 | <Typography.Text truncate> |
1078 | | - {JSON.stringify(row[columnId])} |
| 1087 | + {formatColumn(row?.[columnId])} |
1079 | 1088 | </Typography.Text> |
1080 | 1089 | {:else} |
1081 | | - {@const value = row[columnId]} |
1082 | | - {@const formatted = formatColumn(row[columnId])} |
| 1090 | + {@const value = row?.[columnId]} |
| 1091 | + {@const formatted = formatColumn(row?.[columnId])} |
1083 | 1092 | {@const isEmptyArray = formatted === 'Empty'} |
1084 | 1093 | {@const isDatetimeAttribute = rowColumn.type === 'datetime'} |
1085 | 1094 | {@const isEncryptedAttribute = |
|
1126 | 1135 | {/if} |
1127 | 1136 |
|
1128 | 1137 | <svelte:fragment slot="cell-editor" let:close> |
1129 | | - {@const isRelatedToMany = isRelationshipToMany(rowColumn)} |
1130 | | - {@const hasItems = isRelatedToMany |
1131 | | - ? row[columnId]?.length |
1132 | | - : false} |
1133 | | - |
1134 | | - <EditRowCell |
1135 | | - {row} |
1136 | | - column={rowColumn} |
1137 | | - onRowStructureUpdate={async (row) => { |
1138 | | - const success = await updateRowContents(row); |
1139 | | - if (success) { |
1140 | | - // database update succeeded! |
1141 | | - paginatedRows.update(index, row); |
1142 | | - } |
1143 | | - return success; |
1144 | | - }} |
1145 | | - noInlineEdit={isRelatedToMany && hasItems} |
1146 | | - onChange={(row) => paginatedRows.update(index, row)} |
1147 | | - onRevert={(row) => paginatedRows.update(index, row)} |
1148 | | - openSideSheet={() => { |
1149 | | - close(); /* closes the editor */ |
1150 | | - |
1151 | | - if (isRelationshipToMany(rowColumn)) { |
1152 | | - openSideSheetForRelationsToMany( |
1153 | | - row[columnId], |
1154 | | - rowColumn |
1155 | | - ); |
1156 | | - } else { |
1157 | | - $databaseRowSheetOptions.autoFocus = true; |
1158 | | - onSelectSheetOption('update', null, 'row', row); |
1159 | | - } |
1160 | | - }} /> |
| 1138 | + {#if rowColumn} |
| 1139 | + {@const isRelatedToMany = |
| 1140 | + isRelationshipToMany(rowColumn)} |
| 1141 | + {@const hasItems = isRelatedToMany |
| 1142 | + ? row?.[columnId]?.length |
| 1143 | + : false} |
| 1144 | + |
| 1145 | + <EditRowCell |
| 1146 | + {row} |
| 1147 | + column={rowColumn} |
| 1148 | + onRowStructureUpdate={async (row) => { |
| 1149 | + const success = await updateRowContents(row); |
| 1150 | + if (success) { |
| 1151 | + // database update succeeded! |
| 1152 | + paginatedRows.update(index, row); |
| 1153 | + } |
| 1154 | + return success; |
| 1155 | + }} |
| 1156 | + noInlineEdit={isRelatedToMany && hasItems} |
| 1157 | + onChange={(row) => paginatedRows.update(index, row)} |
| 1158 | + onRevert={(row) => paginatedRows.update(index, row)} |
| 1159 | + openSideSheet={() => { |
| 1160 | + close(); /* closes the editor */ |
| 1161 | + |
| 1162 | + if (isRelationshipToMany(rowColumn)) { |
| 1163 | + openSideSheetForRelationsToMany( |
| 1164 | + row?.[columnId], |
| 1165 | + rowColumn |
| 1166 | + ); |
| 1167 | + } else { |
| 1168 | + $databaseRowSheetOptions.autoFocus = true; |
| 1169 | + onSelectSheetOption( |
| 1170 | + 'update', |
| 1171 | + null, |
| 1172 | + 'row', |
| 1173 | + row |
| 1174 | + ); |
| 1175 | + } |
| 1176 | + }} /> |
| 1177 | + {/if} |
1161 | 1178 | </svelte:fragment> |
1162 | 1179 | </Spreadsheet.Cell> |
1163 | 1180 | {/if} |
|
0 commit comments