Skip to content

Commit e895760

Browse files
committed
mini improvements
Minimap and pan controls Smaller Ribbon swtiched back to previous for performance Navbar double glass edge landing page hero changes
1 parent 37c736b commit e895760

17 files changed

+809
-335
lines changed

app/chromoviz/chromosome-synteny.tsx

+116-78
Original file line numberDiff line numberDiff line change
@@ -128,44 +128,34 @@ const AlignmentFilterButton = ({
128128
currentFilter,
129129
onClick,
130130
icon: Icon,
131-
label,
132-
iconOnly = false
131+
label
133132
}: {
134133
filter: 'all' | 'forward' | 'reverse';
135134
currentFilter: 'all' | 'forward' | 'reverse';
136135
onClick: (filter: 'all' | 'forward' | 'reverse') => void;
137136
icon: any;
138137
label: string;
139-
iconOnly?: boolean;
140138
}) => {
141139
return (
142140
<button
143141
onClick={() => onClick(filter)}
144142
className={cn(
145-
'group relative inline-flex h-8 items-center justify-center overflow-hidden rounded-md',
143+
'group relative inline-flex h-7 items-center justify-center overflow-hidden rounded-md',
146144
'bg-background border transition-all duration-200',
147145
filter === currentFilter
148-
? 'border-primary text-primary'
149-
: 'border-border hover:border-primary/50 text-muted-foreground',
150-
iconOnly
151-
? 'w-8'
152-
: filter === currentFilter
153-
? 'w-24'
154-
: 'w-8 hover:w-24'
146+
? 'border-primary text-primary w-24'
147+
: 'border-border hover:border-primary/50 text-muted-foreground w-7 hover:w-24'
155148
)}
156149
>
157-
{!iconOnly && (
158-
<div className={cn(
159-
'inline-flex whitespace-nowrap transition-all duration-200',
160-
filter === currentFilter ? '-translate-x-2 opacity-100' : 'opacity-0 group-hover:-translate-x-2 group-hover:opacity-100'
161-
)}>
162-
{label}
163-
</div>
164-
)}
165150
<div className={cn(
166-
"transition-all duration-200",
167-
iconOnly ? "static" : "absolute right-2"
151+
'inline-flex whitespace-nowrap transition-all duration-200',
152+
filter === currentFilter
153+
? '-translate-x-2 opacity-100'
154+
: 'opacity-0 group-hover:-translate-x-2 group-hover:opacity-100'
168155
)}>
156+
{label}
157+
</div>
158+
<div className="absolute right-2">
169159
<Icon className="h-4 w-4" />
170160
</div>
171161
</button>
@@ -257,6 +247,8 @@ const ChromosomeScrollbar = ({
257247
}, [containerRef, svgRef]);
258248

259249
const handleThumbMouseDown = useCallback((e: React.MouseEvent) => {
250+
e.preventDefault(); // Prevent default selection
251+
e.stopPropagation(); // Stop event propagation
260252
setIsDragging(true);
261253
setStartX(e.clientX - scrollThumbRef.current!.offsetLeft);
262254
}, []);
@@ -265,6 +257,7 @@ const ChromosomeScrollbar = ({
265257
if (!isDragging || !scrollTrackRef.current || !scrollThumbRef.current || !svgRef.current) return;
266258

267259
e.preventDefault();
260+
e.stopPropagation();
268261
const trackRect = scrollTrackRef.current.getBoundingClientRect();
269262
const thumbWidth = scrollThumbRef.current.clientWidth;
270263
const x = e.clientX - trackRect.left - startX;
@@ -304,31 +297,47 @@ const ChromosomeScrollbar = ({
304297
}, [isDragging, handleMouseMove, handleMouseUp]);
305298

306299
return (
307-
<div className="absolute bottom-4 left-4 right-4 h-6">
300+
<div
301+
className="absolute bottom-4 left-4 right-4 h-6 select-none"
302+
onMouseDown={(e) => e.stopPropagation()} // Prevent selection events
303+
>
308304
<div
309305
ref={scrollTrackRef}
310-
className="relative w-full h-2 bg-muted rounded-full"
306+
className="relative w-full h-2 bg-muted rounded-full select-none"
307+
onMouseDown={(e) => e.stopPropagation()} // Prevent selection events
311308
>
312309
<div
313310
ref={scrollThumbRef}
314311
style={{
315312
width: `${getThumbWidth()}px`,
316-
left: scrollLeft
313+
left: scrollLeft,
314+
userSelect: 'none', // Prevent text selection
315+
WebkitUserSelect: 'none',
316+
MozUserSelect: 'none',
317+
msUserSelect: 'none'
317318
}}
318319
className={cn(
319320
"absolute top-0 h-full rounded-full bg-primary/50",
320321
"cursor-grab hover:bg-primary/70 transition-colors",
322+
"select-none touch-none", // Prevent selection and touch events
321323
isDragging && "cursor-grabbing bg-primary"
322324
)}
323325
onMouseDown={handleThumbMouseDown}
326+
onDragStart={(e) => e.preventDefault()} // Prevent drag events
324327
>
325328
{/* Thumbnail preview */}
326-
<div className="absolute -top-20 left-0 w-40 h-16 bg-background border rounded-lg overflow-hidden opacity-0 group-hover:opacity-100 transition-opacity">
329+
<div
330+
className={cn(
331+
"absolute -top-20 left-0 w-40 h-16 bg-background border rounded-lg overflow-hidden",
332+
"opacity-0 group-hover:opacity-100 transition-opacity select-none pointer-events-none"
333+
)}
334+
>
327335
<svg
328336
width="100%"
329337
height="100%"
330338
viewBox={`0 0 ${width} ${height}`}
331339
preserveAspectRatio="xMidYMid meet"
340+
className="select-none pointer-events-none"
332341
>
333342
{/* Miniature version of chromosomes */}
334343
</svg>
@@ -1204,38 +1213,87 @@ export function ChromosomeSynteny({
12041213
if (!svgRef.current) return;
12051214

12061215
try {
1207-
// Get SVG content
12081216
const svgElement = svgRef.current;
1209-
const svgData = new XMLSerializer().serializeToString(svgElement);
1217+
const clone = svgElement.cloneNode(true) as SVGSVGElement;
1218+
const bbox = svgElement.getBBox();
1219+
1220+
// Add padding (50px on each side)
1221+
const padding = 50;
1222+
const totalWidth = bbox.width + (padding * 2);
1223+
const totalHeight = bbox.height + (padding * 2) + 30; // Extra 30px for credits
1224+
1225+
// Check dark mode once at the beginning
1226+
const isDarkMode = document.documentElement.classList.contains('dark');
1227+
1228+
// Update clone dimensions with padding
1229+
clone.setAttribute('width', `${totalWidth}`);
1230+
clone.setAttribute('height', `${totalHeight}`);
1231+
clone.setAttribute('viewBox', `${bbox.x - padding} ${bbox.y - padding} ${totalWidth} ${totalHeight}`);
1232+
1233+
// Inline styles
1234+
const styles = document.styleSheets;
1235+
let stylesText = '';
1236+
for (let i = 0; i < styles.length; i++) {
1237+
try {
1238+
const rules = styles[i].cssRules || styles[i].rules;
1239+
for (let j = 0; j < rules.length; j++) {
1240+
stylesText += rules[j].cssText + '\n';
1241+
}
1242+
} catch (e) {
1243+
console.warn('Could not read styles', e);
1244+
}
1245+
}
1246+
1247+
// Add styles with dark mode consideration
1248+
const styleElement = document.createElement('style');
1249+
styleElement.textContent = `
1250+
${stylesText}
1251+
${isDarkMode ? `
1252+
text, .text-foreground {
1253+
fill: #ffffff !important;
1254+
color: #ffffff !important;
1255+
}
1256+
.text-muted-foreground {
1257+
fill: #a1a1aa !important;
1258+
color: #a1a1aa !important;
1259+
}
1260+
` : ''}
1261+
`;
1262+
clone.insertBefore(styleElement, clone.firstChild);
1263+
1264+
const svgData = new XMLSerializer().serializeToString(clone);
1265+
const svgBlob = new Blob([
1266+
'<?xml version="1.0" standalone="no"?>\r\n',
1267+
svgData
1268+
], { type: 'image/svg+xml;charset=utf-8' });
12101269

1211-
// Create canvas
12121270
const canvas = document.createElement('canvas');
12131271
const ctx = canvas.getContext('2d');
12141272
if (!ctx) throw new Error('Could not get canvas context');
12151273

1216-
// Set canvas size to match SVG
1217-
const svgSize = svgElement.getBoundingClientRect();
1218-
canvas.width = svgSize.width * 2; // 2x for better quality
1219-
canvas.height = svgSize.height * 2;
1274+
const scale2x = 2;
1275+
canvas.width = totalWidth * scale2x;
1276+
canvas.height = totalHeight * scale2x;
12201277

1221-
// Set background color based on theme
1222-
const isDarkMode = document.documentElement.classList.contains('dark');
1278+
// Use the same isDarkMode value for background
12231279
ctx.fillStyle = isDarkMode ? '#020817' : '#ffffff';
12241280
ctx.fillRect(0, 0, canvas.width, canvas.height);
12251281

1226-
// Create image from SVG
1227-
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
1228-
const url = URL.createObjectURL(blob);
1229-
1230-
const img = new window.Image();
1231-
img.src = url;
1282+
const url = URL.createObjectURL(svgBlob);
1283+
const img = document.createElement('img') as HTMLImageElement;
12321284

12331285
await new Promise((resolve, reject) => {
12341286
img.onload = () => {
1235-
ctx.scale(2, 2); // Scale up for better quality
1236-
ctx.drawImage(img, 0, 0);
1287+
ctx.scale(scale2x, scale2x);
1288+
ctx.drawImage(img, 0, 0, totalWidth, totalHeight);
1289+
1290+
// Add credits with same isDarkMode value
1291+
ctx.scale(0.5, 0.5);
1292+
ctx.fillStyle = isDarkMode ? '#a1a1aa' : '#94a3b8';
1293+
ctx.font = '24px system-ui, sans-serif';
1294+
ctx.textAlign = 'right';
1295+
ctx.fillText('© 2024 CHITRA', totalWidth * 2 - 20, totalHeight * 2 - 20);
12371296

1238-
// Convert to desired format
12391297
const mimeType = format === 'png' ? 'image/png' : 'image/jpeg';
12401298
const quality = format === 'png' ? 1 : 0.95;
12411299

@@ -1245,23 +1303,20 @@ export function ChromosomeSynteny({
12451303
return;
12461304
}
12471305

1248-
// Create download link
12491306
const downloadLink = document.createElement('a');
12501307
downloadLink.href = URL.createObjectURL(blob);
1251-
downloadLink.download = `chromoviz-overview-${new Date().toISOString().split('T')[0]}.${format}`;
1308+
downloadLink.download = `chromoviz-full-${new Date().toISOString().split('T')[0]}.${format}`;
12521309
document.body.appendChild(downloadLink);
12531310
downloadLink.click();
12541311
document.body.removeChild(downloadLink);
12551312

1256-
// Cleanup
12571313
URL.revokeObjectURL(downloadLink.href);
12581314
URL.revokeObjectURL(url);
12591315
resolve(true);
12601316
}, mimeType, quality);
12611317
};
1262-
img.onerror = () => {
1263-
reject(new Error('Failed to load image'));
1264-
};
1318+
img.onerror = () => reject(new Error('Failed to load image'));
1319+
img.src = url;
12651320
});
12661321
} catch (error) {
12671322
console.error('Error exporting image:', error);
@@ -1298,45 +1353,28 @@ export function ChromosomeSynteny({
12981353
<div className="flex items-center gap-2">
12991354
{/* Show full controls on larger screens */}
13001355
<div className="hidden md:flex items-center gap-2">
1301-
<div className="flex items-center gap-1">
1356+
<div className="flex items-center gap-1.5">
13021357
<AlignmentFilterButton
13031358
filter="all"
13041359
currentFilter={alignmentFilter}
13051360
onClick={setAlignmentFilter}
13061361
icon={ArrowLeftRight}
13071362
label="All"
1308-
iconOnly={true}
13091363
/>
13101364
<AlignmentFilterButton
13111365
filter="forward"
13121366
currentFilter={alignmentFilter}
13131367
onClick={setAlignmentFilter}
13141368
icon={ArrowRight}
13151369
label="Forward"
1316-
iconOnly={true}
13171370
/>
13181371
<AlignmentFilterButton
13191372
filter="reverse"
13201373
currentFilter={alignmentFilter}
13211374
onClick={setAlignmentFilter}
13221375
icon={ArrowLeft}
13231376
label="Reverse"
1324-
iconOnly={true}
1325-
/>
1326-
</div>
1327-
1328-
<Separator orientation="vertical" className="h-6" />
1329-
1330-
<div className="flex items-center gap-1.5">
1331-
<Switch
1332-
id="annotations-mode"
1333-
checked={showAnnotations}
1334-
onCheckedChange={setShowAnnotations}
1335-
className="scale-75"
13361377
/>
1337-
<Label htmlFor="annotations-mode" className="text-xs text-muted-foreground">
1338-
Annotations
1339-
</Label>
13401378
</div>
13411379
</div>
13421380

@@ -1419,7 +1457,7 @@ export function ChromosomeSynteny({
14191457
</div>
14201458

14211459
{/* Main Content Area */}
1422-
<div className="relative flex-1 h-[calc(100%)]">
1460+
<div className="relative flex-1 h-[calc(100vh-6rem)]">
14231461
<div className="w-full h-full">
14241462
<svg
14251463
ref={svgRef}
@@ -1431,7 +1469,7 @@ export function ChromosomeSynteny({
14311469
preserveAspectRatio="xMidYMid meet"
14321470
/>
14331471

1434-
<div className="absolute bottom-20 right-4 z-20">
1472+
<div className="absolute bottom-16 right-4 z-20 hidden md:block scale-90">
14351473
<MiniMap
14361474
mainSvgRef={svgRef}
14371475
zoomBehaviorRef={zoomBehaviorRef}
@@ -1455,7 +1493,7 @@ export function ChromosomeSynteny({
14551493
</>
14561494
)}
14571495

1458-
<div className="absolute left-4 bottom-20 z-20">
1496+
<div className="absolute left-4 bottom-16 z-20 hidden md:block scale-90">
14591497
<div className="inline-grid w-fit grid-cols-3 gap-1">
14601498
<div></div>
14611499
<Button
@@ -1468,11 +1506,11 @@ export function ChromosomeSynteny({
14681506
onTouchStart={() => startContinuousPan('up')}
14691507
onTouchEnd={stopContinuousPan}
14701508
className={cn(
1471-
"h-8 w-8 transition-colors",
1509+
"h-7 w-7 transition-colors",
14721510
isPanningRef.current && "bg-blue-500/10 border-blue-500/50"
14731511
)}
14741512
>
1475-
<ChevronUp className="h-4 w-4" />
1513+
<ChevronUp className="h-3.5 w-3.5" />
14761514
</Button>
14771515
<div></div>
14781516
<Button
@@ -1485,11 +1523,11 @@ export function ChromosomeSynteny({
14851523
onTouchStart={() => startContinuousPan('left')}
14861524
onTouchEnd={stopContinuousPan}
14871525
className={cn(
1488-
"h-8 w-8 transition-colors",
1526+
"h-7 w-7 transition-colors",
14891527
isPanningRef.current && "bg-blue-500/10 border-blue-500/50"
14901528
)}
14911529
>
1492-
<ChevronLeft className="h-4 w-4" />
1530+
<ChevronLeft className="h-3.5 w-3.5" />
14931531
</Button>
14941532
<div className="flex items-center justify-center">
14951533
<Circle className="h-4 w-4 opacity-60" />
@@ -1504,11 +1542,11 @@ export function ChromosomeSynteny({
15041542
onTouchStart={() => startContinuousPan('right')}
15051543
onTouchEnd={stopContinuousPan}
15061544
className={cn(
1507-
"h-8 w-8 transition-colors",
1545+
"h-7 w-7 transition-colors",
15081546
isPanningRef.current && "bg-blue-500/10 border-blue-500/50"
15091547
)}
15101548
>
1511-
<ChevronRight className="h-4 w-4" />
1549+
<ChevronRight className="h-3.5 w-3.5" />
15121550
</Button>
15131551
<div></div>
15141552
<Button
@@ -1521,11 +1559,11 @@ export function ChromosomeSynteny({
15211559
onTouchStart={() => startContinuousPan('down')}
15221560
onTouchEnd={stopContinuousPan}
15231561
className={cn(
1524-
"h-8 w-8 transition-colors",
1562+
"h-7 w-7 transition-colors",
15251563
isPanningRef.current && "bg-blue-500/10 border-blue-500/50"
15261564
)}
15271565
>
1528-
<ChevronDown className="h-4 w-4" />
1566+
<ChevronDown className="h-3.5 w-3.5" />
15291567
</Button>
15301568
<div></div>
15311569
</div>

0 commit comments

Comments
 (0)