mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-06 00:02:13 +00:00
refactor: unify group node headers with shared GroupHeader component
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
c0d193e0c7
commit
77c3ef6abf
@@ -0,0 +1,104 @@
|
||||
<script lang="ts">
|
||||
import { NOTE_COLORS, NoteColor } from './noteColors'
|
||||
import { stopPropagation, preventDefault } from 'svelte/legacy'
|
||||
import { ChevronRight } from 'lucide-svelte'
|
||||
import TextInput from '$lib/components/text_input/TextInput.svelte'
|
||||
|
||||
interface Props {
|
||||
summary?: string
|
||||
color?: string
|
||||
collapsed: boolean
|
||||
editMode: boolean
|
||||
onToggleCollapse: () => void
|
||||
onSummaryUpdate?: (text: string) => void
|
||||
}
|
||||
|
||||
let { summary, color, collapsed, editMode, onToggleCollapse, onSummaryUpdate }: Props =
|
||||
$props()
|
||||
|
||||
let colorConfig = $derived(
|
||||
NOTE_COLORS[(color as NoteColor) ?? NoteColor.BLUE] ?? NOTE_COLORS[NoteColor.BLUE]
|
||||
)
|
||||
|
||||
// Inline summary editing
|
||||
let editingSummary = $state(false)
|
||||
let summaryInput = $state('')
|
||||
let textInputComponent: TextInput | undefined = $state(undefined)
|
||||
|
||||
function startEditingSummary() {
|
||||
if (!editMode) return
|
||||
editingSummary = true
|
||||
summaryInput = summary ?? ''
|
||||
requestAnimationFrame(() => {
|
||||
textInputComponent?.focus()
|
||||
textInputComponent?.select()
|
||||
})
|
||||
}
|
||||
|
||||
function saveSummary() {
|
||||
editingSummary = false
|
||||
const trimmed = summaryInput.trim()
|
||||
if (trimmed !== (summary ?? '')) {
|
||||
onSummaryUpdate?.(trimmed)
|
||||
}
|
||||
}
|
||||
|
||||
function handleSummaryKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter') {
|
||||
saveSummary()
|
||||
} else if (event.key === 'Escape') {
|
||||
editingSummary = false
|
||||
}
|
||||
}
|
||||
|
||||
// Animate chevron rotation on mount
|
||||
let chevronRotated = $state(collapsed)
|
||||
$effect(() => {
|
||||
requestAnimationFrame(() => {
|
||||
chevronRotated = !collapsed
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
class="flex items-center h-[22px] w-full px-2 relative cursor-pointer {colorConfig.background} {colorConfig.text} {collapsed
|
||||
? 'rounded-t-md'
|
||||
: 'rounded-md'}"
|
||||
onclick={stopPropagation(preventDefault(onToggleCollapse))}
|
||||
onpointerdown={stopPropagation(preventDefault(() => {}))}
|
||||
title={collapsed ? 'Expand group' : 'Collapse group'}
|
||||
>
|
||||
<div
|
||||
class="flex items-center justify-center shrink-0 opacity-60 transition-transform duration-100"
|
||||
class:rotate-90={chevronRotated}
|
||||
>
|
||||
<ChevronRight size={12} />
|
||||
</div>
|
||||
<div class="absolute inset-x-0 flex items-center justify-center h-full pointer-events-none px-7">
|
||||
{#if editingSummary}
|
||||
<TextInput
|
||||
bind:this={textInputComponent}
|
||||
bind:value={summaryInput}
|
||||
size="xs"
|
||||
class="!bg-transparent !border-transparent !shadow-none !text-2xs !font-medium !p-0 !m-0 !min-w-0 w-full text-center !min-h-0 !h-auto nodrag nowheel pointer-events-auto"
|
||||
inputProps={{
|
||||
placeholder: 'Group',
|
||||
onblur: saveSummary,
|
||||
onkeydown: handleSummaryKeydown,
|
||||
spellcheck: false
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<span
|
||||
class="text-2xs font-medium truncate text-center pointer-events-auto {editMode
|
||||
? 'cursor-text rounded px-0.5 -mx-0.5 hover:bg-black/10 dark:hover:bg-white/10'
|
||||
: ''}"
|
||||
onclick={editMode ? stopPropagation(preventDefault(startEditingSummary)) : undefined}
|
||||
onpointerdown={editMode ? stopPropagation(preventDefault(() => {})) : undefined}
|
||||
>{summary || 'Group'}</span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -40,12 +40,10 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center gap-1">
|
||||
{#each displayModules as mod, i (mod.id)}
|
||||
<div
|
||||
class="w-6 h-6 rounded-full overflow-hidden bg-surface-secondary flex items-center justify-center shrink-0 shadow-sm"
|
||||
style:z-index={displayModules.length - i}
|
||||
class:-ml-1={i > 0}
|
||||
class="w-6 h-6 rounded-full overflow-hidden bg-surface-tertiary flex items-center justify-center shrink-0 shadow-sm"
|
||||
>
|
||||
<FlowModuleIcon module={mod} size={14} />
|
||||
</div>
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
import { getGroupEditorContext, type FlowGroup } from './groupEditor.svelte'
|
||||
import { NoteColor, NOTE_COLORS } from './noteColors'
|
||||
import GroupActionBar from './GroupActionBar.svelte'
|
||||
import StepCountTab from './StepCountTab.svelte'
|
||||
import GroupHeader from './GroupHeader.svelte'
|
||||
import type { CollapsedSubflowN } from './graphBuilder.svelte'
|
||||
import { stopPropagation, preventDefault } from 'svelte/legacy'
|
||||
|
||||
interface Props {
|
||||
hoveredNodeId: string | null
|
||||
@@ -21,35 +20,6 @@
|
||||
// Menu open state
|
||||
let menuOpen = $state(false)
|
||||
|
||||
// Inline summary editing
|
||||
let editingGroupId = $state<string | null>(null)
|
||||
let summaryInput = $state('')
|
||||
let summaryInputEl = $state<HTMLInputElement | undefined>(undefined)
|
||||
|
||||
function startEditingSummary(groupId: string, current: string) {
|
||||
if (!editMode) return
|
||||
editingGroupId = groupId
|
||||
summaryInput = current
|
||||
requestAnimationFrame(() => {
|
||||
summaryInputEl?.focus()
|
||||
summaryInputEl?.select()
|
||||
})
|
||||
}
|
||||
|
||||
function saveSummary(groupId: string) {
|
||||
editingGroupId = null
|
||||
const trimmed = summaryInput.trim()
|
||||
groupEditorContext?.groupEditor.updateSummary(groupId, trimmed)
|
||||
}
|
||||
|
||||
function handleSummaryKeydown(event: KeyboardEvent, groupId: string) {
|
||||
if (event.key === 'Enter') {
|
||||
saveSummary(groupId)
|
||||
} else if (event.key === 'Escape') {
|
||||
editingGroupId = null
|
||||
}
|
||||
}
|
||||
|
||||
// Action bar hover state to prevent flicker
|
||||
let actionBarHovered = $state(false)
|
||||
|
||||
@@ -86,63 +56,27 @@
|
||||
if (group.module_ids.length === 0) return null
|
||||
const { minX, minY, maxX, maxY } = calculateNodesBoundsWithOffset(group.module_ids, allNodes)
|
||||
const padding = 16
|
||||
const topPadding = 28
|
||||
const topPadding = 34
|
||||
const headerHeight = 22
|
||||
const halfHeader = headerHeight / 2
|
||||
return {
|
||||
x: minX - padding,
|
||||
y: minY - topPadding,
|
||||
y: minY - topPadding + halfHeader,
|
||||
width: maxX - minX + 2 * padding,
|
||||
height: maxY - minY + topPadding + padding
|
||||
height: maxY - minY + topPadding - halfHeader + padding,
|
||||
headerY: minY - topPadding
|
||||
}
|
||||
}
|
||||
|
||||
// Outline color mapping — default uses /60 opacity, hover uses full opacity
|
||||
const GROUP_OUTLINE_COLORS: Record<NoteColor, string> = {
|
||||
[NoteColor.YELLOW]: 'outline-yellow-400/60 dark:outline-yellow-600/60',
|
||||
[NoteColor.BLUE]: 'outline-blue-400/60 dark:outline-blue-600/60',
|
||||
[NoteColor.GREEN]: 'outline-green-400/60 dark:outline-green-600/60',
|
||||
[NoteColor.PURPLE]: 'outline-purple-400/60 dark:outline-purple-600/60',
|
||||
[NoteColor.PINK]: 'outline-pink-400/60 dark:outline-pink-600/60',
|
||||
[NoteColor.ORANGE]: 'outline-orange-400/60 dark:outline-orange-600/60',
|
||||
[NoteColor.RED]: 'outline-red-400/60 dark:outline-red-600/60',
|
||||
[NoteColor.CYAN]: 'outline-cyan-400/60 dark:outline-cyan-600/60',
|
||||
[NoteColor.LIME]: 'outline-lime-400/60 dark:outline-lime-600/60',
|
||||
[NoteColor.GRAY]: 'outline-gray-400/60 dark:outline-gray-600/60'
|
||||
}
|
||||
|
||||
const GROUP_OUTLINE_COLORS_HOVER: Record<NoteColor, string> = {
|
||||
[NoteColor.YELLOW]: 'outline-yellow-400 dark:outline-yellow-600',
|
||||
[NoteColor.BLUE]: 'outline-blue-400 dark:outline-blue-600',
|
||||
[NoteColor.GREEN]: 'outline-green-400 dark:outline-green-600',
|
||||
[NoteColor.PURPLE]: 'outline-purple-400 dark:outline-purple-600',
|
||||
[NoteColor.PINK]: 'outline-pink-400 dark:outline-pink-600',
|
||||
[NoteColor.ORANGE]: 'outline-orange-400 dark:outline-orange-600',
|
||||
[NoteColor.RED]: 'outline-red-400 dark:outline-red-600',
|
||||
[NoteColor.CYAN]: 'outline-cyan-400 dark:outline-cyan-600',
|
||||
[NoteColor.LIME]: 'outline-lime-400 dark:outline-lime-600',
|
||||
[NoteColor.GRAY]: 'outline-gray-400 dark:outline-gray-600'
|
||||
}
|
||||
|
||||
function getOutlineColorClass(color?: string, hovered?: boolean): string {
|
||||
const map = hovered ? GROUP_OUTLINE_COLORS_HOVER : GROUP_OUTLINE_COLORS
|
||||
return map[(color as NoteColor) ?? NoteColor.BLUE] ?? map[NoteColor.BLUE]
|
||||
}
|
||||
|
||||
const GROUP_BG_COLORS: Record<NoteColor, string> = {
|
||||
[NoteColor.YELLOW]: 'bg-yellow-400/5 dark:bg-yellow-600/5',
|
||||
[NoteColor.BLUE]: 'bg-blue-400/5 dark:bg-blue-600/5',
|
||||
[NoteColor.GREEN]: 'bg-green-400/5 dark:bg-green-600/5',
|
||||
[NoteColor.PURPLE]: 'bg-purple-400/5 dark:bg-purple-600/5',
|
||||
[NoteColor.PINK]: 'bg-pink-400/5 dark:bg-pink-600/5',
|
||||
[NoteColor.ORANGE]: 'bg-orange-400/5 dark:bg-orange-600/5',
|
||||
[NoteColor.RED]: 'bg-red-400/5 dark:bg-red-600/5',
|
||||
[NoteColor.CYAN]: 'bg-cyan-400/5 dark:bg-cyan-600/5',
|
||||
[NoteColor.LIME]: 'bg-lime-400/5 dark:bg-lime-600/5',
|
||||
[NoteColor.GRAY]: 'bg-gray-400/5 dark:bg-gray-600/5'
|
||||
const config = NOTE_COLORS[(color as NoteColor) ?? NoteColor.BLUE] ?? NOTE_COLORS[NoteColor.BLUE]
|
||||
return hovered ? config.outline : config.outlineHover
|
||||
}
|
||||
|
||||
function getBgColorClass(color?: string): string {
|
||||
return (
|
||||
GROUP_BG_COLORS[(color as NoteColor) ?? NoteColor.BLUE] ?? GROUP_BG_COLORS[NoteColor.BLUE]
|
||||
NOTE_COLORS[(color as NoteColor) ?? NoteColor.BLUE]?.backgroundLight ??
|
||||
NOTE_COLORS[NoteColor.BLUE].backgroundLight
|
||||
)
|
||||
}
|
||||
|
||||
@@ -184,7 +118,7 @@
|
||||
<!-- Bounding box background + outline (behind nodes) -->
|
||||
<ViewportPortal target="back">
|
||||
<div
|
||||
class="absolute rounded-lg outline outline-1 pointer-events-none transition-colors duration-150 {getOutlineColorClass(
|
||||
class="absolute rounded-lg outline outline-1 -outline-offset-1 pointer-events-none transition-colors duration-150 {getOutlineColorClass(
|
||||
group.color,
|
||||
visibleGroup?.id === group.id
|
||||
)} {getBgColorClass(group.color)}"
|
||||
@@ -194,58 +128,25 @@
|
||||
></div>
|
||||
</ViewportPortal>
|
||||
|
||||
<!-- StepCountTab (left) + summary + ellipsis menu (right) -->
|
||||
<!-- Group header + ellipsis menu -->
|
||||
<ViewportPortal target="front">
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="absolute flex items-start justify-between"
|
||||
style="pointer-events: auto; transform: translate({bounds.x}px, {bounds.y}px); width: {bounds.width}px;"
|
||||
class="absolute flex flex-col items-center"
|
||||
style="pointer-events: auto; transform: translate({bounds.x}px, {bounds.headerY}px); width: {bounds.width}px;"
|
||||
style:z-index="4"
|
||||
>
|
||||
<div class="relative" style="margin-left: 16px;">
|
||||
<StepCountTab
|
||||
stepCount={group.module_ids.length}
|
||||
<div class="relative" style="width: 275px;">
|
||||
<GroupHeader
|
||||
summary={group.summary}
|
||||
color={group.color}
|
||||
collapsed={false}
|
||||
short
|
||||
onExpand={() => toggleCollapse(group.id)}
|
||||
{editMode}
|
||||
onToggleCollapse={() => toggleCollapse(group.id)}
|
||||
onSummaryUpdate={(text) =>
|
||||
groupEditorContext?.groupEditor.updateSummary(group.id, text)}
|
||||
/>
|
||||
{#if editingGroupId === group.id}
|
||||
{@const textColorClass =
|
||||
NOTE_COLORS[(group.color as NoteColor) ?? NoteColor.BLUE]?.text ?? ''}
|
||||
<input
|
||||
bind:this={summaryInputEl}
|
||||
bind:value={summaryInput}
|
||||
class="absolute !text-3xs !font-medium !h-4 !bg-transparent !outline-none {textColorClass}"
|
||||
style="top: 2px; left:-3px; width: 160px; padding: 2px 2px;"
|
||||
onblur={() => saveSummary(group.id)}
|
||||
onkeydown={(e) => handleSummaryKeydown(e, group.id)}
|
||||
onclick={stopPropagation(preventDefault(() => {}))}
|
||||
onpointerdown={stopPropagation(preventDefault(() => {}))}
|
||||
spellcheck={false}
|
||||
/>
|
||||
{:else}
|
||||
{@const textColorClass =
|
||||
NOTE_COLORS[(group.color as NoteColor) ?? NoteColor.BLUE]?.text ?? ''}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<span
|
||||
class="absolute text-3xs font-medium truncate max-w-[150px] text-opacity-60 {textColorClass} {editMode
|
||||
? 'cursor-text rounded px-0.5 -mx-0.5 hover:text-opacity-100'
|
||||
: ''}"
|
||||
style="top: 2px; left: 0px;"
|
||||
onclick={editMode
|
||||
? stopPropagation(
|
||||
preventDefault(() => startEditingSummary(group.id, group.summary ?? ''))
|
||||
)
|
||||
: undefined}
|
||||
onpointerdown={editMode ? stopPropagation(preventDefault(() => {})) : undefined}
|
||||
>{group.summary || (editMode ? 'Group' : '')}</span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
{#if editMode}
|
||||
<div class="relative" style="margin-top: -8px;">
|
||||
{#if editMode}
|
||||
<GroupActionBar
|
||||
note={group.note}
|
||||
color={group.color}
|
||||
@@ -262,8 +163,8 @@
|
||||
visibleGroup = undefined
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</ViewportPortal>
|
||||
{/if}
|
||||
|
||||
@@ -258,10 +258,11 @@ export function getGroupEditorContext(): GroupEditorContext | undefined {
|
||||
return getContext<GroupEditorContext | undefined>(CONTEXT_KEY)
|
||||
}
|
||||
|
||||
/** Extra vertical space pushed above the topmost node of each group for the tab */
|
||||
export const GROUP_HEADER_HEIGHT = 0
|
||||
/** Height of the group header bar */
|
||||
export const GROUP_HEADER_HEIGHT = 22
|
||||
|
||||
const GROUP_TOP_MARGIN = 28
|
||||
/** Extra margin between the header and the first node */
|
||||
const GROUP_TOP_MARGIN = 12
|
||||
|
||||
/**
|
||||
* Compute adjusted node positions for collapsed groups whose note is visible.
|
||||
|
||||
@@ -14,6 +14,7 @@ export enum NoteColor {
|
||||
|
||||
export interface NoteColorConfig {
|
||||
background: string
|
||||
backgroundLight: string
|
||||
outline: string
|
||||
outlineHover: string
|
||||
text: string
|
||||
@@ -24,6 +25,7 @@ export interface NoteColorConfig {
|
||||
export const NOTE_COLORS: Record<NoteColor, NoteColorConfig> = {
|
||||
[NoteColor.YELLOW]: {
|
||||
background: 'bg-yellow-200 dark:bg-yellow-900',
|
||||
backgroundLight: 'bg-yellow-400/5 dark:bg-yellow-600/5',
|
||||
outline: 'outline-yellow-300 dark:outline-yellow-600',
|
||||
outlineHover: 'outline-yellow-300/60 dark:outline-yellow-600/60',
|
||||
text: 'text-yellow-900 dark:text-yellow-100',
|
||||
@@ -31,6 +33,7 @@ export const NOTE_COLORS: Record<NoteColor, NoteColorConfig> = {
|
||||
},
|
||||
[NoteColor.BLUE]: {
|
||||
background: 'bg-blue-100 dark:bg-blue-950',
|
||||
backgroundLight: 'bg-blue-400/5 dark:bg-blue-600/5',
|
||||
outline: 'outline-blue-300 dark:outline-blue-600',
|
||||
outlineHover: 'outline-blue-300/60 dark:outline-blue-600/60',
|
||||
text: 'text-blue-900 dark:text-blue-100',
|
||||
@@ -38,6 +41,7 @@ export const NOTE_COLORS: Record<NoteColor, NoteColorConfig> = {
|
||||
},
|
||||
[NoteColor.GREEN]: {
|
||||
background: 'bg-green-200 dark:bg-green-900',
|
||||
backgroundLight: 'bg-green-400/5 dark:bg-green-600/5',
|
||||
outline: 'outline-green-300 dark:outline-green-600',
|
||||
outlineHover: 'outline-green-300/60 dark:outline-green-600/60',
|
||||
text: 'text-green-900 dark:text-green-100',
|
||||
@@ -45,6 +49,7 @@ export const NOTE_COLORS: Record<NoteColor, NoteColorConfig> = {
|
||||
},
|
||||
[NoteColor.PURPLE]: {
|
||||
background: 'bg-purple-200 dark:bg-purple-900',
|
||||
backgroundLight: 'bg-purple-400/5 dark:bg-purple-600/5',
|
||||
outline: 'outline-purple-300 dark:outline-purple-600',
|
||||
outlineHover: 'outline-purple-300/60 dark:outline-purple-600/60',
|
||||
text: 'text-purple-900 dark:text-purple-100',
|
||||
@@ -52,6 +57,7 @@ export const NOTE_COLORS: Record<NoteColor, NoteColorConfig> = {
|
||||
},
|
||||
[NoteColor.PINK]: {
|
||||
background: 'bg-pink-200 dark:bg-pink-900',
|
||||
backgroundLight: 'bg-pink-400/5 dark:bg-pink-600/5',
|
||||
outline: 'outline-pink-300 dark:outline-pink-600',
|
||||
outlineHover: 'outline-pink-300/60 dark:outline-pink-600/60',
|
||||
text: 'text-pink-900 dark:text-pink-100',
|
||||
@@ -59,6 +65,7 @@ export const NOTE_COLORS: Record<NoteColor, NoteColorConfig> = {
|
||||
},
|
||||
[NoteColor.ORANGE]: {
|
||||
background: 'bg-orange-200 dark:bg-orange-900',
|
||||
backgroundLight: 'bg-orange-400/5 dark:bg-orange-600/5',
|
||||
outline: 'outline-orange-300 dark:outline-orange-600',
|
||||
outlineHover: 'outline-orange-300/60 dark:outline-orange-600/60',
|
||||
text: 'text-orange-900 dark:text-orange-100',
|
||||
@@ -66,6 +73,7 @@ export const NOTE_COLORS: Record<NoteColor, NoteColorConfig> = {
|
||||
},
|
||||
[NoteColor.RED]: {
|
||||
background: 'bg-red-200 dark:bg-red-900',
|
||||
backgroundLight: 'bg-red-400/5 dark:bg-red-600/5',
|
||||
outline: 'outline-red-300 dark:outline-red-600',
|
||||
outlineHover: 'outline-red-300/60 dark:outline-red-600/60',
|
||||
text: 'text-red-900 dark:text-red-100',
|
||||
@@ -73,6 +81,7 @@ export const NOTE_COLORS: Record<NoteColor, NoteColorConfig> = {
|
||||
},
|
||||
[NoteColor.CYAN]: {
|
||||
background: 'bg-cyan-200 dark:bg-cyan-900',
|
||||
backgroundLight: 'bg-cyan-400/5 dark:bg-cyan-600/5',
|
||||
outline: 'outline-cyan-300 dark:outline-cyan-600',
|
||||
outlineHover: 'outline-cyan-300/60 dark:outline-cyan-600/60',
|
||||
text: 'text-cyan-900 dark:text-cyan-100',
|
||||
@@ -80,6 +89,7 @@ export const NOTE_COLORS: Record<NoteColor, NoteColorConfig> = {
|
||||
},
|
||||
[NoteColor.LIME]: {
|
||||
background: 'bg-lime-200 dark:bg-lime-900',
|
||||
backgroundLight: 'bg-lime-400/5 dark:bg-lime-600/5',
|
||||
outline: 'outline-lime-300 dark:outline-lime-600',
|
||||
outlineHover: 'outline-lime-300/60 dark:outline-lime-600/60',
|
||||
text: 'text-lime-900 dark:text-lime-100',
|
||||
@@ -87,6 +97,7 @@ export const NOTE_COLORS: Record<NoteColor, NoteColorConfig> = {
|
||||
},
|
||||
[NoteColor.GRAY]: {
|
||||
background: 'bg-gray-200 dark:bg-gray-800',
|
||||
backgroundLight: 'bg-gray-400/5 dark:bg-gray-600/5',
|
||||
outline: 'outline-gray-300 dark:outline-gray-600',
|
||||
outlineHover: 'outline-gray-300/60 dark:outline-gray-600/60',
|
||||
text: 'text-gray-900 dark:text-gray-100',
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
import NodeWrapper from './NodeWrapper.svelte'
|
||||
import type { CollapsedGroupN } from '../../graphBuilder.svelte'
|
||||
import { getGraphContext } from '../../graphContext'
|
||||
import GroupNodeCard from '../../GroupNodeCard.svelte'
|
||||
import GroupHeader from '../../GroupHeader.svelte'
|
||||
import GroupActionBar from '../../GroupActionBar.svelte'
|
||||
import GroupModuleIcons from '../../GroupModuleIcons.svelte'
|
||||
import GroupNoteArea from '../../GroupNoteArea.svelte'
|
||||
import { getGroupEditorContext } from '../../groupEditor.svelte'
|
||||
import StepCountTab from '../../StepCountTab.svelte'
|
||||
import { NOTE_COLORS, NoteColor } from '../../noteColors'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
interface Props {
|
||||
data: CollapsedGroupN['data']
|
||||
@@ -23,6 +26,10 @@
|
||||
groupEditorContext?.groupEditor.getGroups().find((g) => g.id === data.groupId)
|
||||
)
|
||||
|
||||
let noteColorConfig = $derived(
|
||||
NOTE_COLORS[(data.color as NoteColor) ?? NoteColor.BLUE] ?? NOTE_COLORS[NoteColor.BLUE]
|
||||
)
|
||||
|
||||
let hover = $state(false)
|
||||
let menuOpen = $state(false)
|
||||
</script>
|
||||
@@ -31,27 +38,47 @@
|
||||
{#snippet children({ darkMode })}
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div class="relative" onmouseenter={() => (hover = true)} onmouseleave={() => (hover = false)}>
|
||||
<StepCountTab
|
||||
stepCount={data.stepCount}
|
||||
color={data.color}
|
||||
onExpand={() => data.eventHandlers.expandGroup(data.groupId)}
|
||||
/>
|
||||
<div
|
||||
class={twMerge(
|
||||
'w-full module cursor-pointer max-w-full',
|
||||
'rounded-md overflow-clip outline outline-1 -outline-offset-1',
|
||||
noteColorConfig.outlineHover,
|
||||
noteColorConfig.backgroundLight
|
||||
)}
|
||||
style="width: 275px;"
|
||||
>
|
||||
<div class="relative z-1">
|
||||
<GroupHeader
|
||||
summary={data.summary}
|
||||
color={data.color}
|
||||
collapsed={true}
|
||||
editMode={data.editMode}
|
||||
onToggleCollapse={() => data.eventHandlers.expandGroup(data.groupId)}
|
||||
onSummaryUpdate={(text) =>
|
||||
groupEditorContext?.groupEditor.updateSummary(data.groupId, text)}
|
||||
/>
|
||||
{#if data.modules && data.modules.length > 0}
|
||||
<div class="flex items-center justify-center w-full gap-1.5 px-2 h-[34px]">
|
||||
<GroupModuleIcons modules={data.modules} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<GroupNodeCard
|
||||
summary={data.summary}
|
||||
color={data.color}
|
||||
{selected}
|
||||
note={data.note}
|
||||
showNote={data.showNotes && data.note != null}
|
||||
editMode={data.editMode}
|
||||
modules={data.modules}
|
||||
onSummaryUpdate={(text) =>
|
||||
groupEditorContext?.groupEditor.updateSummary(data.groupId, text)}
|
||||
onNoteUpdate={(text) => groupEditorContext?.groupEditor.updateNote(data.groupId, text)}
|
||||
onHeightChange={(h) => {
|
||||
groupEditorContext?.groupEditor.setNoteHeight(data.groupId, h)
|
||||
}}
|
||||
/>
|
||||
{#if data.showNotes && data.note != null}
|
||||
<div class="relative z-1">
|
||||
<GroupNoteArea
|
||||
note={data.note ?? ''}
|
||||
color={data.color}
|
||||
editMode={data.editMode}
|
||||
onHeightChange={(h) => {
|
||||
groupEditorContext?.groupEditor.setNoteHeight(data.groupId, h)
|
||||
}}
|
||||
onNoteUpdate={(text) =>
|
||||
groupEditorContext?.groupEditor.updateNote(data.groupId, text)}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if data.editMode}
|
||||
<GroupActionBar
|
||||
|
||||
Reference in New Issue
Block a user