|
| 1 | +<script lang="ts"> |
| 2 | + import { hiddenObjects, sidebarHiddenGroups } from '../lib/state'; |
| 3 | + import { VisibilityManager } from '../lib/commands/VisibilityManager'; |
| 4 | + import GroupButton from './GroupButton.svelte'; |
| 5 | + import ObjectIcon from '../icons/ObjectIcon.svelte'; |
| 6 | + import EyeIcon from '../icons/EyeIcon.svelte'; |
| 7 | + import EyeOffIcon from '../icons/EyeOffIcon.svelte'; |
| 8 | +
|
| 9 | + let { |
| 10 | + objectKey, |
| 11 | + faces, |
| 12 | + nodes, |
| 13 | + color, |
| 14 | + }: { |
| 15 | + objectKey: string; |
| 16 | + faces: string[]; |
| 17 | + nodes: string[]; |
| 18 | + color: number[]; |
| 19 | + } = $props(); |
| 20 | +
|
| 21 | + let objectName = $derived(objectKey.replace('all_', '').replace('.obj', '')); |
| 22 | + let colorCss = $derived( |
| 23 | + `rgb(${Math.round(color[0] * 255)},${Math.round(color[1] * 255)},${Math.round(color[2] * 255)})` |
| 24 | + ); |
| 25 | + let isHidden = $derived($hiddenObjects.has(objectKey)); |
| 26 | + let collapsed = $state(false); |
| 27 | +
|
| 28 | + let hiddenGroupCount = $derived.by(() => { |
| 29 | + const hidden = $sidebarHiddenGroups.get(objectKey); |
| 30 | + if (!hidden) return 0; |
| 31 | + return [...faces, ...nodes].filter((g) => hidden.has(g)).length; |
| 32 | + }); |
| 33 | +
|
| 34 | + let allGroupsHiddenFromSidebar = $derived.by(() => { |
| 35 | + const hidden = $sidebarHiddenGroups.get(objectKey); |
| 36 | + if (!hidden) return false; |
| 37 | + return [...faces, ...nodes].every((g) => hidden.has(g)); |
| 38 | + }); |
| 39 | +
|
| 40 | + let groupCount = $derived(faces.length + nodes.length); |
| 41 | +
|
| 42 | + function toggleVisibility() { |
| 43 | + VisibilityManager.Instance.toggleObjectVisibility(objectKey); |
| 44 | + } |
| 45 | +
|
| 46 | + function toggleCollapsed() { |
| 47 | + if (isHidden || allGroupsHiddenFromSidebar) return; |
| 48 | + collapsed = !collapsed; |
| 49 | + } |
| 50 | +
|
| 51 | + let contextMenu: { x: number; y: number } | null = $state(null); |
| 52 | +
|
| 53 | + function onContextMenu(e: MouseEvent) { |
| 54 | + e.preventDefault(); |
| 55 | + contextMenu = { x: e.clientX, y: e.clientY }; |
| 56 | + } |
| 57 | +
|
| 58 | + function closeContextMenu() { |
| 59 | + contextMenu = null; |
| 60 | + } |
| 61 | +
|
| 62 | + function hideAllOthers() { |
| 63 | + VisibilityManager.Instance.hideAllOthers(objectKey); |
| 64 | + closeContextMenu(); |
| 65 | + } |
| 66 | +</script> |
| 67 | + |
| 68 | +{#if contextMenu} |
| 69 | + <div |
| 70 | + class="fixed inset-0 z-40" |
| 71 | + onclick={closeContextMenu} |
| 72 | + oncontextmenu={(e) => { |
| 73 | + e.preventDefault(); |
| 74 | + closeContextMenu(); |
| 75 | + }} |
| 76 | + role="presentation" |
| 77 | + ></div> |
| 78 | + <div |
| 79 | + class="fixed z-[9999] bg-ui-popup-bg border border-ui-border rounded shadow-[0_4px_16px_rgba(0,0,0,0.25)] py-[3px] overflow-hidden" |
| 80 | + style="left: {contextMenu.x}px; top: {contextMenu.y}px" |
| 81 | + > |
| 82 | + <button |
| 83 | + class="flex items-center w-full px-[10px] py-[5px] text-xs cursor-pointer text-ui-fg whitespace-nowrap bg-transparent border-0 text-left hover:bg-ui-elem-hover" |
| 84 | + onclick={hideAllOthers} |
| 85 | + > |
| 86 | + Hide all other objects |
| 87 | + </button> |
| 88 | + </div> |
| 89 | +{/if} |
| 90 | + |
| 91 | +<span |
| 92 | + role="group" |
| 93 | + class="h-4.5 w-full self-stretch flex items-center gap-1 pl-1.25 pr-0.5 mb-2 not-nth-of-type-[1]:mt-1 text-xs font-bold text-ui-text-primary" |
| 94 | + oncontextmenu={onContextMenu} |
| 95 | +> |
| 96 | + <span style="color: {colorCss}"><ObjectIcon class="size-[18px]" /></span> |
| 97 | + <span |
| 98 | + class="flex-1 truncate text-center px-2 select-none" |
| 99 | + style="cursor: {isHidden || allGroupsHiddenFromSidebar ? 'default' : 'pointer'}" |
| 100 | + onclick={toggleCollapsed} |
| 101 | + onkeydown={(e) => e.key === 'Enter' && toggleCollapsed()} |
| 102 | + role="button" |
| 103 | + tabindex="0" |
| 104 | + > |
| 105 | + {objectName} |
| 106 | + </span> |
| 107 | + <button |
| 108 | + class="size-4.5 shrink-0 flex items-center justify-center cursor-pointer opacity-40 hover:opacity-90" |
| 109 | + title="Hide/show mesh" |
| 110 | + onclick={toggleVisibility} |
| 111 | + > |
| 112 | + {#if isHidden} |
| 113 | + <EyeOffIcon class="size-4" /> |
| 114 | + {:else} |
| 115 | + <EyeIcon class="size-4" /> |
| 116 | + {/if} |
| 117 | + </button> |
| 118 | +</span> |
| 119 | + |
| 120 | +{#if isHidden} |
| 121 | + <div class="w-full text-center text-[0.7rem] font-normal mb-2 text-ui-text-muted"> |
| 122 | + {groupCount} groups |
| 123 | + </div> |
| 124 | +{:else if !collapsed} |
| 125 | + <div class="w-full flex flex-col items-center space-y-1"> |
| 126 | + {#each faces as groupName} |
| 127 | + <GroupButton {objectKey} {groupName} isFace={true} /> |
| 128 | + {/each} |
| 129 | + {#each nodes as groupName} |
| 130 | + <GroupButton {objectKey} {groupName} isFace={false} /> |
| 131 | + {/each} |
| 132 | + {#if hiddenGroupCount > 0} |
| 133 | + <div class="w-full text-center text-[0.7rem] pb-1 text-ui-text-muted"> |
| 134 | + {hiddenGroupCount} hidden |
| 135 | + </div> |
| 136 | + {/if} |
| 137 | + </div> |
| 138 | +{:else} |
| 139 | + <div class="w-full text-center text-[0.7rem] font-normal mb-2 text-ui-text-muted"> |
| 140 | + {groupCount} groups |
| 141 | + </div> |
| 142 | +{/if} |
0 commit comments