feat(icons): add brand icon picker from dashboard-icons
Add a second tab in the node Icon picker to choose from the ~2250 brand icons hosted by homarr-labs/dashboard-icons (Plex, Sonarr, Home Assistant, etc.) served via jsDelivr CDN. The Generic tab keeps the existing lucide picker unchanged. Storage uses a 'brand:<slug>' prefix on custom_icon, so existing nodes referencing lucide keys keep working with zero migration. A new resolveCustomIcon helper returns a discriminated union (lucide | brand) and a NodeIcon component centralizes rendering for both kinds. Includes a manifest fetch script (scripts/fetch-dashboard-icons.mjs) and a checked-in dashboardIcons.json snapshot.
This commit is contained in:
@@ -44,6 +44,7 @@ vi.mock('@/utils/nodeColors', () => ({
|
||||
|
||||
vi.mock('@/utils/nodeIcons', () => ({
|
||||
resolveNodeIcon: (_typeIcon: unknown) => _typeIcon,
|
||||
isBrandIconKey: (k: string | undefined) => !!k && k.startsWith('brand:'),
|
||||
}))
|
||||
|
||||
vi.mock('@/utils/maskIp', () => ({
|
||||
|
||||
@@ -3,7 +3,8 @@ import { Handle, Position, NodeResizer, useUpdateNodeInternals, useViewport, typ
|
||||
import { Cpu, MemoryStick, HardDrive, ExternalLink, type LucideIcon } from 'lucide-react'
|
||||
import type { NodeData } from '@/types'
|
||||
import { resolveNodeColors } from '@/utils/nodeColors'
|
||||
import { resolveNodeIcon } from '@/utils/nodeIcons'
|
||||
import { resolveNodeIcon, isBrandIconKey } from '@/utils/nodeIcons'
|
||||
import { NodeIcon } from '@/components/ui/NodeIcon'
|
||||
import { resolvePropertyIcon } from '@/utils/propertyIcons'
|
||||
import { useThemeStore } from '@/stores/themeStore'
|
||||
import { THEMES } from '@/utils/themes'
|
||||
@@ -98,7 +99,9 @@ export function BaseNode({ id, data, selected, icon: typeIcon, width, height }:
|
||||
background: theme.colors.nodeIconBackground,
|
||||
}}
|
||||
>
|
||||
{createElement(resolvedIcon, { size: 15 })}
|
||||
{isBrandIconKey(data.custom_icon)
|
||||
? <NodeIcon typeIcon={typeIcon} customIconKey={data.custom_icon} size={15} />
|
||||
: createElement(resolvedIcon, { size: 15 })}
|
||||
</div>
|
||||
|
||||
{/* Label + IP */}
|
||||
|
||||
@@ -3,7 +3,8 @@ import { Handle, Position, NodeResizer, type NodeProps, type Node } from '@xyflo
|
||||
import { Layers } from 'lucide-react'
|
||||
import type { NodeData } from '@/types'
|
||||
import { resolveNodeColors } from '@/utils/nodeColors'
|
||||
import { resolveNodeIcon } from '@/utils/nodeIcons'
|
||||
import { resolveNodeIcon, isBrandIconKey } from '@/utils/nodeIcons'
|
||||
import { NodeIcon } from '@/components/ui/NodeIcon'
|
||||
import { resolvePropertyIcon } from '@/utils/propertyIcons'
|
||||
import { useCanvasStore } from '@/stores/canvasStore'
|
||||
import { maskIp, splitIps } from '@/utils/maskIp'
|
||||
@@ -87,7 +88,9 @@ export function ProxmoxGroupNode(props: NodeProps<Node<NodeData>>) {
|
||||
background: theme.colors.nodeIconBackground,
|
||||
}}
|
||||
>
|
||||
{createElement(resolvedIcon, { size: 12 })}
|
||||
{isBrandIconKey(data.custom_icon)
|
||||
? <NodeIcon typeIcon={Layers} customIconKey={data.custom_icon} size={12} />
|
||||
: createElement(resolvedIcon, { size: 12 })}
|
||||
</div>
|
||||
<div className="flex flex-col min-w-0 flex-1">
|
||||
<span
|
||||
|
||||
Reference in New Issue
Block a user