import { useEffect } from 'react'; import { ChevronLeft, Save, Rocket } from 'lucide-react'; import { cn } from '@/lib/utils'; import { Button } from '../ui/button'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import type { StackAction } from './EditorView'; interface MobileComposeEditorProps { content: string; envContent: string; setContent: (next: string) => void; setEnvContent: (next: string) => void; activeTab: 'compose' | 'env' | 'files'; setActiveTab: (tab: 'compose' | 'env' | 'files') => void; envExists: boolean; envFiles: string[]; selectedEnvFile: string; changeEnvFile: (file: string) => Promise; isFileLoading: boolean; loadingAction: StackAction | null; canEdit: boolean; requestSave: () => void; requestSaveAndDeploy: (e: React.MouseEvent) => void; onClose: () => void; hasUnsavedChanges: () => boolean; } // Full-screen phone editing surface for the compose file and the selected .env. // Deliberately a lightweight monospace textarea, not Monaco: the flow is scoped to // small, safe edits, and the native keyboard plus reliable touch scrolling matter // more than syntax highlighting here. Every save protection (ETag conflict, diff // preview, save-and-deploy, dirty-navigation guard) is reused from useStackActions // via the shared editor state, so this surface only renders the controls. export function MobileComposeEditor(props: MobileComposeEditorProps) { const { content, envContent, setContent, setEnvContent, activeTab, setActiveTab, envExists, envFiles, selectedEnvFile, changeEnvFile, isFileLoading, loadingAction, canEdit, requestSave, requestSaveAndDeploy, onClose, hasUnsavedChanges, } = props; // The save handlers (saveFile) key off the shared editorState.activeTab, so the // displayed buffer must match it. The desktop editor can hand off a 'files' tab // (or 'env' with no env file) when it crosses into the mobile breakpoint; left // alone the textarea would show compose while a save silently no-ops on 'files'. // Normalize to 'compose' so the visible edit always saves to the visible file. useEffect(() => { if (activeTab === 'files' || (activeTab === 'env' && !envExists)) { setActiveTab('compose'); } }, [activeTab, envExists, setActiveTab]); // Mirror of the normalization above for this render: 'env' only when an env // file exists, else 'compose'. The effect makes the shared activeTab follow. const tab: 'compose' | 'env' = activeTab === 'env' && envExists ? 'env' : 'compose'; const value = tab === 'compose' ? content || '' : envContent || ''; // Switching the env file refetches and overwrites the env buffer, so block it // while there are unsaved edits (matches the desktop selector being disabled // mid-edit). The compose <-> .env toggle stays free: both buffers persist. const envSwitchDisabled = hasUnsavedChanges() || isFileLoading; const actionsDisabled = isFileLoading || loadingAction === 'deploy'; // Read-only while an env-file fetch is in flight: changeEnvFile overwrites the // buffer when it resolves, so edits typed during the load would be silently lost. const editorReadOnly = !canEdit || isFileLoading; return (
{/* Header: close + file selector */}
{envExists ? (
{(['compose', 'env'] as const).map(id => { const on = tab === id; return ( ); })}
) : ( compose.yaml )}
{tab === 'env' && envFiles.length > 1 && ( )}
{/* Editor */}