|
10 | 10 | import Label from '../../components/Label.svelte'; |
11 | 11 | import Textarea from '../../components/Textarea.svelte'; |
12 | 12 | import PageHeader from '../../layout/PageHeader.svelte'; |
| 13 | + import SidebarResizeHandle from '../../layout/SidebarResizeHandle.svelte'; |
13 | 14 | import Modal from '../../dialogs/Modal.svelte'; |
14 | 15 | import ModalHeader from '../../dialogs/ModalHeader.svelte'; |
15 | 16 | import EmptyState from '../../components/EmptyState.svelte'; |
|
22 | 23 | import AssetImportWizard from './import/AssetImportWizard.svelte'; |
23 | 24 | import AssetSubFilterBar from './AssetSubFilterBar.svelte'; |
24 | 25 | import CustomFieldRenderer from '../items/CustomFieldRenderer.svelte'; |
| 26 | + import AssetDetailContent from './AssetDetailContent.svelte'; |
25 | 27 | import { retainValuesForType } from './assetFormValues.js'; |
26 | 28 | import { isBooleanCustomFieldType } from '../../utils/customFieldTypes.js'; |
27 | 29 | import { toHotkeyString } from '../../utils/keyboardShortcuts.js'; |
28 | 30 | import { formatDateSimple } from '../../utils/dateFormatter.js'; |
29 | 31 | import { fetchAssetCategories, fetchAssetStatuses, flattenCategories } from './shared/assetSetUtils.js'; |
30 | | - import { useEventListener } from 'runed'; |
31 | 32 |
|
32 | 33 | // Props for detail view |
33 | 34 | let { assetId = null } = $props(); |
|
71 | 72 |
|
72 | 73 | // Asset detail panel resize state |
73 | 74 | let assetPanelWidth = $state(320); |
74 | | - let isResizingAssetPanel = $state(false); |
75 | | - let assetResizeStartX = 0; |
76 | | - let assetResizeStartWidth = 0; |
77 | | -
|
78 | | - function startAssetPanelResize(event) { |
79 | | - event.preventDefault(); |
80 | | - assetResizeStartX = event.clientX; |
81 | | - assetResizeStartWidth = assetPanelWidth; |
82 | | - isResizingAssetPanel = true; |
83 | | - } |
84 | | -
|
85 | | - function onAssetResizeMove(e) { |
86 | | - const deltaX = assetResizeStartX - e.clientX; |
87 | | - assetPanelWidth = Math.max(280, Math.min(600, assetResizeStartWidth + deltaX)); |
88 | | - } |
89 | | -
|
90 | | - function onAssetResizeEnd() { |
91 | | - isResizingAssetPanel = false; |
92 | | - } |
93 | | -
|
94 | | - useEventListener(() => (isResizingAssetPanel ? document : null), 'mousemove', onAssetResizeMove); |
95 | | - useEventListener(() => (isResizingAssetPanel ? document : null), 'mouseup', onAssetResizeEnd); |
96 | | -
|
97 | 75 | // Filter state |
98 | 76 | let selectedCategoryId = $state(null); |
99 | 77 | let searchMode = $state('simple'); // 'simple' or 'ql' |
|
607 | 585 |
|
608 | 586 | <!-- Detail content --> |
609 | 587 | <div class="rounded-lg p-6" style="background: var(--ds-surface-raised); border: 1px solid var(--ds-border);"> |
610 | | - {#if directAsset.description} |
611 | | - <div class="mb-6"> |
612 | | - <h4 class="text-xs font-medium uppercase mb-1" style="color: var(--ds-text-subtlest);">{t('common.description')}</h4> |
613 | | - <p class="text-sm" style="color: var(--ds-text);">{directAsset.description}</p> |
614 | | - </div> |
615 | | - {/if} |
616 | | - <div class="grid grid-cols-2 gap-4"> |
617 | | - {#if directAsset.asset_type_name} |
618 | | - <div> |
619 | | - <h4 class="text-xs font-medium uppercase mb-1" style="color: var(--ds-text-subtlest);">{t('common.type')}</h4> |
620 | | - <span class="inline-flex items-center gap-1" style="color: var(--ds-text);"> |
621 | | - <ColorDot color={directAsset.asset_type_color || '#6b7280'} /> |
622 | | - {directAsset.asset_type_name} |
623 | | - </span> |
624 | | - </div> |
625 | | - {/if} |
626 | | - {#if directAsset.category_name} |
627 | | - <div> |
628 | | - <h4 class="text-xs font-medium uppercase mb-1" style="color: var(--ds-text-subtlest);">{t('common.category')}</h4> |
629 | | - <span class="inline-flex items-center gap-1" style="color: var(--ds-text);"> |
630 | | - <IconFolder class="w-4 h-4 text-yellow-500" /> |
631 | | - {directAsset.category_name} |
632 | | - </span> |
633 | | - </div> |
634 | | - {/if} |
635 | | - {#if directAsset.status_name} |
636 | | - <div> |
637 | | - <h4 class="text-xs font-medium uppercase mb-1" style="color: var(--ds-text-subtlest);">{t('common.status')}</h4> |
638 | | - <span class="inline-flex items-center gap-1.5" style="color: var(--ds-text);"> |
639 | | - <span class="w-2 h-2 rounded-full" style="background-color: {directAsset.status_color || '#6b7280'};"></span> |
640 | | - {directAsset.status_name} |
641 | | - </span> |
642 | | - </div> |
643 | | - {/if} |
644 | | - {#if directAsset.asset_tag} |
645 | | - <div> |
646 | | - <h4 class="text-xs font-medium uppercase mb-1" style="color: var(--ds-text-subtlest);">Asset Tag</h4> |
647 | | - <span class="text-sm font-mono" style="color: var(--ds-text);">{directAsset.asset_tag}</span> |
648 | | - </div> |
649 | | - {/if} |
650 | | - {#if directAsset.creator_name} |
651 | | - <div> |
652 | | - <h4 class="text-xs font-medium uppercase mb-1" style="color: var(--ds-text-subtlest);">{t('common.createdBy')}</h4> |
653 | | - <span class="text-sm" style="color: var(--ds-text);">{directAsset.creator_name}</span> |
654 | | - </div> |
655 | | - {/if} |
656 | | - <div> |
657 | | - <h4 class="text-xs font-medium uppercase mb-1" style="color: var(--ds-text-subtlest);">{t('common.created')}</h4> |
658 | | - <span class="text-sm" style="color: var(--ds-text);">{formatDateSimple(directAsset.created_at)}</span> |
659 | | - </div> |
660 | | - <div> |
661 | | - <h4 class="text-xs font-medium uppercase mb-1" style="color: var(--ds-text-subtlest);">{t('common.updated')}</h4> |
662 | | - <span class="text-sm" style="color: var(--ds-text);">{formatDateSimple(directAsset.updated_at)}</span> |
663 | | - </div> |
664 | | - {#if directAsset.linked_item_count > 0} |
665 | | - <div> |
666 | | - <h4 class="text-xs font-medium uppercase mb-1" style="color: var(--ds-text-subtlest);">Linked Items</h4> |
667 | | - <span class="text-sm" style="color: var(--ds-text);">{directAsset.linked_item_count}</span> |
668 | | - </div> |
669 | | - {/if} |
670 | | - </div> |
671 | | - {#if directAsset.custom_field_values && Object.keys(directAsset.custom_field_values).length > 0} |
672 | | - <div class="border-t pt-4 mt-4" style="border-color: var(--ds-border);"> |
673 | | - <h4 class="text-xs font-medium uppercase mb-3" style="color: var(--ds-text-subtlest);">Custom Fields</h4> |
674 | | - {#each Object.entries(directAsset.custom_field_values) as [fieldId, value]} |
675 | | - {@const fieldDef = displayTypeFields.find(f => String(f.custom_field_id) === String(fieldId))} |
676 | | - {#if fieldDef && value !== null && value !== ''} |
677 | | - <div class="mb-3"> |
678 | | - <h4 class="text-xs font-medium uppercase mb-1" style="color: var(--ds-text-subtlest);">{fieldDef.field_name}</h4> |
679 | | - <CustomFieldRenderer |
680 | | - field={{ |
681 | | - id: fieldDef.custom_field_id, |
682 | | - name: fieldDef.field_name, |
683 | | - field_type: fieldDef.field_type, |
684 | | - options: fieldDef.options |
685 | | - }} |
686 | | - value={value} |
687 | | - readonly={true} |
688 | | - noPadding={true} |
689 | | - /> |
690 | | - </div> |
691 | | - {/if} |
692 | | - {/each} |
693 | | - </div> |
694 | | - {/if} |
| 588 | + <AssetDetailContent asset={directAsset} fieldDefinitions={displayTypeFields} layout="grid" /> |
695 | 589 | </div> |
696 | 590 | {:else} |
697 | 591 | <EmptyState |
|
947 | 841 | <!-- Right sidebar: Asset detail (when selected) --> |
948 | 842 | {#if selectedAsset} |
949 | 843 | <div class="flex-shrink-0 flex flex-col relative" style="width: {assetPanelWidth}px; min-width: 280px; max-width: 600px; border-left: 1px solid var(--ds-border);"> |
950 | | - <!-- Resize handle --> |
951 | | - <!-- svelte-ignore a11y_no_static_element_interactions --> |
952 | | - <div |
953 | | - class="absolute left-0 top-0 bottom-0 w-1 cursor-ew-resize transition-colors z-10" |
954 | | - style="background-color: transparent;" |
955 | | - onmouseenter={(e) => e.currentTarget.style.backgroundColor = '#3b82f6'} |
956 | | - onmouseleave={(e) => e.currentTarget.style.backgroundColor = 'transparent'} |
957 | | - onmousedown={startAssetPanelResize} |
958 | | - ></div> |
| 844 | + <SidebarResizeHandle |
| 845 | + width={assetPanelWidth} |
| 846 | + minWidth={280} |
| 847 | + maxWidth={600} |
| 848 | + defaultWidth={320} |
| 849 | + edge="left" |
| 850 | + label="Resize asset details" |
| 851 | + title="Drag to resize, double-click to reset" |
| 852 | + onresize={(width) => assetPanelWidth = width} |
| 853 | + /> |
959 | 854 | <div class="p-4 flex items-center justify-between" style="border-bottom: 1px solid var(--ds-border);"> |
960 | 855 | <h2 class="font-semibold truncate" style="color: var(--ds-text);">{selectedAsset.title}</h2> |
961 | 856 | <div class="flex items-center gap-1"> |
|
994 | 889 | </div> |
995 | 890 | </div> |
996 | 891 | <div class="flex-1 overflow-auto p-4"> |
997 | | - {#if selectedAsset.description} |
998 | | - <div class="mb-4"> |
999 | | - <h4 class="text-xs font-medium uppercase mb-1" style="color: var(--ds-text-subtlest);">{t('common.description')}</h4> |
1000 | | - <p class="text-sm" style="color: var(--ds-text);">{selectedAsset.description}</p> |
1001 | | - </div> |
1002 | | - {/if} |
1003 | | - <div class="space-y-3"> |
1004 | | - {#if selectedAsset.asset_type_name} |
1005 | | - <div> |
1006 | | - <h4 class="text-xs font-medium uppercase mb-1" style="color: var(--ds-text-subtlest);">{t('common.type')}</h4> |
1007 | | - <span class="inline-flex items-center gap-1" style="color: var(--ds-text);"> |
1008 | | - <ColorDot color={selectedAsset.asset_type_color || '#6b7280'} /> |
1009 | | - {selectedAsset.asset_type_name} |
1010 | | - </span> |
1011 | | - </div> |
1012 | | - {/if} |
1013 | | - {#if selectedAsset.category_name} |
1014 | | - <div> |
1015 | | - <h4 class="text-xs font-medium uppercase mb-1" style="color: var(--ds-text-subtlest);">{t('common.category')}</h4> |
1016 | | - <span class="inline-flex items-center gap-1" style="color: var(--ds-text);"> |
1017 | | - <IconFolder class="w-4 h-4 text-yellow-500" /> |
1018 | | - {selectedAsset.category_name} |
1019 | | - </span> |
1020 | | - </div> |
1021 | | - {/if} |
1022 | | - {#if selectedAsset.status_name} |
1023 | | - <div> |
1024 | | - <h4 class="text-xs font-medium uppercase mb-1" style="color: var(--ds-text-subtlest);">{t('common.status')}</h4> |
1025 | | - <span class="inline-flex items-center gap-1.5" style="color: var(--ds-text);"> |
1026 | | - <span class="w-2 h-2 rounded-full" style="background-color: {selectedAsset.status_color || '#6b7280'};"></span> |
1027 | | - {selectedAsset.status_name} |
1028 | | - </span> |
1029 | | - </div> |
1030 | | - {/if} |
1031 | | - {#if selectedAsset.asset_tag} |
1032 | | - <div> |
1033 | | - <h4 class="text-xs font-medium uppercase mb-1" style="color: var(--ds-text-subtlest);">Asset Tag</h4> |
1034 | | - <span class="text-sm font-mono" style="color: var(--ds-text);">{selectedAsset.asset_tag}</span> |
1035 | | - </div> |
1036 | | - {/if} |
1037 | | - {#if selectedAsset.creator_name} |
1038 | | - <div> |
1039 | | - <h4 class="text-xs font-medium uppercase mb-1" style="color: var(--ds-text-subtlest);">{t('common.createdBy')}</h4> |
1040 | | - <span class="text-sm" style="color: var(--ds-text);">{selectedAsset.creator_name}</span> |
1041 | | - </div> |
1042 | | - {/if} |
1043 | | - <div> |
1044 | | - <h4 class="text-xs font-medium uppercase mb-1" style="color: var(--ds-text-subtlest);">{t('common.created')}</h4> |
1045 | | - <span class="text-sm" style="color: var(--ds-text);">{formatDateSimple(selectedAsset.created_at)}</span> |
1046 | | - </div> |
1047 | | - <div> |
1048 | | - <h4 class="text-xs font-medium uppercase mb-1" style="color: var(--ds-text-subtlest);">{t('common.updated')}</h4> |
1049 | | - <span class="text-sm" style="color: var(--ds-text);">{formatDateSimple(selectedAsset.updated_at)}</span> |
1050 | | - </div> |
1051 | | - {#if selectedAsset.linked_item_count > 0} |
1052 | | - <div> |
1053 | | - <h4 class="text-xs font-medium uppercase mb-1" style="color: var(--ds-text-subtlest);">Linked Items</h4> |
1054 | | - <span class="text-sm" style="color: var(--ds-text);">{selectedAsset.linked_item_count}</span> |
1055 | | - </div> |
1056 | | - {/if} |
1057 | | - </div> |
1058 | | - {#if selectedAsset.custom_field_values && Object.keys(selectedAsset.custom_field_values).length > 0} |
1059 | | - <div class="border-t pt-4 mt-4" style="border-color: var(--ds-border);"> |
1060 | | - <h4 class="text-xs font-medium uppercase mb-3" style="color: var(--ds-text-subtlest);">Custom Fields</h4> |
1061 | | - {#each Object.entries(selectedAsset.custom_field_values) as [fieldId, value]} |
1062 | | - {@const fieldDef = displayTypeFields.find(f => String(f.custom_field_id) === String(fieldId))} |
1063 | | - {#if fieldDef && value !== null && value !== ''} |
1064 | | - <div class="mb-3"> |
1065 | | - <h4 class="text-xs font-medium uppercase mb-1" style="color: var(--ds-text-subtlest);">{fieldDef.field_name}</h4> |
1066 | | - <CustomFieldRenderer |
1067 | | - field={{ |
1068 | | - id: fieldDef.custom_field_id, |
1069 | | - name: fieldDef.field_name, |
1070 | | - field_type: fieldDef.field_type, |
1071 | | - options: fieldDef.options |
1072 | | - }} |
1073 | | - value={value} |
1074 | | - readonly={true} |
1075 | | - noPadding={true} |
1076 | | - /> |
1077 | | - </div> |
1078 | | - {/if} |
1079 | | - {/each} |
1080 | | - </div> |
1081 | | - {/if} |
| 892 | + <AssetDetailContent asset={selectedAsset} fieldDefinitions={displayTypeFields} /> |
1082 | 893 | </div> |
1083 | 894 | </div> |
1084 | 895 | {/if} |
|
0 commit comments