492 lines
21 KiB
TypeScript
492 lines
21 KiB
TypeScript
"use client"
|
|
|
|
import { useState, useEffect } from "react"
|
|
import TerminalVisualizer from "@/components/terminal-visualizer"
|
|
|
|
// Types for the layout tree
|
|
type PaneNode = {
|
|
type: "pane"
|
|
id: number
|
|
}
|
|
|
|
type SplitNode = {
|
|
type: "split-h" | "split-v"
|
|
ratio: number
|
|
children: [LayoutNode, LayoutNode]
|
|
}
|
|
|
|
export type LayoutNode = PaneNode | SplitNode
|
|
|
|
const ASCII_ART = `
|
|
_________________________________________
|
|
/ \\
|
|
| root@mytmux:~ $ tmux new -s dev |
|
|
| [0] nvim ---------------- [1] server -- |
|
|
| | | | |
|
|
| | import { life } | npm run | |
|
|
| | from 'tmux'; | dev | |
|
|
| | | | |
|
|
| | // TODO: Sleep | | |
|
|
| |________________________|____________| |
|
|
| [2] logs ------------------------------ |
|
|
| | > ready in 200ms | |
|
|
| | > watching files... | |
|
|
| |_____________________________________| |
|
|
\\_________________________________________/
|
|
`
|
|
|
|
export default function Home() {
|
|
const [layout, setLayout] = useState<LayoutNode>({ type: "pane", id: 0 })
|
|
const [nextId, setNextId] = useState(1)
|
|
const [activePaneId, setActivePaneId] = useState(0)
|
|
const [generatedConfig, setGeneratedConfig] = useState("")
|
|
|
|
// Helper to find and split the active node
|
|
function findAndSplitNode(
|
|
node: LayoutNode,
|
|
activeId: number,
|
|
direction: "h" | "v",
|
|
nextIdVal: number,
|
|
): { found: boolean; nextId: number; newNode?: LayoutNode } {
|
|
if (node.type === "pane") {
|
|
if (node.id === activeId) {
|
|
const oldId = node.id
|
|
const newId = nextIdVal
|
|
|
|
// Create new split node
|
|
const newNode: SplitNode = {
|
|
type: direction === "h" ? "split-h" : "split-v",
|
|
ratio: 0.5,
|
|
children: [
|
|
{ type: "pane", id: oldId },
|
|
{ type: "pane", id: newId },
|
|
],
|
|
}
|
|
return { found: true, nextId: nextIdVal + 1, newNode }
|
|
}
|
|
return { found: false, nextId: nextIdVal }
|
|
} else {
|
|
// Recursively check children
|
|
// We need to clone the node to avoid mutating state directly if we were modifying in place,
|
|
// but here we are rebuilding the tree structure where needed.
|
|
|
|
// Check left/top child
|
|
const leftResult = findAndSplitNode(node.children[0], activeId, direction, nextIdVal)
|
|
|
|
if (leftResult.found) {
|
|
return {
|
|
found: true,
|
|
nextId: leftResult.nextId,
|
|
newNode: {
|
|
...node,
|
|
children: [leftResult.newNode as LayoutNode, node.children[1]],
|
|
},
|
|
}
|
|
}
|
|
|
|
// Check right/bottom child
|
|
const rightResult = findAndSplitNode(node.children[1], activeId, direction, nextIdVal)
|
|
|
|
if (rightResult.found) {
|
|
return {
|
|
found: true,
|
|
nextId: rightResult.nextId,
|
|
newNode: {
|
|
...node,
|
|
children: [node.children[0], rightResult.newNode as LayoutNode],
|
|
},
|
|
}
|
|
}
|
|
|
|
return { found: false, nextId: nextIdVal }
|
|
}
|
|
}
|
|
|
|
function splitPane(direction: "h" | "v") {
|
|
const result = findAndSplitNode(layout, activePaneId, direction, nextId)
|
|
|
|
if (result.found && result.newNode) {
|
|
setNextId(result.nextId)
|
|
setLayout(result.newNode)
|
|
}
|
|
}
|
|
|
|
function resetLayout() {
|
|
setLayout({ type: "pane", id: 0 })
|
|
setNextId(1)
|
|
setActivePaneId(0)
|
|
}
|
|
|
|
// Generate config whenever layout changes
|
|
useEffect(() => {
|
|
let config = `# ~/.tmux.conf setup\n`
|
|
config += `# Configured by Shawn Anderson (Long Tail Financial)\n`
|
|
config += `# Generated by mytmux.life\n\n`
|
|
config += `new-session -s development -n editor\n`
|
|
|
|
function traverse(node: LayoutNode) {
|
|
if (node.type === "split-h") {
|
|
config += `split-window -h\n`
|
|
traverse(node.children[1]) // Right child
|
|
config += `select-pane -L\n` // Go back left
|
|
traverse(node.children[0]) // Left child
|
|
} else if (node.type === "split-v") {
|
|
config += `split-window -v\n`
|
|
traverse(node.children[1]) // Bottom child
|
|
config += `select-pane -U\n` // Go back up
|
|
traverse(node.children[0]) // Top child
|
|
}
|
|
}
|
|
|
|
traverse(layout)
|
|
|
|
config += `\n# Select the initially active pane\n`
|
|
config += `select-pane -t ${activePaneId}\n`
|
|
|
|
setGeneratedConfig(config)
|
|
}, [layout, activePaneId])
|
|
|
|
return (
|
|
<div className="flex flex-col gap-16 pb-20">
|
|
{/* Hero Section */}
|
|
<section className="container px-4 pt-20 md:pt-32">
|
|
<div className="grid gap-8 md:grid-cols-2 items-center">
|
|
<div className="space-y-6">
|
|
<div className="inline-flex items-center rounded-full border border-primary/50 bg-primary/10 px-3 py-1 text-xs font-medium text-primary">
|
|
<span className="mr-2 h-2 w-2 rounded-full bg-primary animate-pulse"></span>
|
|
SYSTEM ONLINE
|
|
</div>
|
|
<h1 className="text-4xl font-extrabold tracking-tight lg:text-6xl glow-text">
|
|
Master Your <br />
|
|
<span className="text-primary"><Terminal /></span>
|
|
</h1>
|
|
<p className="text-xl text-muted-foreground max-w-[600px]">
|
|
Stop wasting time switching windows. <span className="text-foreground font-bold">mytmux.life</span> helps
|
|
you architect the perfect terminal development environment.
|
|
</p>
|
|
<div className="flex flex-col sm:flex-row gap-4 pt-4">
|
|
<a
|
|
href="#configurator"
|
|
className="inline-flex h-12 items-center justify-center bg-primary text-primary-foreground px-8 text-sm font-medium transition-colors hover:bg-primary/90 hover:shadow-[0_0_20px_rgba(0,255,0,0.5)]"
|
|
>
|
|
INITIALIZE_CONFIG
|
|
</a>
|
|
<a
|
|
href="#learn"
|
|
className="inline-flex h-12 items-center justify-center border border-input bg-background px-8 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground"
|
|
>
|
|
TMUX-IFESTO
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
{/* ASCII Art / Decorative Element */}
|
|
<div className="hidden md:block p-6 border border-border bg-card/50 font-mono text-xs leading-none text-muted-foreground select-none overflow-hidden">
|
|
<pre>{ASCII_ART}</pre>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Configurator Section */}
|
|
<section id="configurator" className="container px-4 py-12">
|
|
<div className="flex flex-col space-y-4 mb-8">
|
|
<h2 className="text-3xl font-bold tracking-tight border-l-4 border-primary pl-4">ENVIRONMENT_CONFIGURATOR</h2>
|
|
<p className="text-muted-foreground">
|
|
Visually design your session layout. Click actions to split the active pane.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="grid lg:grid-cols-3 gap-8 h-[600px]">
|
|
{/* Controls & Output */}
|
|
<div className="flex flex-col gap-6 h-full">
|
|
<div className="p-6 border border-border bg-card space-y-6">
|
|
<h3 className="text-lg font-bold flex items-center gap-2">
|
|
<span className="text-primary">></span> ACTIONS
|
|
</h3>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<button
|
|
onClick={() => splitPane("h")}
|
|
className="h-20 border border-dashed border-muted-foreground hover:border-primary hover:text-primary hover:bg-primary/5 transition-all flex flex-col items-center justify-center gap-2 cursor-pointer"
|
|
>
|
|
<div className="flex gap-1 h-6 w-8 border border-current p-0.5">
|
|
<div className="w-1/2 h-full bg-current/50"></div>
|
|
<div className="w-1/2 h-full border border-current"></div>
|
|
</div>
|
|
<span className="text-xs">SPLIT_VERTICAL (%)</span>
|
|
</button>
|
|
<button
|
|
onClick={() => splitPane("v")}
|
|
className="h-20 border border-dashed border-muted-foreground hover:border-primary hover:text-primary hover:bg-primary/5 transition-all flex flex-col items-center justify-center gap-2 cursor-pointer"
|
|
>
|
|
<div className="flex flex-col gap-1 h-8 w-6 border border-current p-0.5">
|
|
<div className="h-1/2 w-full bg-current/50"></div>
|
|
<div className="h-1/2 w-full border border-current"></div>
|
|
</div>
|
|
<span className="text-xs">SPLIT_HORIZONTAL (")</span>
|
|
</button>
|
|
</div>
|
|
|
|
<div className="pt-4 border-t border-border">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<span className="text-xs text-muted-foreground">ACTIVE_PANE_ID:</span>
|
|
<span className="font-mono text-primary">{activePaneId}</span>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<button
|
|
onClick={resetLayout}
|
|
className="flex-1 py-2 text-xs bg-destructive/10 text-destructive hover:bg-destructive/20 border border-destructive/20 cursor-pointer"
|
|
>
|
|
RESET_LAYOUT
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex-1 p-6 border border-border bg-card flex flex-col min-h-0">
|
|
<h3 className="text-lg font-bold flex items-center gap-2 mb-4">
|
|
<span className="text-primary">></span> GENERATED_CONFIG
|
|
</h3>
|
|
<div className="flex-1 bg-black p-4 font-mono text-xs text-muted-foreground overflow-auto border border-border">
|
|
<pre>{generatedConfig}</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Visualizer Canvas */}
|
|
<div className="lg:col-span-2 h-full border border-border bg-card p-1 relative">
|
|
<div className="absolute top-0 left-0 bg-primary text-black text-[10px] font-bold px-2 py-0.5 z-10">
|
|
CANVAS_RENDER_TARGET
|
|
</div>
|
|
<TerminalVisualizer layout={layout} activePaneId={activePaneId} />
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Documentation / Info Section */}
|
|
<section id="learn" className="container px-4 py-12">
|
|
<div className="flex flex-col space-y-4 mb-8">
|
|
<h2 className="text-3xl font-bold tracking-tight border-l-4 border-primary pl-4">THE_TMUX_IFESTO</h2>
|
|
<p className="text-muted-foreground">The philosophy of persistent, multiplexed terminal environments.</p>
|
|
</div>
|
|
<div className="grid md:grid-cols-3 gap-12">
|
|
<div className="md:col-span-1 space-y-2">
|
|
<h3 className="text-xl font-bold text-primary">01. PERSISTENCE</h3>
|
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
|
Your work should not die when your connection drops. Detach your session, go home, and reattach exactly
|
|
where you left off. The session lives on the server, independent of your client.
|
|
</p>
|
|
</div>
|
|
<div className="md:col-span-1 space-y-2">
|
|
<h3 className="text-xl font-bold text-primary">02. MULTIPLEXING</h3>
|
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
|
One terminal is never enough. Split your workspace into windows (tabs) and panes (tiled splits). Monitor
|
|
logs, edit code, and run git commands simultaneously in a single SSH connection.
|
|
</p>
|
|
</div>
|
|
<div className="md:col-span-1 space-y-2">
|
|
<h3 className="text-xl font-bold text-primary">03. CONFIGURATION</h3>
|
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
|
The terminal is your canvas. Script your layouts, bind custom keys, and automate your startup. A
|
|
well-configured tmux environment is a force multiplier for any developer.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Mobile Setup Section */}
|
|
<section className="container px-4 py-12 border-t border-border">
|
|
<h2 className="text-2xl font-bold mb-8">MOBILE_DEPLOYMENT_PROTOCOLS</h2>
|
|
<div className="grid md:grid-cols-2 gap-8">
|
|
{/* Android Protocol */}
|
|
<div className="border border-border bg-card p-6 space-y-4">
|
|
<h3 className="text-xl font-bold text-primary flex items-center gap-2">
|
|
<span className="text-xs bg-primary text-black px-1">ANDROID</span>
|
|
<span>TERMUX_PROTOCOL</span>
|
|
</h3>
|
|
<div className="space-y-4 text-sm text-muted-foreground font-mono">
|
|
<div className="flex gap-4">
|
|
<span className="text-primary">01.</span>
|
|
<div>
|
|
<p className="mb-1 text-foreground">Acquire F-Droid</p>
|
|
<p>Download the F-Droid APK from f-droid.org to access the open-source repository.</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-4">
|
|
<span className="text-primary">02.</span>
|
|
<div>
|
|
<p className="mb-1 text-foreground">Install Termux</p>
|
|
<p>Search for "Termux" within F-Droid. Do not use the Play Store version (deprecated).</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-4">
|
|
<span className="text-primary">03.</span>
|
|
<div>
|
|
<p className="mb-1 text-foreground">Initialize Environment</p>
|
|
<div className="bg-black p-2 mt-1 border border-border text-xs">
|
|
<p>$ pkg update && pkg upgrade</p>
|
|
<p>$ pkg install tmux vim git</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* iOS Protocol */}
|
|
<div className="border border-border bg-card p-6 space-y-4">
|
|
<h3 className="text-xl font-bold text-primary flex items-center gap-2">
|
|
<span className="text-xs bg-primary text-black px-1">APPLE</span>
|
|
<span>ISH_SHELL_PROTOCOL</span>
|
|
</h3>
|
|
<div className="space-y-4 text-sm text-muted-foreground font-mono">
|
|
<div className="flex gap-4">
|
|
<span className="text-primary">01.</span>
|
|
<div>
|
|
<p className="mb-1 text-foreground">Acquire iSH Shell</p>
|
|
<p>Install "iSH Shell" from the App Store. This provides a localized Alpine Linux environment.</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-4">
|
|
<span className="text-primary">02.</span>
|
|
<div>
|
|
<p className="mb-1 text-foreground">Package Management</p>
|
|
<p>iSH uses Alpine's 'apk' package manager.</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-4">
|
|
<span className="text-primary">03.</span>
|
|
<div>
|
|
<p className="mb-1 text-foreground">Deploy Tmux</p>
|
|
<div className="bg-black p-2 mt-1 border border-border text-xs">
|
|
<p>$ apk update</p>
|
|
<p>$ apk add tmux vim openssh</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Cheat Sheet */}
|
|
<section className="container px-4 py-12 border-t border-border">
|
|
<h2 className="text-2xl font-bold mb-8">COMMAND_REFERENCE_MATRIX</h2>
|
|
<div className="grid md:grid-cols-2 gap-4">
|
|
{[
|
|
{ cmd: "tmux new -s <name>", desc: "Start new named session" },
|
|
{ cmd: "tmux a -t <name>", desc: "Attach to existing session" },
|
|
{ cmd: "tmux ls", desc: "List all active sessions" },
|
|
{ cmd: "Ctrl+b d", desc: "Detach from current session" },
|
|
{ cmd: "Ctrl+b %", desc: "Split pane vertically" },
|
|
{ cmd: 'Ctrl+b "', desc: "Split pane horizontally" },
|
|
{ cmd: "Ctrl+b <arrows>", desc: "Navigate between panes" },
|
|
{ cmd: "Ctrl+b z", desc: "Toggle pane zoom (maximize)" },
|
|
{ cmd: "Ctrl+b c", desc: "Create new window" },
|
|
{ cmd: "Ctrl+b n", desc: "Move to next window" },
|
|
{ cmd: "Ctrl+b ,", desc: "Rename current window" },
|
|
{ cmd: "Ctrl+b [", desc: "Enter copy/scroll mode" },
|
|
].map((item, i) => (
|
|
<div
|
|
key={i}
|
|
className="flex items-center justify-between p-4 border border-border bg-card/50 hover:bg-card transition-colors group"
|
|
>
|
|
<span className="text-muted-foreground group-hover:text-foreground transition-colors text-sm font-mono">
|
|
{item.desc}
|
|
</span>
|
|
<code className="bg-secondary px-2 py-1 text-primary text-xs border border-primary/20">{item.cmd}</code>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</section>
|
|
|
|
{/* Learning Resources Section */}
|
|
<section className="container px-4 py-12 border-t border-border">
|
|
<h2 className="text-2xl font-bold mb-8">LEARNING_RESOURCES</h2>
|
|
|
|
{/* Video Tutorials */}
|
|
<div className="mb-12">
|
|
<h3 className="text-xl font-bold text-primary mb-4">VIDEO_TUTORIALS</h3>
|
|
<div className="grid md:grid-cols-3 gap-6">
|
|
<a
|
|
href="https://www.youtube.com/watch?v=niuOc02Rvrc"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="border border-border bg-card p-6 hover:bg-card/80 hover:border-primary transition-all group"
|
|
>
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<span className="text-primary text-xl">▶</span>
|
|
<span className="text-sm font-mono text-muted-foreground group-hover:text-foreground">YouTube</span>
|
|
</div>
|
|
<p className="text-sm">Intro to tmux</p>
|
|
</a>
|
|
|
|
<a
|
|
href="https://www.youtube.com/watch?v=jaI3Hcw-ZaA"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="border border-border bg-card p-6 hover:bg-card/80 hover:border-primary transition-all group"
|
|
>
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<span className="text-primary text-xl">▶</span>
|
|
<span className="text-sm font-mono text-muted-foreground group-hover:text-foreground">YouTube</span>
|
|
</div>
|
|
<p className="text-sm">Advanced tmux workflows</p>
|
|
</a>
|
|
|
|
<a
|
|
href="https://www.youtube.com/watch?v=nTqu6w2wc68"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="border border-border bg-card p-6 hover:bg-card/80 hover:border-primary transition-all group"
|
|
>
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<span className="text-primary text-xl">▶</span>
|
|
<span className="text-sm font-mono text-muted-foreground group-hover:text-foreground">YouTube</span>
|
|
</div>
|
|
<p className="text-sm">Deep dive into tmux</p>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Credits and References */}
|
|
<div className="border border-primary/30 bg-primary/5 p-6">
|
|
<h3 className="text-xl font-bold text-primary mb-4">INSPIRATION_&_CREDITS</h3>
|
|
<div className="space-y-4 text-sm text-muted-foreground">
|
|
<p className="text-foreground">
|
|
Special thanks to{" "}
|
|
<a
|
|
href="https://github.com/dvbuntu"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-primary hover:underline font-mono"
|
|
>
|
|
Danielle Van Boxel
|
|
</a>{" "}
|
|
for inspiring much of Shawn Anderson's early tmux.conf configuration.
|
|
</p>
|
|
<div className="flex flex-col sm:flex-row gap-4 pt-2">
|
|
<a
|
|
href="https://github.com/dvbuntu/.files/blob/master/.tmux.conf"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="inline-flex items-center gap-2 px-4 py-2 border border-border bg-card hover:bg-card/80 hover:border-primary transition-all font-mono text-xs"
|
|
>
|
|
<span className="text-primary">></span>
|
|
View tmux.conf
|
|
</a>
|
|
<a
|
|
href="https://github.com/dvbuntu"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="inline-flex items-center gap-2 px-4 py-2 border border-border bg-card hover:bg-card/80 hover:border-primary transition-all font-mono text-xs"
|
|
>
|
|
<span className="text-primary">></span>
|
|
GitHub Profile
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
)
|
|
}
|