Skip to content

Commit 992f136

Browse files
committed
order buttons fixes
1 parent 6082ba2 commit 992f136

8 files changed

Lines changed: 101 additions & 53 deletions

File tree

README.md

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,62 @@
33
</p>
44

55
<h1 align="center">OTG Code</h1>
6-
<p align="center"><em>On-The-Go Code</em></p>
6+
<p align="center"><em>On-The-Go Code — a mobile-first browser IDE for remote development</em></p>
77

8-
<p align="center">Mobile-terminal-UX first coding IDE fully in the browser, with built-in localhost browser proxy for local testing. Vibe code on your VPS or home machine from anywhere — phone, tablet, or desktop — with zero client-side installation using Cloudflare Quick Tunnels.</p>
8+
<p align="center">Code on your VPS or home machine from anywhere — phone, tablet, or desktop — with zero client-side installation. Full terminal, file explorer, code editor, and localhost preview in a single browser tab, accessible via Cloudflare Quick Tunnels.</p>
99

1010
<p align="center">
11-
<img src="https://img.shields.io/badge/version-0.1.0-blue" alt="Version">
11+
<img src="https://img.shields.io/badge/version-0.0.1-blue" alt="Version">
1212
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="License"></a>
1313
<img src="https://img.shields.io/badge/node-%3E%3D20-brightgreen" alt="Node >= 20">
1414
<img src="https://img.shields.io/badge/TypeScript-5.9-blue?logo=typescript&logoColor=white" alt="TypeScript">
1515
<img src="https://img.shields.io/badge/React_Router-v7-red?logo=reactrouter&logoColor=white" alt="React Router">
1616
<img src="https://img.shields.io/badge/PRs-welcome-brightgreen" alt="PRs Welcome">
1717
</p>
1818

19+
<!-- TODO: Add product showcase videos here -->
20+
<!-- <p align="center">
21+
<a href="https://..."><img src="..." alt="Demo video" width="600" /></a>
22+
</p> -->
23+
1924
## Features
2025

21-
- **Terminal** — xterm.js with multi-session renamable tabs, configurable font size, quick action key groups (Ctrl+A-Z, vim, nano, tmux, common commands), multiline text input with send button
22-
- **File Explorer** — multi-session renamable tabs, breadcrumb + editable path navigation, create/rename/delete files and folders, file info dialog, hidden files toggle, context menu (right-click / long-press)
23-
- **Code Editor** — Monaco editor with syntax highlighting, configurable font size, word wrap, drag-and-drop text selection
24-
- **Markdown Preview** — rendered GitHub-flavored markdown with prose styling (headings, code blocks, tables, links, images)
25-
- **Jupyter Notebook Viewer** — renders cells with type badges, markdown as prose, code as monospace, outputs including text/HTML/base64 images
26-
- **HTML Preview** — live rendered HTML in a sandboxed iframe
27-
- **PDF Viewer** — inline PDF rendering with zoom controls and pinch-to-zoom
28-
- **Image Viewer** — inline image display with zoom controls and pinch-to-zoom
29-
- **Video Player** — native HTML5 video playback (mp4, webm, ogg, mov, mkv, avi)
30-
- **Audio Player** — native HTML5 audio playback (mp3, wav, ogg, flac, aac, m4a)
31-
- **Localhost Browser** — reverse proxy to preview any localhost port through the app, multi-tab with console output panel for debugging
32-
- **Responsive Layout** — 3-column resizable panels with draggable dividers on desktop, bottom tab bar navigation on mobile
33-
- **Cloudflare Tunnel** — one command (`./start.sh`) to build, start, and expose your machine to the internet via a temporary public URL
26+
### Terminal
27+
- Multi-session tabs (renamable), xterm.js with configurable font size
28+
- **Quick action tab groups** — color-coded action tabs (blue) and app tabs (green):
29+
- **cmds** — common shell commands (ls, top, df, free, nvidia-smi, etc.)
30+
- **cd** — visual directory picker with server-side CWD detection (works inside tmux)
31+
- **Ctrl+** — sticky modifier combos (Ctrl, Ctrl+Shift, Alt, Alt+Shift) with full A-Z and 0-9
32+
- **code** — coding CLI launchers (Claude Code, Codex, OpenCode) with permission presets, plus common CLI key combos (Shift+Tab, Ctrl+C, Ctrl+O)
33+
- **git** — quick actions (status, log, diff, add, fetch, pull, push, stash, branch), commit with message input, git config setup (user.name/email)
34+
- **nano / vim** — file opener with version detection, in-app command buttons, save/exit shortcuts
35+
- **tmux** — session manager (list, attach, create, kill), in-session window/pane controls, auto extended-keys compatibility
36+
- **Always-visible nav keys** below input — Enter, Bksp, arrows, y/n/q, Esc, Tab, PgUp/PgDn
37+
- **Context-aware tabs** — editor mode (nano/vim) disables action tabs; tmux mode hides nano/vim (can't track state inside tmux); plain terminal shows everything
38+
- Auto-reset of tmux/editor state on socket reconnect
39+
- System info popup with OS, kernel, CPU, memory, GPU details
40+
- Tool version detection (nano, vim, tmux, claude, codex, opencode)
41+
42+
### File Explorer
43+
- Multi-session tabs (renamable), breadcrumb + editable path navigation
44+
- Create, rename, delete files and folders; file info dialog; hidden files toggle
45+
- Context menu (right-click / long-press), selectable/scrollable file paths
46+
- Drag-and-drop file upload
47+
48+
### Viewers & Editors
49+
- **Code Editor** — Monaco with syntax highlighting, configurable font size, word wrap
50+
- **Markdown Preview** — GitHub-flavored with prose styling
51+
- **Jupyter Notebook** — cells with type badges, rendered markdown, code, and outputs
52+
- **HTML Preview** — live rendered in sandboxed iframe
53+
- **PDF Viewer** — inline rendering with zoom and pinch-to-zoom
54+
- **Image Viewer** — zoom controls with full pan/scroll at any zoom level, pinch-to-zoom
55+
- **Video Player** — mp4, webm, ogg, mov, mkv, avi
56+
- **Audio Player** — mp3, wav, ogg, flac, aac, m4a
57+
58+
### Layout & Access
59+
- **Responsive** — 3-column resizable panels on desktop/landscape, single-panel with bottom tab bar on mobile/portrait (orientation-aware breakpoint)
60+
- **Localhost Browser** — reverse proxy to preview any localhost port, multi-tab with console output
61+
- **Cloudflare Tunnel** — one command (`./start.sh`) to build, start, and expose via public URL
3462

3563
## Quick Start
3664

app/components/files/Breadcrumbs.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ export default function Breadcrumbs({ path, onNavigate, onGoUp, canGoUp }: Bread
6767
) : (
6868
<div
6969
ref={scrollRef}
70-
className="flex-1 flex items-center gap-0.5 text-sm overflow-x-auto whitespace-nowrap px-1 scrollbar-none"
70+
onDoubleClick={() => setTextMode(true)}
71+
className="flex-1 flex items-center gap-0.5 text-sm overflow-x-auto whitespace-nowrap px-1 scrollbar-none select-text"
7172
>
7273
<button
7374
onClick={() => onNavigate("/")}

app/components/files/CodeEditor.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { lazy, Suspense, useState, useMemo } from "react";
2+
import CopyPathButton from "./CopyPathButton";
23
import { marked } from "marked";
34

45
const MonacoEditor = lazy(() => import("@monaco-editor/react"));
@@ -223,7 +224,8 @@ export default function CodeEditor({ path, content, onSave, onClose }: CodeEdito
223224
<div className="flex flex-col h-full">
224225
{/* Toolbar */}
225226
<div className="flex items-center gap-2 px-3 py-1.5 bg-[#16162a] border-b border-gray-700 shrink-0">
226-
<span className="text-sm text-gray-300 whitespace-nowrap min-w-0 flex-1 overflow-x-auto select-text mr-2 scrollbar-none" title={path}>{path}</span>
227+
<span className="text-sm text-gray-300 whitespace-nowrap min-w-0 flex-1 overflow-hidden text-ellipsis select-none" title={path}>{path}</span>
228+
<CopyPathButton path={path} />
227229
<div className="flex items-center gap-1.5 shrink-0">
228230
{canPreview && (
229231
<div className="flex items-center bg-[#0d0d1a] rounded overflow-hidden border border-gray-700">
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { useState, useCallback } from "react";
2+
3+
export default function CopyPathButton({ path }: { path: string }) {
4+
const [copied, setCopied] = useState(false);
5+
6+
const handleCopy = useCallback(() => {
7+
navigator.clipboard.writeText(path).then(() => {
8+
setCopied(true);
9+
setTimeout(() => setCopied(false), 1500);
10+
});
11+
}, [path]);
12+
13+
return (
14+
<button
15+
onClick={handleCopy}
16+
className="p-1 text-gray-500 hover:text-white transition-colors shrink-0"
17+
title={copied ? "Copied!" : "Copy path"}
18+
>
19+
{copied ? (
20+
<svg className="w-3.5 h-3.5 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}>
21+
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
22+
</svg>
23+
) : (
24+
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}>
25+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
26+
<path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" />
27+
</svg>
28+
)}
29+
</button>
30+
);
31+
}

app/components/files/FileViewer.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useState, useRef, useCallback } from "react";
2+
import CopyPathButton from "./CopyPathButton";
23
import { Document, Page, pdfjs } from "react-pdf";
34
import "react-pdf/dist/Page/AnnotationLayer.css";
45
import "react-pdf/dist/Page/TextLayer.css";
@@ -27,7 +28,8 @@ function MediaViewer({ path, type, onClose }: { path: string; type: "video" | "a
2728
return (
2829
<div className="flex flex-col h-full">
2930
<div className="flex items-center justify-between px-3 py-2 bg-[#16162a] border-b border-gray-700 shrink-0">
30-
<span className="text-sm text-gray-300 whitespace-nowrap min-w-0 flex-1 overflow-x-auto select-text mr-2 scrollbar-none" title={path}>{path}</span>
31+
<span className="text-sm text-gray-300 whitespace-nowrap min-w-0 flex-1 overflow-hidden text-ellipsis select-none" title={path}>{path}</span>
32+
<CopyPathButton path={path} />
3133
<div className="flex items-center gap-2 shrink-0">
3234
<a
3335
href={`/api/files/download?path=${encodeURIComponent(path)}`}
@@ -75,7 +77,8 @@ function PdfViewer({ path, onClose }: { path: string; onClose: () => void }) {
7577
return (
7678
<div className="flex flex-col h-full">
7779
<div className="flex items-center justify-between px-3 py-2 bg-[#16162a] border-b border-gray-700 shrink-0">
78-
<span className="text-sm text-gray-300 whitespace-nowrap min-w-0 flex-1 overflow-x-auto select-text mr-2 scrollbar-none" title={path}>{path}</span>
80+
<span className="text-sm text-gray-300 whitespace-nowrap min-w-0 flex-1 overflow-hidden text-ellipsis select-none" title={path}>{path}</span>
81+
<CopyPathButton path={path} />
7982
<div className="flex items-center gap-2 shrink-0">
8083
{numPages > 0 && (
8184
<span className="text-[10px] text-gray-500">{numPages} pg</span>
@@ -158,7 +161,8 @@ export default function FileViewer({ path, content, onSave, onClose }: FileViewe
158161
return (
159162
<div className="flex flex-col h-full">
160163
<div className="flex items-center justify-between px-3 py-2 bg-[#16162a] border-b border-gray-700 shrink-0">
161-
<span className="text-sm text-gray-300 whitespace-nowrap min-w-0 flex-1 overflow-x-auto select-text mr-2 scrollbar-none" title={path}>{path}</span>
164+
<span className="text-sm text-gray-300 whitespace-nowrap min-w-0 flex-1 overflow-hidden text-ellipsis select-none" title={path}>{path}</span>
165+
<CopyPathButton path={path} />
162166
<div className="flex items-center gap-2 shrink-0">
163167
<a
164168
href={`/api/files/download?path=${encodeURIComponent(path)}`}

app/components/files/ImageViewer.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useState, useRef, useCallback } from "react";
2+
import CopyPathButton from "./CopyPathButton";
23

34
interface ImageViewerProps {
45
path: string;
@@ -40,7 +41,8 @@ export default function ImageViewer({ path, onClose }: ImageViewerProps) {
4041
return (
4142
<div className="flex flex-col h-full">
4243
<div className="flex items-center justify-between px-3 py-2 bg-[#16162a] border-b border-gray-700 shrink-0">
43-
<span className="text-sm text-gray-300 whitespace-nowrap min-w-0 flex-1 overflow-x-auto select-text mr-2 scrollbar-none" title={path}>{path}</span>
44+
<span className="text-sm text-gray-300 whitespace-nowrap min-w-0 flex-1 overflow-hidden text-ellipsis select-none" title={path}>{path}</span>
45+
<CopyPathButton path={path} />
4446
<div className="flex items-center gap-2 shrink-0">
4547
{/* Zoom controls */}
4648
<div className="flex items-center gap-1 border border-gray-700 rounded overflow-hidden">

app/components/terminal/InputBox.tsx

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -484,36 +484,16 @@ export default function InputBox() {
484484
{/* Tab bar */}
485485
<div className="border-b border-gray-700/50 overflow-x-auto scrollbar-none" style={{ minWidth: 0 }}>
486486
<div className="flex items-center gap-1 px-2 py-1 w-max">
487-
{isEditorMode ? (
488-
<>
489-
{/* Editor mode (nano/vim): only editor + sticky, tmux if applicable */}
490-
{inEditor === "nano" && tabBtn(NANO_TAB, "nano \u2318", "nano commands", false, "app")}
491-
{inEditor === "vim" && tabBtn(VIM_TAB, "vim \u2318", "vim commands", false, "app")}
492-
{tabBtn(STICKY_TAB, STICKY_MODES.find((m) => m.id === stickyMode)?.label || "Ctrl+", "Modifier combos")}
493-
</>
494-
) : inTmux ? (
495-
<>
496-
{/* Tmux mode: all action tabs + tmux (no nano/vim — can't track inside tmux) */}
497-
{TERMINAL_GROUPS.map((g) => tabBtn(g.label, g.label, g.title))}
498-
{tabBtn(CD_TAB, "cd", "Change directory", !activeSessionId)}
499-
{tabBtn(STICKY_TAB, STICKY_MODES.find((m) => m.id === stickyMode)?.label || "Ctrl+", "Modifier combos")}
500-
{tabBtn(CODE_TAB, "code", "Coding CLI launchers & keys", !activeSessionId)}
501-
{tabBtn(GIT_TAB, "git", "Git actions", !activeSessionId)}
502-
{tabBtn(TMUX_TAB, "tmux \u2318", "tmux commands", !activeSessionId, "app")}
503-
</>
504-
) : (
505-
<>
506-
{/* Plain terminal: all action tabs + all app tabs */}
507-
{TERMINAL_GROUPS.map((g) => tabBtn(g.label, g.label, g.title))}
508-
{tabBtn(CD_TAB, "cd", "Change directory", !activeSessionId)}
509-
{tabBtn(STICKY_TAB, STICKY_MODES.find((m) => m.id === stickyMode)?.label || "Ctrl+", "Modifier combos")}
510-
{tabBtn(CODE_TAB, "code", "Coding CLI launchers & keys", !activeSessionId)}
511-
{tabBtn(GIT_TAB, "git", "Git actions", !activeSessionId)}
512-
{tabBtn(NANO_TAB, "nano", "Open file in nano", !activeSessionId, "app")}
513-
{tabBtn(VIM_TAB, "vim", "Open file in vim", !activeSessionId, "app")}
514-
{tabBtn(TMUX_TAB, inTmux ? "tmux \u2318" : "tmux", inTmux ? "tmux commands" : "tmux sessions", !activeSessionId, "app")}
515-
</>
516-
)}
487+
{/* Action tabs (blue) — always in same order, hidden in editor mode */}
488+
{!isEditorMode && TERMINAL_GROUPS.map((g) => tabBtn(g.label, g.label, g.title))}
489+
{!isEditorMode && tabBtn(CD_TAB, "cd", "Change directory", !activeSessionId)}
490+
{tabBtn(STICKY_TAB, STICKY_MODES.find((m) => m.id === stickyMode)?.label || "Ctrl+", "Modifier combos")}
491+
{!isEditorMode && tabBtn(CODE_TAB, "code", "Coding CLI launchers & keys", !activeSessionId)}
492+
{!isEditorMode && tabBtn(GIT_TAB, "git", "Git actions", !activeSessionId)}
493+
{/* App tabs (green) — nano/vim hidden in tmux mode, shown with active indicator in editor mode */}
494+
{!inTmux && tabBtn(NANO_TAB, inEditor === "nano" ? "nano \u2318" : "nano", inEditor === "nano" ? "nano commands" : "Open file in nano", !activeSessionId && inEditor !== "nano", "app")}
495+
{!inTmux && tabBtn(VIM_TAB, inEditor === "vim" ? "vim \u2318" : "vim", inEditor === "vim" ? "vim commands" : "Open file in vim", !activeSessionId && inEditor !== "vim", "app")}
496+
{tabBtn(TMUX_TAB, inTmux ? "tmux \u2318" : "tmux", inTmux ? "tmux commands" : "tmux sessions", !activeSessionId && !inTmux, "app")}
517497
</div>
518498
</div>
519499

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "otgcode",
3-
"version": "0.1.0-pre-alpha",
3+
"version": "0.0.1",
44
"private": true,
55
"type": "module",
66
"scripts": {

0 commit comments

Comments
 (0)