|
1 | 1 | <template>
|
2 |
| - <tbody v-if="nodes.length" :class="classes"> |
3 |
| - <template v-for="(node, nodeIndex) in nodes" :key="nodeIndex"> |
| 2 | + <tbody v-if="mappedNodes.length" :class="classes"> |
| 3 | + <template |
| 4 | + v-for="( |
| 5 | + { node, index, visibility, hydrateNode, createNodeChildrenAndRefresh }, mappedIndex |
| 6 | + ) in mappedNodes.nodes" |
| 7 | + :key="index" |
| 8 | + > |
4 | 9 | <!-- Row -->
|
5 | 10 | <tr
|
6 | 11 | class="--txtAlign"
|
7 |
| - :class="[`--txtSize-${size}`, { ['is--selected']: selectedNodes[nodeIndex][0] }]" |
| 12 | + :class="[`--txtSize-${size}`, { ['is--selected']: selectedNodes[index][0] }]" |
8 | 13 | >
|
9 | 14 | <th
|
10 |
| - v-if="nodes.length > 1 || $slots.default" |
| 15 | + v-if="mappedNodes.length > 1 || $slots.default" |
11 | 16 | class="--sticky"
|
12 | 17 | :class="{ ['is--selected']: !!ordering['id'] }"
|
13 | 18 | data-column-name="id"
|
|
16 | 21 | <component
|
17 | 22 | :is="preferId"
|
18 | 23 | v-if="preferId && preferId !== true"
|
19 |
| - :index="nodeIndex" |
20 |
| - :node="node" |
| 24 | + v-bind="{ index, node: nodes[index] }" |
21 | 25 | />
|
22 | 26 | <div v-else class="flx --flxRow --flx-start-center --gap-10">
|
23 | 27 | <InputToggle
|
24 | 28 | v-if="!isReadOnly"
|
25 |
| - :id="tableId + String(node.id ?? nodeIndex)" |
26 |
| - v-model="selectedNodes[nodeIndex][0]" |
| 29 | + :id="tableId + String(node.id ?? index)" |
| 30 | + v-model="selectedNodes[mappedIndex][0]" |
27 | 31 | :theme="theme || themeValues"
|
28 | 32 | :title="t('table_select')"
|
29 | 33 | :size="size"
|
30 | 34 | />
|
31 |
| - <span :title="String(node.id ?? nodeIndex)"> |
32 |
| - {{ node.id && preferId ? node.id : nodeIndex + 1 }} |
| 35 | + <span :title="String(node.id ?? index)"> |
| 36 | + {{ node.id && preferId ? node.id : index + 1 }} |
33 | 37 | </span>
|
34 | 38 | </div>
|
35 | 39 | </th>
|
|
51 | 55 | meta: {
|
52 | 56 | ...meta,
|
53 | 57 | ...(meta.updateNode && {
|
54 |
| - updateNode: (n: any) => meta.updateNode?.(n, node), |
| 58 | + updateNode: (n: any) => meta.updateNode?.(n, nodes[index]), |
55 | 59 | }),
|
56 | 60 | },
|
57 | 61 | node,
|
|
89 | 93 | cloneNodeAndRefresh,
|
90 | 94 | deleteNodeAndRefresh,
|
91 | 95 | deleteNodesAndRefresh,
|
92 |
| - show: visibility[nodeIndex].show, |
| 96 | + show: canShowChildren(visibility, mappedIndex), |
93 | 97 | }"
|
94 | 98 | ></slot>
|
95 | 99 | <ActionButton
|
|
101 | 105 | :size="size"
|
102 | 106 | round
|
103 | 107 | :disabled="selectedNodes.some(([n]) => n)"
|
104 |
| - @click="() => updateNodeAndRefresh(node)" |
| 108 | + @click="() => updateNodeAndRefresh(nodes[index])" |
105 | 109 | >
|
106 | 110 | <IconFa name="pencil" />
|
107 | 111 | </ActionButton>
|
|
133 | 137 | :theme="invertedTheme"
|
134 | 138 | :size="size"
|
135 | 139 | :aria-label="t('table_duplicate')"
|
136 |
| - @click="() => cloneNodeAndRefresh(node, setModel)" |
| 140 | + @click=" |
| 141 | + () => cloneNodeAndRefresh(nodes[index], setModel) |
| 142 | + " |
137 | 143 | >
|
138 | 144 | <IconFa name="clone" />
|
139 | 145 | <span>
|
|
149 | 155 | @click="
|
150 | 156 | () =>
|
151 | 157 | deleteNodeAndRefresh(
|
152 |
| - node, |
| 158 | + nodes[index], |
153 | 159 | setModel,
|
154 | 160 | dropdownRef
|
155 | 161 | )
|
|
167 | 173 | cloneNodeAndRefresh,
|
168 | 174 | deleteNodeAndRefresh,
|
169 | 175 | deleteNodesAndRefresh,
|
170 |
| - show: visibility[nodeIndex].show, |
| 176 | + show: canShowChildren(visibility, mappedIndex), |
171 | 177 | }"
|
172 | 178 | ></slot>
|
173 | 179 | </ul>
|
|
182 | 188 | <tr class="no--hover --width-100">
|
183 | 189 | <td :colspan="propertiesMeta.length + 2">
|
184 | 190 | <BaseBox
|
185 |
| - v-show="visibility[nodeIndex].show" |
| 191 | + v-show="canShowChildren(visibility, mappedIndex)" |
186 | 192 | :theme="theme || themeValues"
|
187 | 193 | transparent
|
188 | 194 | button
|
|
196 | 202 | deleteNodeAndRefresh,
|
197 | 203 | deleteNodesAndRefresh,
|
198 | 204 | createNodeChildrenAndRefresh,
|
199 |
| - show: visibility[nodeIndex].show, |
| 205 | + show: canShowChildren(visibility, mappedIndex), |
200 | 206 | hydrateParentNode: hydrateNode,
|
201 | 207 | }"
|
202 | 208 | ></slot>
|
|
210 | 216 | <ActionLink
|
211 | 217 | :theme="theme || themeValues"
|
212 | 218 | :size="size"
|
213 |
| - :active="visibility[nodeIndex].show" |
| 219 | + :active="canShowChildren(visibility, mappedIndex)" |
214 | 220 | :tooltip="
|
215 | 221 | t(
|
216 |
| - visibility[nodeIndex].show |
| 222 | + canShowChildren(visibility, mappedIndex) |
217 | 223 | ? 'table_hide_name'
|
218 | 224 | : 'table_see_name',
|
219 | 225 | {
|
220 | 226 | name:
|
221 | 227 | childrenName ||
|
222 | 228 | childrenCountKey ||
|
223 |
| - String(node.id ?? nodeIndex).split('/')[0], |
| 229 | + String(node.id ?? index).split('/')[0], |
224 | 230 | }
|
225 | 231 | )
|
226 | 232 | "
|
227 | 233 | tooltip-position="right"
|
228 |
| - :disabled=" |
229 |
| - !visibility[nodeIndex].childrenCount || |
230 |
| - visibility[nodeIndex].showNodeChildren |
231 |
| - " |
| 234 | + :disabled="!visibility.childrenCount || visibility.showNodeChildren" |
232 | 235 | class="--p-5"
|
233 |
| - @click="() => toggleChildren(nodeIndex)" |
| 236 | + @click="() => toggleChildren(mappedIndex)" |
234 | 237 | >
|
235 |
| - <span v-if="visibility[nodeIndex].childrenCount >= 1"> |
236 |
| - {{ visibility[nodeIndex].childrenCount }} |
| 238 | + <span v-if="visibility.childrenCount >= 1"> |
| 239 | + {{ visibility.childrenCount }} |
237 | 240 | </span>
|
238 | 241 | <IconFa name="chevron-down" indicator />
|
239 | 242 | </ActionLink>
|
240 | 243 | <ActionButtonLink
|
241 | 244 | v-if="createNodeChildren"
|
242 | 245 | :theme="theme || themeValues"
|
243 | 246 | :size="size"
|
244 |
| - :disabled="visibility[nodeIndex].disableCreateNodeChildren" |
| 247 | + :disabled="visibility.disableCreateNodeChildren" |
245 | 248 | :tooltip="
|
246 | 249 | t('table_create_new_name', {
|
247 | 250 | name:
|
248 | 251 | childrenName ||
|
249 | 252 | childrenCountKey ||
|
250 |
| - String(node.id ?? nodeIndex).split('/')[0], |
| 253 | + String(node.id ?? index).split('/')[0], |
251 | 254 | })
|
252 | 255 | "
|
253 | 256 | tooltip-position="right"
|
254 | 257 | class="--p-5:md-inv"
|
255 | 258 | link-button
|
256 | 259 | round
|
257 |
| - @click="() => createNodeChildrenAndRefresh(node)" |
| 260 | + @click="() => createNodeChildrenAndRefresh(nodes[index])" |
258 | 261 | >
|
259 | 262 | <IconFa name="plus" />
|
260 | 263 | </ActionButtonLink>
|
|
272 | 275 | </template>
|
273 | 276 |
|
274 | 277 | <script setup lang="ts" generic="T extends Record<string, any>, TM extends Record<string, any> = T">
|
275 |
| - import { computed } from "vue"; |
276 |
| -
|
277 |
| - import { useI18n, useSwal } from "@open-xamu-co/ui-common-helpers"; |
| 278 | + import { useI18n } from "@open-xamu-co/ui-common-helpers"; |
278 | 279 |
|
279 | 280 | import IconFa from "../icon/Fa.vue";
|
280 | 281 | import ActionLink from "../action/Link.vue";
|
|
287 | 288 |
|
288 | 289 | import type { iTableChildProps } from "../../types/props";
|
289 | 290 | import useTheme from "../../composables/theme";
|
290 |
| - import { useHelpers, useResolveNodeFn } from "../../composables/utils"; |
291 |
| - import type { iNodeFn } from "@open-xamu-co/ui-common-types"; |
| 291 | + import { useHelpers } from "../../composables/utils"; |
292 | 292 |
|
293 | 293 | export interface iTableBodyProps<
|
294 | 294 | Ti extends Record<string, any>,
|
295 | 295 | TMi extends Record<string, any> = Ti,
|
296 | 296 | > extends iTableChildProps<Ti, TMi> {}
|
297 | 297 |
|
298 |
| - interface INodeVisibility { |
299 |
| - disableCreateNodeChildren?: boolean; |
300 |
| - showNodeChildren?: boolean; |
301 |
| - childrenCount: number; |
302 |
| - show: boolean; |
303 |
| - } |
304 |
| -
|
305 | 298 | /**
|
306 | 299 | * Table body
|
307 | 300 | *
|
|
312 | 305 |
|
313 | 306 | const props = defineProps<iTableBodyProps<T, TM>>();
|
314 | 307 |
|
315 |
| - const Swal = useHelpers(useSwal); |
316 | 308 | const { t } = useHelpers(useI18n);
|
317 | 309 | const { themeValues, dangerThemeValues } = useTheme(props);
|
318 |
| -
|
319 |
| - /** |
320 |
| - * Cached node visibility |
321 |
| - */ |
322 |
| - const visibility = computed(() => { |
323 |
| - return props.nodes.reduce( |
324 |
| - (acc, node, nodeIndex) => { |
325 |
| - const disableCreateNodeChildren = props.disableCreateNodeChildren?.(node); |
326 |
| - const showNodeChildren = props.showNodeChildren?.(node); |
327 |
| - const childrenCount = props.childrenCount(node); |
328 |
| - const shouldShow = props.selectedNodes[nodeIndex][1] && !!childrenCount; |
329 |
| -
|
330 |
| - acc[nodeIndex] = { |
331 |
| - disableCreateNodeChildren, |
332 |
| - showNodeChildren, |
333 |
| - childrenCount, |
334 |
| - show: showNodeChildren ?? shouldShow, |
335 |
| - }; |
336 |
| -
|
337 |
| - return acc; |
338 |
| - }, |
339 |
| - {} as Record<number, INodeVisibility> |
340 |
| - ); |
341 |
| - }); |
342 |
| -
|
343 |
| - function hydrateNode(newNode: T | null, _newErrors?: unknown) { |
344 |
| - if (!props.hydrateNodes || !newNode) return; |
345 |
| -
|
346 |
| - // Replace the node with the updated one |
347 |
| - const nodeIndex = props.nodes.findIndex(({ id }) => id === newNode.id); |
348 |
| - const existingNode = props.nodes[nodeIndex]; |
349 |
| - const updatedNodes = props.nodes.toSpliced(nodeIndex, 1, { ...existingNode, ...newNode }); |
350 |
| -
|
351 |
| - props.hydrateNodes(updatedNodes); |
352 |
| - } |
353 |
| -
|
354 |
| - /** |
355 |
| - * Creates children for given node |
356 |
| - * sometimes it could fail but still update (api issue) |
357 |
| - * |
358 |
| - * @single |
359 |
| - */ |
360 |
| - const createNodeChildrenAndRefresh: iNodeFn<T> = async (node: T) => { |
361 |
| - // display loader |
362 |
| - Swal.fireLoader(); |
363 |
| -
|
364 |
| - // run process |
365 |
| - const response = await useResolveNodeFn(props.createNodeChildren?.(node)); |
366 |
| - const [updatedParent, event, closeModal] = response; |
367 |
| -
|
368 |
| - // unfinished task |
369 |
| - if (typeof updatedParent === "undefined" || updatedParent === null) { |
370 |
| - if (Swal.isLoading()) Swal.close(); |
371 |
| - } else if (updatedParent) { |
372 |
| - Swal.fire({ |
373 |
| - icon: "success", |
374 |
| - title: t("swal.table_created"), |
375 |
| - text: t("swal.table_created_text"), |
376 |
| - willOpen() { |
377 |
| - // If has children, prefer hydration over refreshing |
378 |
| - if ( |
379 |
| - props.childrenCount(node) && |
380 |
| - props.hydrateNodes && |
381 |
| - typeof updatedParent === "object" |
382 |
| - ) { |
383 |
| - hydrateNode({ ...node, ...updatedParent }); |
384 |
| - } else if (!props.omitRefresh) props.refresh?.(); |
385 |
| -
|
386 |
| - closeModal?.(); |
387 |
| - }, |
388 |
| - }); |
389 |
| - } else { |
390 |
| - // Error, children possibly not created |
391 |
| - Swal.fire({ |
392 |
| - icon: "warning", |
393 |
| - title: t("swal.table_possibly_not_created"), |
394 |
| - text: t("swal.table_possibly_not_created_text"), |
395 |
| - target: event, |
396 |
| - }); |
397 |
| - } |
398 |
| -
|
399 |
| - return response; |
400 |
| - }; |
401 | 310 | </script>
|
0 commit comments