Skip to content

Commit d23326f

Browse files
committed
feat: changed text layout foundations and made it more consistent across instances.
1 parent 0ff0144 commit d23326f

6 files changed

Lines changed: 117 additions & 14 deletions

File tree

src/AbstractionCarousel.jsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,8 @@ const AbstractionCarousel = ({
184184
// Store bindings
185185
const nodePrototypesMap = useGraphStore((state) => state.nodePrototypes);
186186
const thingNodeId = useGraphStore((state) => state.thingNodeId);
187-
187+
const textSettings = useGraphStore((state) => state.textSettings);
188+
188189
// Pre-calculate the abstraction chain and base dimensions for each node
189190
const abstractionChainWithDims = useMemo(() => {
190191
if (!selectedNode) return [];
@@ -1197,7 +1198,7 @@ const AbstractionCarousel = ({
11971198
padding: `20px ${(() => {
11981199
const singleLineSidePadding = 22;
11991200
const availableWidth = unscaledWidth - (2 * singleLineSidePadding);
1200-
const averageCharWidth = 12; // Keep consistent with Node.jsx
1201+
const averageCharWidth = 12 * textSettings.fontSize; // Scale with font size
12011202
const charsPerLine = Math.floor(availableWidth / averageCharWidth);
12021203
const isMultiline = (item.name || '').length > charsPerLine;
12031204
return isMultiline ? 30 : 22;
@@ -1209,11 +1210,11 @@ const AbstractionCarousel = ({
12091210
>
12101211
<span
12111212
style={{
1212-
fontSize: '20px',
1213+
fontSize: `${20 * textSettings.fontSize}px`,
12131214
fontWeight: 'bold',
12141215
fontFamily: "'EmOne', sans-serif",
12151216
color: item.textColor || getTextColor(nodeColor),
1216-
lineHeight: '32px',
1217+
lineHeight: `${28 * textSettings.lineSpacing}px`, // Base changed from 32 to 28
12171218
whiteSpace: 'normal',
12181219
overflowWrap: 'break-word',
12191220
wordBreak: 'break-word',

src/Node.jsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ const Node = ({
149149
const hasAnyDefinitions = definitionGraphIds.length > 0;
150150
// Access store state before any memoizations that depend on it
151151
const storeState = useGraphStore();
152+
const textSettings = useGraphStore((state) => state.textSettings);
152153

153154
// Determine display title: prefer current graph title in preview, else node name
154155
const currentGraphName = useMemo(() => {
@@ -182,11 +183,12 @@ const Node = ({
182183
const availableWidth = currentWidth - (2 * singleLineSidePadding);
183184

184185
// Quick character-based estimation (more accurate than previous method)
185-
const averageCharWidth = 12; // From constants
186+
// Account for font size scaling when calculating char width
187+
const averageCharWidth = 12 * textSettings.fontSize; // Scale with font size
186188
const charsPerLine = Math.floor(availableWidth / averageCharWidth);
187189

188190
return displayTitle.length > charsPerLine;
189-
}, [displayTitle, currentWidth, isPreviewing, hasAnyDefinitions]);
191+
}, [displayTitle, currentWidth, isPreviewing, hasAnyDefinitions, textSettings.fontSize]);
190192

191193
// Get the currently displayed graph ID
192194
const currentGraphId = definitionGraphIds[currentDefinitionIndex] || definitionGraphIds[0];
@@ -393,10 +395,10 @@ const Node = ({
393395
<span
394396
className="node-name-text"
395397
style={{
396-
fontSize: '20px',
398+
fontSize: `${20 * textSettings.fontSize}px`,
397399
fontWeight: 'bold',
398400
color: nodeTextColor,
399-
lineHeight: '32px', // Increased line spacing for better readability
401+
lineHeight: `${28 * textSettings.lineSpacing}px`, // Base line height changed from 32 to 28
400402
whiteSpace: 'normal',
401403
overflowWrap: 'break-word',
402404
wordBreak: 'break-word',
@@ -508,7 +510,8 @@ const Node = ({
508510
scaleMode="fit"
509511
minNodeSize={60}
510512
renderContext="decomposition"
511-
nodeFontScale={1.4}
513+
nodeFontScale={1.4 * textSettings.fontSize}
514+
nodeLineHeightScale={textSettings.lineSpacing}
512515
cornerRadiusMultiplier={64}
513516
onNodeHover={(nodeData, isHovering) => {
514517
if (isHovering) {
@@ -612,10 +615,10 @@ const Node = ({
612615
height: '100%',
613616
padding: '4px 8px',
614617
boxSizing: 'border-box',
615-
fontSize: '20px',
618+
fontSize: `${20 * textSettings.fontSize}px`,
616619
color: nodeTextColor,
617620
fontWeight: 'normal',
618-
lineHeight: '24px',
621+
lineHeight: `${24 * textSettings.lineSpacing}px`, // Tighter for description
619622
textAlign: 'center',
620623
wordWrap: 'break-word',
621624
overflowWrap: 'break-word',

src/RedstringMenu.jsx

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,68 @@ const RedstringMenu = ({
648648
<RefreshCw size={14} style={{ marginRight: '8px' }} />
649649
Condense Nodes
650650
</div>
651+
652+
{/* Text Size Submenu */}
653+
<div
654+
className={`submenu-item has-submenu ${isNestedSubmenuOpen('textSize') ? 'active-submenu-parent' : ''}`}
655+
onMouseEnter={() => handleNestedSubmenuItemHover('textSize')}
656+
onMouseLeave={handleNestedSubmenuItemLeave}
657+
style={{ position: 'relative' }}
658+
>
659+
<span>Text Size</span>
660+
<ChevronRight size={14} className="nested-chevron" />
661+
{isNestedSubmenuOpen('textSize') && (
662+
<div
663+
className="submenu-container"
664+
onMouseEnter={handleNestedSubmenuEnter}
665+
onMouseLeave={handleNestedSubmenuItemLeave}
666+
style={{ left: '100%', top: 0 }}
667+
onMouseDown={(e) => {
668+
e.stopPropagation();
669+
setIsInteracting(true);
670+
}}
671+
onMouseUp={(e) => {
672+
e.stopPropagation();
673+
setIsInteracting(false);
674+
}}
675+
onPointerDown={(e) => {
676+
e.stopPropagation();
677+
setIsInteracting(true);
678+
}}
679+
onPointerUp={(e) => {
680+
e.stopPropagation();
681+
setIsInteracting(false);
682+
}}
683+
onTouchStart={(e) => {
684+
e.stopPropagation();
685+
setIsInteracting(true);
686+
}}
687+
onTouchEnd={(e) => {
688+
e.stopPropagation();
689+
setIsInteracting(false);
690+
}}
691+
>
692+
<MaroonSlider
693+
label="Font Size"
694+
value={useGraphStore.getState().textSettings.fontSize}
695+
min={0.7}
696+
max={1.4}
697+
step={0.05}
698+
onChange={(v) => useGraphStore.getState().setTextFontSize(v)}
699+
suffix="x"
700+
/>
701+
<MaroonSlider
702+
label="Line Spacing"
703+
value={useGraphStore.getState().textSettings.lineSpacing}
704+
min={0.7}
705+
max={1.0}
706+
step={0.05}
707+
onChange={(v) => useGraphStore.getState().setTextLineSpacing(v)}
708+
suffix="x"
709+
/>
710+
</div>
711+
)}
712+
</div>
651713
</div>
652714
)}
653715
</div>

src/UniversalNodeRenderer.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ const UniversalNodeRenderer = ({
151151
className = '',
152152
connectionFontScale = 1,
153153
nodeFontScale = 1,
154+
nodeLineHeightScale = 1,
154155
connectionStrokeScale = 1, // Allow manual override of connection stroke width scaling
155156
renderContext = 'full', // 'full' | 'decomposition' | 'preview' - affects stroke/text rendering
156157

@@ -1013,7 +1014,7 @@ const UniversalNodeRenderer = ({
10131014
} else {
10141015
// Full canvas view: use Node.jsx's proportions
10151016
baseFontSize = node.isGroup ? 24 : 20;
1016-
baseLineHeight = node.isGroup ? 28 : 32; // Reduced line height for groups (was 32)
1017+
baseLineHeight = node.isGroup ? 28 : 28; // Default line height (changed from 32 to 28)
10171018
baseVerticalPadding = node.isGroup ? 10 : 20; // Drastically reduced vertical padding for groups (was 18)
10181019
baseSingleLineSidePadding = node.isGroup ? 30 : 22; // Match Node.jsx side padding
10191020
baseMultiLineSidePadding = node.isGroup ? 36 : 30; // Match Node.jsx multiline padding
@@ -1022,7 +1023,7 @@ const UniversalNodeRenderer = ({
10221023

10231024
// Apply transform scale to all measurements
10241025
const computedFontSize = Math.max(8, baseFontSize * transform.scale * nodeFontScale);
1025-
const computedLineHeight = Math.max(10, baseLineHeight * transform.scale * nodeFontScale);
1026+
const computedLineHeight = Math.max(10, baseLineHeight * transform.scale * nodeFontScale * nodeLineHeightScale);
10261027
let verticalPadding = baseVerticalPadding * transform.scale;
10271028
const singleLineSidePadding = baseSingleLineSidePadding * transform.scale;
10281029
const multiLineSidePadding = baseMultiLineSidePadding * transform.scale;

src/constants.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export const NAME_AREA_FACTOR = 0.7; // Determines effective height for name pos
1818
export const EXPANDED_NODE_WIDTH = 300; // Width when image is present
1919
export const AVERAGE_CHAR_WIDTH = 12; // Approx width per char for 20px bold font
2020
export const WRAPPED_NODE_HEIGHT = 110; // Height for text-only nodes when text wraps
21-
export const LINE_HEIGHT_ESTIMATE = 32; // Approx height of one line of text (px)
21+
export const LINE_HEIGHT_ESTIMATE = 28; // Approx height of one line of text (px) - reduced from 32 for tighter spacing
2222

2323
export const EDGE_MARGIN = 75; // Pixels from viewport edge for decomposed view placement
2424

src/store/graphStore.jsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,17 @@ const useGraphStore = create(saveCoordinatorMiddleware((set, get, api) => {
535535
// Connections visualization/layout settings
536536
autoLayoutSettings: getDefaultAutoLayoutSettings(),
537537
forceTunerSettings: getDefaultForceTunerSettings(),
538+
// Text appearance settings
539+
textSettings: (() => {
540+
try {
541+
return {
542+
fontSize: parseFloat(localStorage.getItem('redstring_text_font_size')) || 1.0,
543+
lineSpacing: parseFloat(localStorage.getItem('redstring_text_line_spacing')) || 1.0,
544+
};
545+
} catch (_) {
546+
return { fontSize: 1.0, lineSpacing: 1.0 };
547+
}
548+
})(),
538549

539550
// Git Federation State
540551
gitConnection: (() => {
@@ -2648,6 +2659,31 @@ const useGraphStore = create(saveCoordinatorMiddleware((set, get, api) => {
26482659
draft.autoLayoutSettings.cleanLaneSpacing = clamped;
26492660
})),
26502661

2662+
// Text appearance settings
2663+
setTextFontSize: (value) => set(produce((draft) => {
2664+
const v = Number(value);
2665+
if (!Number.isFinite(v) || v < 0.7 || v > 1.4) {
2666+
console.warn(`[setTextFontSize] Invalid value: ${value}`);
2667+
return;
2668+
}
2669+
draft.textSettings.fontSize = v;
2670+
try {
2671+
localStorage.setItem('redstring_text_font_size', String(v));
2672+
} catch (_) {}
2673+
})),
2674+
2675+
setTextLineSpacing: (value) => set(produce((draft) => {
2676+
const v = Number(value);
2677+
if (!Number.isFinite(v) || v < 0.7 || v > 1.0) {
2678+
console.warn(`[setTextLineSpacing] Invalid value: ${value}`);
2679+
return;
2680+
}
2681+
draft.textSettings.lineSpacing = v;
2682+
try {
2683+
localStorage.setItem('redstring_text_line_spacing', String(v));
2684+
} catch (_) {}
2685+
})),
2686+
26512687
setLayoutScalePreset: (preset) => set(produce((draft) => {
26522688
if (!draft.autoLayoutSettings) {
26532689
draft.autoLayoutSettings = getDefaultAutoLayoutSettings();

0 commit comments

Comments
 (0)