mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-12-12 02:35:42 +00:00
Merge pull request #117 from guarzo/guarzo/import
Clean-up theme swap logic for nodes
This commit is contained in:
@@ -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<Node<SolarSystemRawType>>(initialNodes);
|
||||
const [edges, , onEdgesChange] = useEdgesState<Edge<SolarSystemConnection>>(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
|
||||
|
||||
@@ -9,6 +9,7 @@ export type MapData = MapUnionTypes & {
|
||||
visibleNodes: Set<string>;
|
||||
showKSpaceBG: boolean;
|
||||
isThickConnections: boolean;
|
||||
linkedSigEveId: string;
|
||||
};
|
||||
|
||||
interface MapProviderProps {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<MapSolarSystemType>) => {
|
||||
const nodeVars = useSolarSystemNode(props);
|
||||
|
||||
return (
|
||||
<>
|
||||
{visible && (
|
||||
{nodeVars.visible && (
|
||||
<div className={classes.Bookmarks}>
|
||||
{labelCustom !== '' && (
|
||||
{nodeVars.labelCustom !== '' && (
|
||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.custom)}>
|
||||
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] ">{labelCustom}</span>
|
||||
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] ">{nodeVars.labelCustom}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isShattered && (
|
||||
{nodeVars.isShattered && (
|
||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.shattered)}>
|
||||
<span className={clsx('pi pi-chart-pie', classes.icon)} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{killsCount && (
|
||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[killsActivityType!])}>
|
||||
{nodeVars.killsCount && (
|
||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[nodeVars.killsActivityType!])}>
|
||||
<div className={clsx(classes.BookmarkWithIcon)}>
|
||||
<span className={clsx(PrimeIcons.BOLT, classes.icon)} />
|
||||
<span className={clsx(classes.text)}>{killsCount}</span>
|
||||
<span className={clsx(classes.text)}>{nodeVars.killsCount}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{labelsInfo.map(x => (
|
||||
{nodeVars.labelsInfo.map(x => (
|
||||
<div key={x.id} className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[x.id])}>
|
||||
{x.shortName}
|
||||
</div>
|
||||
@@ -81,19 +51,30 @@ export const SolarSystemNodeDefault = memo(props => {
|
||||
)}
|
||||
|
||||
<div
|
||||
className={clsx(classes.RootCustomNode, regionClass && classes[regionClass], classes[STATUS_CLASSES[status]], {
|
||||
[classes.selected]: selected,
|
||||
})}
|
||||
className={clsx(
|
||||
classes.RootCustomNode,
|
||||
nodeVars.regionClass && classes[nodeVars.regionClass],
|
||||
classes[STATUS_CLASSES[nodeVars.status]],
|
||||
{
|
||||
[classes.selected]: nodeVars.selected,
|
||||
},
|
||||
)}
|
||||
>
|
||||
{visible && (
|
||||
{nodeVars.visible && (
|
||||
<>
|
||||
<div className={classes.HeadRow}>
|
||||
<div className={clsx(classes.classTitle, classTitleColor, '[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]')}>
|
||||
{classTitle ?? '-'}
|
||||
<div
|
||||
className={clsx(
|
||||
classes.classTitle,
|
||||
nodeVars.classTitleColor,
|
||||
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]',
|
||||
)}
|
||||
>
|
||||
{nodeVars.classTitle ?? '-'}
|
||||
</div>
|
||||
|
||||
{tag != null && tag !== '' && (
|
||||
<div className={clsx(classes.TagTitle, 'text-sky-400 font-medium')}>{tag}</div>
|
||||
{nodeVars.tag != null && nodeVars.tag !== '' && (
|
||||
<div className={clsx(classes.TagTitle, 'text-sky-400 font-medium')}>{nodeVars.tag}</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
@@ -102,53 +83,55 @@ export const SolarSystemNodeDefault = memo(props => {
|
||||
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] flex-grow overflow-hidden text-ellipsis whitespace-nowrap font-sans',
|
||||
)}
|
||||
>
|
||||
{systemName}
|
||||
{nodeVars.systemName}
|
||||
</div>
|
||||
|
||||
{isWormhole && (
|
||||
{nodeVars.isWormhole && (
|
||||
<div className={classes.statics}>
|
||||
{sortedStatics.map(whClass => (
|
||||
{nodeVars.sortedStatics.map(whClass => (
|
||||
<WormholeClassComp key={whClass} id={whClass} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{effectName !== null && isWormhole && (
|
||||
<div className={clsx(classes.effect, EFFECT_BACKGROUND_STYLES[effectName])} />
|
||||
{nodeVars.effectName !== null && nodeVars.isWormhole && (
|
||||
<div className={clsx(classes.effect, EFFECT_BACKGROUND_STYLES[nodeVars.effectName])} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={clsx(classes.BottomRow, 'flex items-center justify-between')}>
|
||||
{customName && (
|
||||
{nodeVars.customName && (
|
||||
<div className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] text-blue-300 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">
|
||||
{customName}
|
||||
{nodeVars.customName}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isWormhole && !customName && (
|
||||
{!nodeVars.isWormhole && !nodeVars.customName && (
|
||||
<div className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] text-stone-300 whitespace-nowrap overflow-hidden text-ellipsis mr-0.5">
|
||||
{regionName}
|
||||
{nodeVars.regionName}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isWormhole && !customName && <div />}
|
||||
{nodeVars.isWormhole && !nodeVars.customName && <div />}
|
||||
|
||||
<div className="flex items-center justify-end">
|
||||
<div className="flex gap-1 items-center">
|
||||
{locked && <i className={PrimeIcons.LOCK} style={{ fontSize: '0.45rem', fontWeight: 'bold' }} />}
|
||||
{nodeVars.locked && (
|
||||
<i className={PrimeIcons.LOCK} style={{ fontSize: '0.45rem', fontWeight: 'bold' }} />
|
||||
)}
|
||||
|
||||
{hubs.includes(solarSystemId.toString()) && (
|
||||
{nodeVars.hubs.includes(nodeVars.solarSystemId.toString()) && (
|
||||
<i className={PrimeIcons.MAP_MARKER} style={{ fontSize: '0.45rem', fontWeight: 'bold' }} />
|
||||
)}
|
||||
|
||||
{charactersInSystem.length > 0 && (
|
||||
{nodeVars.charactersInSystem.length > 0 && (
|
||||
<div
|
||||
className={clsx(classes.localCounter, {
|
||||
['text-amber-300']: hasUserCharacters,
|
||||
['text-amber-300']: nodeVars.hasUserCharacters,
|
||||
})}
|
||||
>
|
||||
<i className="pi pi-users" style={{ fontSize: '0.50rem' }} />
|
||||
<span className="font-sans">{charactersInSystem.length}</span>
|
||||
<span className="font-sans">{nodeVars.charactersInSystem.length}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -158,19 +141,19 @@ export const SolarSystemNodeDefault = memo(props => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{visible && (
|
||||
{nodeVars.visible && (
|
||||
<>
|
||||
{unsplashedLeft.length > 0 && (
|
||||
{nodeVars.unsplashedLeft.length > 0 && (
|
||||
<div className={classes.Unsplashed}>
|
||||
{unsplashedLeft.map(sig => (
|
||||
{nodeVars.unsplashedLeft.map(sig => (
|
||||
<UnsplashedSignature key={sig.sig_id} signature={sig} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{unsplashedRight.length > 0 && (
|
||||
{nodeVars.unsplashedRight.length > 0 && (
|
||||
<div className={clsx(classes.Unsplashed, classes['Unsplashed--right'])}>
|
||||
{unsplashedRight.map(sig => (
|
||||
{nodeVars.unsplashedRight.map(sig => (
|
||||
<UnsplashedSignature key={sig.sig_id} signature={sig} />
|
||||
))}
|
||||
</div>
|
||||
@@ -178,44 +161,44 @@ export const SolarSystemNodeDefault = memo(props => {
|
||||
</>
|
||||
)}
|
||||
|
||||
<div onMouseDownCapture={handleDbClick} className={classes.Handlers}>
|
||||
<div onMouseDownCapture={nodeVars.dbClick} className={classes.Handlers}>
|
||||
<Handle
|
||||
type="source"
|
||||
className={clsx(classes.Handle, classes.HandleTop, {
|
||||
[classes.selected]: selected,
|
||||
[classes.Tick]: isThickConnections,
|
||||
[classes.selected]: nodeVars.selected,
|
||||
[classes.Tick]: nodeVars.isThickConnections,
|
||||
})}
|
||||
style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
|
||||
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
|
||||
position={Position.Top}
|
||||
id="a"
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
className={clsx(classes.Handle, classes.HandleRight, {
|
||||
[classes.selected]: selected,
|
||||
[classes.Tick]: isThickConnections,
|
||||
[classes.selected]: nodeVars.selected,
|
||||
[classes.Tick]: nodeVars.isThickConnections,
|
||||
})}
|
||||
style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
|
||||
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
|
||||
position={Position.Right}
|
||||
id="b"
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
className={clsx(classes.Handle, classes.HandleBottom, {
|
||||
[classes.selected]: selected,
|
||||
[classes.Tick]: isThickConnections,
|
||||
[classes.selected]: nodeVars.selected,
|
||||
[classes.Tick]: nodeVars.isThickConnections,
|
||||
})}
|
||||
style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
|
||||
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
|
||||
position={Position.Bottom}
|
||||
id="c"
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
className={clsx(classes.Handle, classes.HandleLeft, {
|
||||
[classes.selected]: selected,
|
||||
[classes.Tick]: isThickConnections,
|
||||
[classes.selected]: nodeVars.selected,
|
||||
[classes.Tick]: nodeVars.isThickConnections,
|
||||
})}
|
||||
style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
|
||||
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
|
||||
position={Position.Left}
|
||||
id="d"
|
||||
/>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<MapSolarSystemType>) => {
|
||||
const nodeVars = useSolarSystemNode(props);
|
||||
|
||||
return (
|
||||
<>
|
||||
{visible && (
|
||||
{nodeVars.visible && (
|
||||
<div className={classes.Bookmarks}>
|
||||
{labelCustom !== '' && (
|
||||
{nodeVars.labelCustom !== '' && (
|
||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.custom)}>
|
||||
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] ">{labelCustom}</span>
|
||||
<span className="[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] ">{nodeVars.labelCustom}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isShattered && (
|
||||
{nodeVars.isShattered && (
|
||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES.shattered)}>
|
||||
<span className={clsx('pi pi-chart-pie', classes.icon)} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{killsCount && (
|
||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[killsActivityType!])}>
|
||||
{nodeVars.killsCount && (
|
||||
<div className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[nodeVars.killsActivityType!])}>
|
||||
<div className={clsx(classes.BookmarkWithIcon)}>
|
||||
<span className={clsx(PrimeIcons.BOLT, classes.icon)} />
|
||||
<span className={clsx(classes.text)}>{killsCount}</span>
|
||||
<span className={clsx(classes.text)}>{nodeVars.killsCount}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{labelsInfo.map(x => (
|
||||
{nodeVars.labelsInfo.map(x => (
|
||||
<div key={x.id} className={clsx(classes.Bookmark, MARKER_BOOKMARK_BG_STYLES[x.id])}>
|
||||
{x.shortName}
|
||||
</div>
|
||||
@@ -81,18 +50,31 @@ export const SolarSystemNodeTheme = memo(props => {
|
||||
)}
|
||||
|
||||
<div
|
||||
className={clsx(classes.RootCustomNode, regionClass && classes[regionClass], classes[STATUS_CLASSES[status]], {
|
||||
[classes.selected]: selected,
|
||||
})}
|
||||
className={clsx(
|
||||
classes.RootCustomNode,
|
||||
nodeVars.regionClass && classes[nodeVars.regionClass],
|
||||
classes[STATUS_CLASSES[nodeVars.status]],
|
||||
{
|
||||
[classes.selected]: nodeVars.selected,
|
||||
},
|
||||
)}
|
||||
>
|
||||
{visible && (
|
||||
{nodeVars.visible && (
|
||||
<>
|
||||
<div className={classes.HeadRow}>
|
||||
<div className={clsx(classes.classTitle, classTitleColor, '[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]')}>
|
||||
{classTitle ?? '-'}
|
||||
<div
|
||||
className={clsx(
|
||||
classes.classTitle,
|
||||
nodeVars.classTitleColor,
|
||||
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)]',
|
||||
)}
|
||||
>
|
||||
{nodeVars.classTitle ?? '-'}
|
||||
</div>
|
||||
|
||||
{tag != null && tag !== '' && <div className={clsx(classes.TagTitle)}>{tag}</div>}
|
||||
{nodeVars.tag != null && nodeVars.tag !== '' && (
|
||||
<div className={clsx(classes.TagTitle)}>{nodeVars.tag}</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={clsx(
|
||||
@@ -100,63 +82,65 @@ export const SolarSystemNodeTheme = memo(props => {
|
||||
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] flex-grow overflow-hidden text-ellipsis whitespace-nowrap',
|
||||
)}
|
||||
>
|
||||
{systemName}
|
||||
{nodeVars.systemName}
|
||||
</div>
|
||||
|
||||
{isWormhole && (
|
||||
{nodeVars.isWormhole && (
|
||||
<div className={classes.statics}>
|
||||
{sortedStatics.map(whClass => (
|
||||
{nodeVars.sortedStatics.map(whClass => (
|
||||
<WormholeClassComp key={whClass} id={whClass} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{effectName !== null && isWormhole && (
|
||||
<div className={clsx(classes.effect, EFFECT_BACKGROUND_STYLES[effectName])} />
|
||||
{nodeVars.effectName !== null && nodeVars.isWormhole && (
|
||||
<div className={clsx(classes.effect, EFFECT_BACKGROUND_STYLES[nodeVars.effectName])} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={clsx(classes.BottomRow, 'flex items-center justify-between')}>
|
||||
{customName && (
|
||||
{nodeVars.customName && (
|
||||
<div
|
||||
className={clsx(
|
||||
classes.CustomName,
|
||||
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] whitespace-nowrap overflow-hidden text-ellipsis mr-0.5',
|
||||
)}
|
||||
>
|
||||
{customName}
|
||||
{nodeVars.customName}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isWormhole && !customName && (
|
||||
{!nodeVars.isWormhole && !nodeVars.customName && (
|
||||
<div
|
||||
className={clsx(
|
||||
classes.RegionName,
|
||||
'[text-shadow:_0_1px_0_rgb(0_0_0_/_40%)] whitespace-nowrap overflow-hidden text-ellipsis mr-0.5',
|
||||
)}
|
||||
>
|
||||
{regionName}
|
||||
{nodeVars.regionName}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isWormhole && !customName && <div />}
|
||||
{nodeVars.isWormhole && !nodeVars.customName && <div />}
|
||||
|
||||
<div className="flex items-center justify-end">
|
||||
<div className="flex gap-1 items-center">
|
||||
{locked && <i className={PrimeIcons.LOCK} style={{ fontSize: '0.45rem', fontWeight: 'bold' }} />}
|
||||
{nodeVars.locked && (
|
||||
<i className={PrimeIcons.LOCK} style={{ fontSize: '0.45rem', fontWeight: 'bold' }} />
|
||||
)}
|
||||
|
||||
{hubs.includes(solarSystemId.toString()) && (
|
||||
{nodeVars.hubs.includes(nodeVars.solarSystemId.toString()) && (
|
||||
<i className={PrimeIcons.MAP_MARKER} style={{ fontSize: '0.45rem', fontWeight: 'bold' }} />
|
||||
)}
|
||||
|
||||
{charactersInSystem.length > 0 && (
|
||||
{nodeVars.charactersInSystem.length > 0 && (
|
||||
<div
|
||||
className={clsx(classes.localCounter, {
|
||||
[classes.hasUserCharacters]: hasUserCharacters,
|
||||
[classes.hasUserCharacters]: nodeVars.hasUserCharacters,
|
||||
})}
|
||||
>
|
||||
<i className="pi pi-users" style={{ fontSize: '0.50rem' }} />
|
||||
<span className="font-sans">{charactersInSystem.length}</span>
|
||||
<span className="font-sans">{nodeVars.charactersInSystem.length}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -166,19 +150,19 @@ export const SolarSystemNodeTheme = memo(props => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{visible && (
|
||||
{nodeVars.visible && (
|
||||
<>
|
||||
{unsplashedLeft.length > 0 && (
|
||||
{nodeVars.unsplashedLeft.length > 0 && (
|
||||
<div className={classes.Unsplashed}>
|
||||
{unsplashedLeft.map(sig => (
|
||||
{nodeVars.unsplashedLeft.map(sig => (
|
||||
<UnsplashedSignature key={sig.sig_id} signature={sig} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{unsplashedRight.length > 0 && (
|
||||
{nodeVars.unsplashedRight.length > 0 && (
|
||||
<div className={clsx(classes.Unsplashed, classes['Unsplashed--right'])}>
|
||||
{unsplashedRight.map(sig => (
|
||||
{nodeVars.unsplashedRight.map(sig => (
|
||||
<UnsplashedSignature key={sig.sig_id} signature={sig} />
|
||||
))}
|
||||
</div>
|
||||
@@ -186,44 +170,44 @@ export const SolarSystemNodeTheme = memo(props => {
|
||||
</>
|
||||
)}
|
||||
|
||||
<div onMouseDownCapture={handleDbClick} className={classes.Handlers}>
|
||||
<div onMouseDownCapture={nodeVars.dbClick} className={classes.Handlers}>
|
||||
<Handle
|
||||
type="source"
|
||||
className={clsx(classes.Handle, classes.HandleTop, {
|
||||
[classes.selected]: selected,
|
||||
[classes.Tick]: isThickConnections,
|
||||
[classes.selected]: nodeVars.selected,
|
||||
[classes.Tick]: nodeVars.isThickConnections,
|
||||
})}
|
||||
style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
|
||||
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
|
||||
position={Position.Top}
|
||||
id="a"
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
className={clsx(classes.Handle, classes.HandleRight, {
|
||||
[classes.selected]: selected,
|
||||
[classes.Tick]: isThickConnections,
|
||||
[classes.selected]: nodeVars.selected,
|
||||
[classes.Tick]: nodeVars.isThickConnections,
|
||||
})}
|
||||
style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
|
||||
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
|
||||
position={Position.Right}
|
||||
id="b"
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
className={clsx(classes.Handle, classes.HandleBottom, {
|
||||
[classes.selected]: selected,
|
||||
[classes.Tick]: isThickConnections,
|
||||
[classes.selected]: nodeVars.selected,
|
||||
[classes.Tick]: nodeVars.isThickConnections,
|
||||
})}
|
||||
style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
|
||||
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
|
||||
position={Position.Bottom}
|
||||
id="c"
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
className={clsx(classes.Handle, classes.HandleLeft, {
|
||||
[classes.selected]: selected,
|
||||
[classes.Tick]: isThickConnections,
|
||||
[classes.selected]: nodeVars.selected,
|
||||
[classes.Tick]: nodeVars.isThickConnections,
|
||||
})}
|
||||
style={{ visibility: showHandlers ? 'visible' : 'hidden' }}
|
||||
style={{ visibility: nodeVars.showHandlers ? 'visible' : 'hidden' }}
|
||||
position={Position.Left}
|
||||
id="d"
|
||||
/>
|
||||
|
||||
@@ -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<NodeProps<MapSolarSystemType>>;
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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<MapSolarSystemType>) {
|
||||
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,
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
// wrapNode.ts
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { SolarSystemNodeProps } from '../components/SolarSystemNode';
|
||||
|
||||
export function wrapNode<T>(
|
||||
SolarSystemNode: React.FC<SolarSystemNodeProps<T>>
|
||||
): React.FC<NodeProps<T>> {
|
||||
return function NodeAdapter(props) {
|
||||
return <SolarSystemNode {...props} />;
|
||||
};
|
||||
}
|
||||
@@ -130,4 +130,3 @@ export type SearchSystemItem = {
|
||||
system_static_info: SolarSystemStaticInfoRaw;
|
||||
value: number;
|
||||
};
|
||||
|
||||
|
||||
BIN
assets/static/images/news/01-13-theme-swap/faoble-theme.png
Executable file
BIN
assets/static/images/news/01-13-theme-swap/faoble-theme.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
BIN
assets/static/images/news/01-13-theme-swap/theme-selector.png
Executable file
BIN
assets/static/images/news/01-13-theme-swap/theme-selector.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
138
priv/posts/2025/01-13-theme-swap.md
Normal file
138
priv/posts/2025/01-13-theme-swap.md
Normal file
@@ -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
|
||||
|
||||

|
||||
Reference in New Issue
Block a user