diff --git a/assets/js/hooks/Mapper/components/map/Map.tsx b/assets/js/hooks/Mapper/components/map/Map.tsx index af4911b6..e0302649 100644 --- a/assets/js/hooks/Mapper/components/map/Map.tsx +++ b/assets/js/hooks/Mapper/components/map/Map.tsx @@ -1,7 +1,6 @@ import { ForwardedRef, forwardRef, MouseEvent, useCallback, useEffect, useMemo } from 'react'; import ReactFlow, { Background, - ConnectionMode, Edge, MiniMap, Node, @@ -23,12 +22,10 @@ import { ContextMenuConnection, ContextMenuRoot, SolarSystemEdge, - SolarSystemNodeDefault, - SolarSystemNodeTheme, useContextMenuConnectionHandlers, useContextMenuRootHandlers, } from './components'; -import { wrapNode } from './utils/wrapNode'; +import { getBehaviorForTheme } from './helpers/getThemeBehavior'; import { OnMapAddSystemCallback, OnMapSelectionChange } from './map.types'; import { SESSION_KEY } from '@/hooks/Mapper/constants.ts'; import { SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types'; @@ -77,9 +74,6 @@ const initialEdges = [ }, ]; - - - const edgeTypes = { floating: SolarSystemEdge, }; @@ -123,23 +117,20 @@ const MapComp = ({ const [nodes, , onNodesChange] = useNodesState>(initialNodes); const [edges, , onEdgesChange] = useEdgesState>(initialEdges); - - const nodeTypes = useMemo(() => { - return { - custom: - theme !== '' && theme !== 'default' - ? wrapNode(SolarSystemNodeTheme) - : wrapNode(SolarSystemNodeDefault), - }; - }, [theme]); - - useMapHandlers(refn, onSelectionChange); useUpdateNodes(nodes); const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers({ onAddSystem }); const { handleConnectionContext, ...connectionCtxProps } = useContextMenuConnectionHandlers(); const { update } = useMapState(); const { variant, gap, size, color } = useBackgroundVars(theme); + const { isPanAndDrag, nodeComponent, connectionMode } = getBehaviorForTheme(theme || 'default'); + + // You can create nodeTypes dynamically based on the node component + const nodeTypes = useMemo(() => { + return { + custom: nodeComponent, + }; + }, [nodeComponent]); const onConnect: OnConnect = useCallback( params => { @@ -228,7 +219,7 @@ const MapComp = ({ onNodesChange(nextChanges); }, - [getNode, onManualDelete, onNodesChange], + [getNode, getNodes, onManualDelete, onNodesChange], ); useEffect(() => { @@ -253,7 +244,7 @@ const MapComp = ({ defaultViewport={getViewPortFromStore()} edgeTypes={edgeTypes} nodeTypes={nodeTypes} - connectionMode={ConnectionMode.Loose} + connectionMode={connectionMode} snapToGrid nodeDragThreshold={10} onNodeDragStop={handleDragStop} @@ -286,6 +277,12 @@ const MapComp = ({ maxZoom={1.5} elevateNodesOnSelect deleteKeyCode={['Delete']} + {...(isPanAndDrag + ? { + selectionOnDrag: true, + panOnDrag: [2], + } + : {})} // TODO need create clear example with problem with that flag // if system is not visible edge not drawing (and any render in Custom node is not happening) // onlyRenderVisibleElements diff --git a/assets/js/hooks/Mapper/components/map/MapProvider.tsx b/assets/js/hooks/Mapper/components/map/MapProvider.tsx index 61e51157..c0960564 100644 --- a/assets/js/hooks/Mapper/components/map/MapProvider.tsx +++ b/assets/js/hooks/Mapper/components/map/MapProvider.tsx @@ -9,6 +9,7 @@ export type MapData = MapUnionTypes & { visibleNodes: Set; showKSpaceBG: boolean; isThickConnections: boolean; + linkedSigEveId: string; }; interface MapProviderProps { diff --git a/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeDefault.module.scss b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeDefault.module.scss index 1e425077..afb94486 100644 --- a/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeDefault.module.scss +++ b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeDefault.module.scss @@ -6,7 +6,14 @@ $pastel-green: #88b04b; $pastel-yellow: #ffdd59; $dark-bg: #2d2d2d; $text-color: #ffffff; -$tooltip-bg: #202020; // Dark background for tooltips +$tooltip-bg: #202020; + +$node-bg-color: #202020; +$node-soft-bg-color: #202020; +$text-color: #ffffff; +$tag-color: #38BDF8; +$region-name: #D6D3D1; +$custom-name: #93C5FD; .RootCustomNode { display: flex; diff --git a/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeDefault.tsx b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeDefault.tsx index d7774693..94095a4d 100644 --- a/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeDefault.tsx +++ b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeDefault.tsx @@ -1,12 +1,10 @@ import { memo } from 'react'; -import { Handle, Position } from 'reactflow'; +import { MapSolarSystemType } from '../../map.types'; +import { Handle, Position, NodeProps } from 'reactflow'; import clsx from 'clsx'; - import classes from './SolarSystemNodeDefault.module.scss'; import { PrimeIcons } from 'primereact/api'; - import { useSolarSystemNode } from '../../hooks/useSolarSystemNode'; - import { MARKER_BOOKMARK_BG_STYLES, STATUS_CLASSES, @@ -15,64 +13,36 @@ import { import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/WormholeClassComp'; import { UnsplashedSignature } from '@/hooks/Mapper/components/map/components/UnsplashedSignature'; -export const SolarSystemNodeDefault = memo(props => { - const { - charactersInSystem, - classTitle, - classTitleColor, - customName, - effectName, - hasUserCharacters, - hubs, - visible, - labelCustom, - labelsInfo, - locked, - isShattered, - isThickConnections, - isWormhole, - killsCount, - killsActivityType, - regionClass, - regionName, - status, - selected, - tag, - showHandlers, - systemName, - sortedStatics, - solarSystemId, - unsplashedLeft, - unsplashedRight, - dbClick: handleDbClick, - } = useSolarSystemNode(props); +// eslint-disable-next-line react/display-name +export const SolarSystemNodeDefault = memo((props: NodeProps) => { + const nodeVars = useSolarSystemNode(props); return ( <> - {visible && ( + {nodeVars.visible && (
- {labelCustom !== '' && ( + {nodeVars.labelCustom !== '' && (
- {labelCustom} + {nodeVars.labelCustom}
)} - {isShattered && ( + {nodeVars.isShattered && (
)} - {killsCount && ( -
+ {nodeVars.killsCount && ( +
- {killsCount} + {nodeVars.killsCount}
)} - {labelsInfo.map(x => ( + {nodeVars.labelsInfo.map(x => (
{x.shortName}
@@ -81,19 +51,30 @@ export const SolarSystemNodeDefault = memo(props => { )}
- {visible && ( + {nodeVars.visible && ( <>
-
- {classTitle ?? '-'} +
+ {nodeVars.classTitle ?? '-'}
- {tag != null && tag !== '' && ( -
{tag}
+ {nodeVars.tag != null && nodeVars.tag !== '' && ( +
{nodeVars.tag}
)}
{ '[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] flex-grow overflow-hidden text-ellipsis whitespace-nowrap font-sans', )} > - {systemName} + {nodeVars.systemName}
- {isWormhole && ( + {nodeVars.isWormhole && (
- {sortedStatics.map(whClass => ( + {nodeVars.sortedStatics.map(whClass => ( ))}
)} - {effectName !== null && isWormhole && ( -
+ {nodeVars.effectName !== null && nodeVars.isWormhole && ( +
)}
- {customName && ( + {nodeVars.customName && (
- {customName} + {nodeVars.customName}
)} - {!isWormhole && !customName && ( + {!nodeVars.isWormhole && !nodeVars.customName && (
- {regionName} + {nodeVars.regionName}
)} - {isWormhole && !customName &&
} + {nodeVars.isWormhole && !nodeVars.customName &&
}
- {locked && } + {nodeVars.locked && ( + + )} - {hubs.includes(solarSystemId.toString()) && ( + {nodeVars.hubs.includes(nodeVars.solarSystemId.toString()) && ( )} - {charactersInSystem.length > 0 && ( + {nodeVars.charactersInSystem.length > 0 && (
- {charactersInSystem.length} + {nodeVars.charactersInSystem.length}
)}
@@ -158,19 +141,19 @@ export const SolarSystemNodeDefault = memo(props => { )}
- {visible && ( + {nodeVars.visible && ( <> - {unsplashedLeft.length > 0 && ( + {nodeVars.unsplashedLeft.length > 0 && (
- {unsplashedLeft.map(sig => ( + {nodeVars.unsplashedLeft.map(sig => ( ))}
)} - {unsplashedRight.length > 0 && ( + {nodeVars.unsplashedRight.length > 0 && (
- {unsplashedRight.map(sig => ( + {nodeVars.unsplashedRight.map(sig => ( ))}
@@ -178,44 +161,44 @@ export const SolarSystemNodeDefault = memo(props => { )} -
+
diff --git a/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeTheme.module.scss b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeTheme.module.scss index f940512f..655b9ebc 100644 --- a/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeTheme.module.scss +++ b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeTheme.module.scss @@ -6,7 +6,7 @@ $pastel-green: #88b04b; $pastel-yellow: #ffdd59; $dark-bg: #2d2d2d; $text-color: #ffffff; -$tooltip-bg: #202020; // Dark background for tooltips +$tooltip-bg: #202020; .RootCustomNode { display: flex; @@ -160,6 +160,9 @@ $tooltip-bg: #202020; // Dark background for tooltips z-index: 1; display: flex; left: 4px; + font-family: var(--rf-node-font-family, inherit) !important; + font-weight: var(--rf-node-font-weight, inherit) !important; + & > .Bookmark { min-width: 13px; @@ -254,32 +257,28 @@ $tooltip-bg: #202020; // Dark background for tooltips font-weight: 500; position: relative; top: 1px; + font-family: var(--rf-node-font-family, inherit) !important; + font-weight: var(--rf-node-font-weight, inherit) !important; .classTitle { font-size: 11px; - font-weight: bold; + font-family: var(--rf-node-font-family, inherit) !important; + font-weight: var(--rf-node-font-weight, inherit) !important; text-shadow: 0 0 2px rgb(0 0 0 / 73%); } - .TagTitle { - font-size: 11px; - font-weight: medium; - text-shadow: 0 0 2px rgba(231, 146, 52, 0.73); - - color: var(--rf-tag-color, #38BDF8); - } /* Firefox kostyl */ @-moz-document url-prefix() { .classSystemName { - font-family: inherit !important; - font-weight: bold; + font-family: var(--rf-node-font-family, inherit) !important; + font-weight: var(--rf-node-font-weight, inherit) !important; } } .classSystemName { - font-family: inherit !important; - font-weight: bold; + font-family: var(--rf-node-font-family, inherit) !important; + font-weight: var(--rf-node-font-weight, inherit) !important; } .solarSystemName { @@ -291,22 +290,42 @@ $tooltip-bg: #202020; // Dark background for tooltips justify-content: space-between; align-items: center; height: 19px; + font-family: var(--rf-node-font-family, inherit) !important; + font-weight: var(--rf-node-font-weight, inherit) !important; + + + .tagTitle { + font-size: 11px; + font-weight: medium; + text-shadow: 0 0 2px rgba(231, 146, 52, 0.73); + font-family: var(--rf-node-font-family, inherit) !important; + font-weight: var(--rf-node-font-weight, inherit) !important; + color: var(--rf-tag-color, #38BDF8); + } .regionName { - color: var(--rf-region-name, #D6D3D1) + color: var(--rf-region-name, #D6D3D1); + font-family: var(--rf-node-font-family, inherit) !important; + font-weight: var(--rf-node-font-weight, inherit) !important; } .customName { - color: var(--rf-custom-name, #93C5FD) + color: var(--rf-custom-name, #93C5FD); + font-family: var(--rf-node-font-family, inherit) !important; + font-weight: var(--rf-node-font-weight, inherit) !important; } .localCounter { display: flex; - //align-items: center; + color: var(--rf-has-user-characters, #fbbf24); + font-family: var(--rf-node-font-family, inherit) !important; + font-weight: var(--rf-node-font-weight, inherit) !important; gap: 2px; .hasUserCharacters { color: var(--rf-has-user-characters, #fbbf24); + font-family: var(--rf-node-font-family, inherit) !important; + font-weight: var(--rf-node-font-weight, inherit) !important; } & > i { diff --git a/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeTheme.tsx b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeTheme.tsx index 2fda1cd0..d9283bbd 100644 --- a/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeTheme.tsx +++ b/assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeTheme.tsx @@ -1,12 +1,10 @@ import { memo } from 'react'; -import { Handle, Position } from 'reactflow'; +import { MapSolarSystemType } from '../../map.types'; +import { Handle, Position, NodeProps } from 'reactflow'; import clsx from 'clsx'; - import classes from './SolarSystemNodeTheme.module.scss'; import { PrimeIcons } from 'primereact/api'; - import { useSolarSystemNode } from '../../hooks/useSolarSystemNode'; - import { MARKER_BOOKMARK_BG_STYLES, STATUS_CLASSES, @@ -15,64 +13,35 @@ import { import { WormholeClassComp } from '@/hooks/Mapper/components/map/components/WormholeClassComp'; import { UnsplashedSignature } from '@/hooks/Mapper/components/map/components/UnsplashedSignature'; -export const SolarSystemNodeTheme = memo(props => { - const { - charactersInSystem, - classTitle, - classTitleColor, - customName, - effectName, - hasUserCharacters, - hubs, - visible, - labelCustom, - labelsInfo, - locked, - isShattered, - isThickConnections, - isWormhole, - killsCount, - killsActivityType, - regionClass, - regionName, - status, - selected, - tag, - showHandlers, - systemName, - sortedStatics, - solarSystemId, - unsplashedLeft, - unsplashedRight, - dbClick: handleDbClick, - } = useSolarSystemNode(props); +export const SolarSystemNodeTheme = memo((props: NodeProps) => { + const nodeVars = useSolarSystemNode(props); return ( <> - {visible && ( + {nodeVars.visible && (
- {labelCustom !== '' && ( + {nodeVars.labelCustom !== '' && (
- {labelCustom} + {nodeVars.labelCustom}
)} - {isShattered && ( + {nodeVars.isShattered && (
)} - {killsCount && ( -
+ {nodeVars.killsCount && ( +
- {killsCount} + {nodeVars.killsCount}
)} - {labelsInfo.map(x => ( + {nodeVars.labelsInfo.map(x => (
{x.shortName}
@@ -81,18 +50,31 @@ export const SolarSystemNodeTheme = memo(props => { )}
- {visible && ( + {nodeVars.visible && ( <>
-
- {classTitle ?? '-'} +
+ {nodeVars.classTitle ?? '-'}
- {tag != null && tag !== '' &&
{tag}
} + {nodeVars.tag != null && nodeVars.tag !== '' && ( +
{nodeVars.tag}
+ )}
{ '[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] flex-grow overflow-hidden text-ellipsis whitespace-nowrap', )} > - {systemName} + {nodeVars.systemName}
- {isWormhole && ( + {nodeVars.isWormhole && (
- {sortedStatics.map(whClass => ( + {nodeVars.sortedStatics.map(whClass => ( ))}
)} - {effectName !== null && isWormhole && ( -
+ {nodeVars.effectName !== null && nodeVars.isWormhole && ( +
)}
- {customName && ( + {nodeVars.customName && (
- {customName} + {nodeVars.customName}
)} - {!isWormhole && !customName && ( + {!nodeVars.isWormhole && !nodeVars.customName && (
- {regionName} + {nodeVars.regionName}
)} - {isWormhole && !customName &&
} + {nodeVars.isWormhole && !nodeVars.customName &&
}
- {locked && } + {nodeVars.locked && ( + + )} - {hubs.includes(solarSystemId.toString()) && ( + {nodeVars.hubs.includes(nodeVars.solarSystemId.toString()) && ( )} - {charactersInSystem.length > 0 && ( + {nodeVars.charactersInSystem.length > 0 && (
- {charactersInSystem.length} + {nodeVars.charactersInSystem.length}
)}
@@ -166,19 +150,19 @@ export const SolarSystemNodeTheme = memo(props => { )}
- {visible && ( + {nodeVars.visible && ( <> - {unsplashedLeft.length > 0 && ( + {nodeVars.unsplashedLeft.length > 0 && (
- {unsplashedLeft.map(sig => ( + {nodeVars.unsplashedLeft.map(sig => ( ))}
)} - {unsplashedRight.length > 0 && ( + {nodeVars.unsplashedRight.length > 0 && (
- {unsplashedRight.map(sig => ( + {nodeVars.unsplashedRight.map(sig => ( ))}
@@ -186,44 +170,44 @@ export const SolarSystemNodeTheme = memo(props => { )} -
+
diff --git a/assets/js/hooks/Mapper/components/map/helpers/getThemeBehavior.ts b/assets/js/hooks/Mapper/components/map/helpers/getThemeBehavior.ts new file mode 100644 index 00000000..3cc8f983 --- /dev/null +++ b/assets/js/hooks/Mapper/components/map/helpers/getThemeBehavior.ts @@ -0,0 +1,32 @@ +import { SolarSystemNodeDefault, SolarSystemNodeTheme } from '../components/SolarSystemNode'; +import type { NodeProps } from 'reactflow'; +import type { ComponentType } from 'react'; +import { MapSolarSystemType } from '../map.types'; +import { ConnectionMode } from 'reactflow'; + +export type SolarSystemNodeComponent = ComponentType>; + +interface ThemeBehavior { + isPanAndDrag: boolean; + nodeComponent: SolarSystemNodeComponent; + connectionMode: ConnectionMode; +} + +const THEME_BEHAVIORS: { + [key: string]: ThemeBehavior; +} = { + default: { + isPanAndDrag: false, + nodeComponent: SolarSystemNodeDefault, + connectionMode: ConnectionMode.Loose, + }, + pathfinder: { + isPanAndDrag: true, + nodeComponent: SolarSystemNodeTheme, + connectionMode: ConnectionMode.Loose, + }, +}; + +export function getBehaviorForTheme(themeName: string) { + return THEME_BEHAVIORS[themeName] ?? THEME_BEHAVIORS.default; +} diff --git a/assets/js/hooks/Mapper/components/map/hooks/useSolarSystemNode.ts b/assets/js/hooks/Mapper/components/map/hooks/useSolarSystemNode.ts index 2033b9e6..e87db093 100644 --- a/assets/js/hooks/Mapper/components/map/hooks/useSolarSystemNode.ts +++ b/assets/js/hooks/Mapper/components/map/hooks/useSolarSystemNode.ts @@ -1,5 +1,6 @@ import { useMemo } from 'react'; - +import { MapSolarSystemType } from '../map.types'; +import { NodeProps } from 'reactflow'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api'; import { useMapState } from '@/hooks/Mapper/components/map/MapProvider'; @@ -30,19 +31,9 @@ function sortedLabels(labels: string[]) { return LABELS_ORDER.filter(x => labels.includes(x)).map(x => LABELS_INFO[x]); } -export function useSolarSystemNode(props: any) { - const { data, selected, id } = props; - const { - system_static_info, - system_signatures, - locked, - name, - tag, - status, - labels, - temporary_name, - linked_sig_eve_id: linkedSigEveId = '', - } = data; +export function useSolarSystemNode(props: NodeProps) { + const { id, data, selected } = props; + const { system_static_info, system_signatures, locked, name, tag, status, labels, temporary_name } = data; const { system_class, @@ -57,7 +48,6 @@ export function useSolarSystemNode(props: any) { solar_system_name, } = system_static_info; - // Global map state const { interfaceSettings, data: { systemSignatures: mapSystemSignatures }, @@ -81,11 +71,11 @@ export function useSolarSystemNode(props: any) { visibleNodes, showKSpaceBG, isThickConnections, + linkedSigEveId, }, outCommand, } = useMapState(); - // logic const visible = useMemo(() => visibleNodes.has(id), [id, visibleNodes]); const systemSignatures = useMemo( @@ -174,10 +164,9 @@ export function useSolarSystemNode(props: any) { }, [isShowUnsplashedSignatures, systemSignatures]); const nodeVars = { - // original props id, selected, - // computed + visible, isWormhole, classTitleColor, diff --git a/assets/js/hooks/Mapper/components/map/utils/wrapNode.tsx b/assets/js/hooks/Mapper/components/map/utils/wrapNode.tsx deleted file mode 100644 index 73ba1b01..00000000 --- a/assets/js/hooks/Mapper/components/map/utils/wrapNode.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// wrapNode.ts -import { NodeProps } from 'reactflow'; -import { SolarSystemNodeProps } from '../components/SolarSystemNode'; - -export function wrapNode( - SolarSystemNode: React.FC> -): React.FC> { - return function NodeAdapter(props) { - return ; - }; -} diff --git a/assets/js/hooks/Mapper/types/system.ts b/assets/js/hooks/Mapper/types/system.ts index ba4d45e5..8cd7d023 100644 --- a/assets/js/hooks/Mapper/types/system.ts +++ b/assets/js/hooks/Mapper/types/system.ts @@ -130,4 +130,3 @@ export type SearchSystemItem = { system_static_info: SolarSystemStaticInfoRaw; value: number; }; - diff --git a/assets/static/images/news/01-13-theme-swap/faoble-theme.png b/assets/static/images/news/01-13-theme-swap/faoble-theme.png new file mode 100755 index 00000000..fcca53e4 Binary files /dev/null and b/assets/static/images/news/01-13-theme-swap/faoble-theme.png differ diff --git a/assets/static/images/news/01-13-theme-swap/theme-selector.png b/assets/static/images/news/01-13-theme-swap/theme-selector.png new file mode 100755 index 00000000..c7156959 Binary files /dev/null and b/assets/static/images/news/01-13-theme-swap/theme-selector.png differ diff --git a/priv/posts/2025/01-13-theme-swap.md b/priv/posts/2025/01-13-theme-swap.md new file mode 100644 index 00000000..b9257f50 --- /dev/null +++ b/priv/posts/2025/01-13-theme-swap.md @@ -0,0 +1,138 @@ +%{ +title: "How to Add a Custom Theme", +author: "Wanderer Community", +cover_image_uri: "/images/news/01-13-theme-swap/theme-selector.png", +tags: ~w(themes), +description: "", +} + +--- + +# How to Add a Custom Theme + +Adding a custom theme to your map is a great way to give it a unique look and feel. In this guide, we’ll walk you through the necessary steps to create and enable a brand-new theme, from updating the theme selector to creating custom SCSS files. + +--- + +1. Add Your Theme to the Theme Selector + + +Open the file: + +assets/js/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettings.tsx + + +In this file, you’ll find an array called `THEME_OPTIONS`. Simply add your new theme to this array, giving it both a `label` and a `value`. For example: + +```ts +const THEME_OPTIONS = [ + { label: 'Default', value: 'default' }, + { label: 'Pathfinder', value: 'pathfinder' }, + { label: 'YourTheme', value: 'yourtheme' }, +]; +``` + +This ensures your new theme will appear in the theme selection menu. + +--- + + 2. Create the SCSS File for Your Theme + +Next, you’ll need to create a new SCSS file to define your theme’s custom styles. Navigate to: + +``` +assets/js/hooks/Mapper/components/map/styles +``` + +and add a new file. You can use `pathfinder-theme.scss` as a reference. The **filename must be in the format**: + +``` +yourthemename-theme.scss +``` + +> **Why the specific format?** +> The system looks for theme files following this naming pattern. If you choose a different format, it will not load correctly. + +# 2.1. Define Your CSS Variables + +Inside your theme SCSS file, you can override the variables below to customize colors, backgrounds, patterns, text, and more. For example: + +```scss +// yourtheme-theme.scss + +:root { + /* Main pane background color */ + --rf-bg-color: #222; + --rf-soft-bg-color: #333; + + /* Background pattern settings */ + --rf-bg-variant: lines; + --rf-bg-gap: 10px; + --rf-bg-size: 1px; + --rf-bg-pattern-color: rgba(255, 255, 255, 0.15); + + /* Node (system) appearance */ + --rf-node-bg-color: #444; + --rf-node-soft-bg-color: #555; + --rf-node-font-family: "Roboto", sans-serif; + --rf-text-color: #f5f5f5; + --rf-region-name: #a3e4d7; + --rf-custom-name: #d7bde2; + --rf-tag-color: #e59866; + --rf-has-user-character: #f9e79f; + + /* Eve-specific overrides */ + --eve-effect-nullsec: #ff0000; + --eve-wh-type-color-C1: #aaffaa; + /* ...etc... */ +} +``` + +> **Tip:** Feel free to rename or add new custom variables as necessary, but keep in mind the defaults and naming conventions used throughout the existing code. + +--- + + 3. Customize Node-Related Styles + +If you want to override more specific aspects of the node styling, review the file: + +``` +assets/js/hooks/Mapper/components/map/components/SolarSystemNode/SolarSystemNodeTheme.module.scss +``` + +This file shows which variables are already set up for styling through CSS variables. If the element you want to style already has a variable reference, you can simply override that variable in your SCSS theme file. + +--- + + 4. Update Theme Behavior (Optional) + +Finally, if your theme requires special interactions, you can update the theme behavior in: + +``` +assets/js/hooks/Mapper/components/map/helpers/getThemeBehavior.ts +``` + +By default, some overrides are already set up. For example: + +- `isPanAndDrag: true` sets left-click to select, and right-click to pan. (When `false`, it uses the default behavior) +- `nodeComponent: SolarSystemNodeTheme` specifies a special node component that uses theme CSS overrides -- you could also provide your own node component here +- `connectionMode: ConnectionMode.Loose` allows you to control how strict the connection handles are. + +If your theme needs custom logic—like a different node component or a unique interaction pattern—this is where you’d implement it. + +--- + + Summary + +1. **Add your theme** to `THEME_OPTIONS` in `MapSettings.tsx`. +2. **Create a custom SCSS file** with the pattern `yourtheme-theme.scss` and override any relevant variables. +3. **Check for additional styling** in `SolarSystemNodeTheme.module.scss` to see if there are more elements you’d like to override. +4. (Optional) **Modify the theme behavior** in `getThemeBehavior.ts` if you want your theme to have unique interaction patterns or different default behaviors. + +By following these steps, you’ll be able to quickly add your own themed experience to the map. If you need to make further changes (like adding new variables or hooking into different node components), just follow the same pattern and refer to existing examples in the codebase. Happy theming! + +--- + +### Example of heavily customize node component and theme + +![Faoble Theme]("/images/news/01-13-theme-swap/faoble-theme.png")