diff --git a/src/App.tsx b/src/App.tsx index b891880..c011310 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -27,7 +27,12 @@ export default function App() { const s = useSessions(); const u = useUpdate(); const [editing, setEditing] = useState({ kind: "connection", node: null, parentId: null }); - const [active, setActive] = useState("editor"); + const [editorExpanded, setEditorExpanded] = useState(false); + const [sidebarCollapsed, setSidebarCollapsed] = useState(() => { + const saved = localStorage.getItem("remota:sidebarCollapsed"); + return saved === "true"; + }); + const [active, setActive] = useState(""); const [notice, setNotice] = useState(null); // Connect-time password prompt (connections with always_prompt). Resolves with the typed // password, or null if cancelled. The value is handed straight to the session, never stored. @@ -36,6 +41,14 @@ export default function App() { const [setPwModal, setSetPwModal] = useState<{ title: string; resolve: (pw: string | null) => void } | null>(null); const promptNewPassword = (title: string) => new Promise((resolve) => setSetPwModal({ title, resolve })); + const toggleSidebar = () => { + setSidebarCollapsed((prev) => { + const next = !prev; + localStorage.setItem("remota:sidebarCollapsed", String(next)); + return next; + }); + }; + // Built once and rendered in every branch below — including the locked screen. Whoever has not // opened Remota in a month is exactly the person who has not heard about the release that fixed // something, and they should not have to unlock a vault to find out. @@ -66,14 +79,17 @@ export default function App() { function selectNode(n: Node) { setEditing({ kind: n.type === "folder" ? "folder" : "connection", node: n, parentId: findParentId(v.tree, n.id) }); + setEditorExpanded(true); setActive("editor"); } function newConnectionAt(parentId: string | null) { setEditing({ kind: "connection", node: null, parentId }); + setEditorExpanded(true); setActive("editor"); } function newFolderAt(parentId: string | null) { setEditing({ kind: "folder", node: null, parentId }); + setEditorExpanded(true); setActive("editor"); } const newConnection = () => newConnectionAt(targetParent); @@ -81,7 +97,26 @@ export default function App() { function lock() { v.lock(); setEditing({ kind: "connection", node: null, parentId: null }); - setActive("editor"); + setEditorExpanded(false); + setActive(""); + } + + function handleToggleEditor() { + if (editorExpanded) { + if (active === "editor") { + setEditorExpanded(false); + if (s.tabs.length > 0) { + setActive(s.tabs[s.tabs.length - 1].id); + } else { + setActive(""); + } + } else { + setActive("editor"); + } + } else { + setEditorExpanded(true); + setActive("editor"); + } } async function saveNode(n: Node) { @@ -109,7 +144,7 @@ export default function App() { } const newId = await s.openSession({ connId: id, - title: found.node.name, + title: found.node.name.trim(), protocol: found.node.conn.protocol, host: found.node.conn.host, port: found.node.conn.port, @@ -124,7 +159,12 @@ export default function App() { } function closeTab(id: string) { s.closeSession(id); - setActive((cur) => (cur === id ? "editor" : cur)); + setActive((cur) => { + if (cur !== id) return cur; + const remaining = s.tabs.filter((t) => t.id !== id); + if (remaining.length > 0) return remaining[remaining.length - 1].id; + return editorExpanded ? "editor" : ""; + }); } // Arrastar um nó para dentro de uma pasta (ou raiz). O backend (upsert) trata do mover. @@ -214,7 +254,7 @@ export default function App() { async function deleteNode(id: string) { const node = findNode(v.tree, id); - const name = node?.name ?? "este item"; + const name = node?.name?.trim() ?? "este item"; const isFolder = node?.type === "folder"; const msg = isFolder ? `Move the folder "${name}" and its contents to the Trash?` @@ -241,6 +281,9 @@ export default function App() { { label: "New connection", onClick: newConnection }, { label: "New folder", onClick: newFolder }, "sep", + { label: sidebarCollapsed ? "Show Connections Sidebar" : "Collapse Connections Sidebar", onClick: toggleSidebar }, + { label: editorExpanded ? "Collapse Editor" : "Expand Editor", onClick: handleToggleEditor }, + "sep", { label: "Import from mRemoteNG (confCons.xml)…", onClick: importMremoteng }, { label: "Import connections (Remota)…", onClick: importRemotaJson }, { label: "Export connections (encrypted)…", onClick: exportConnectionsEncrypted }, @@ -286,8 +329,26 @@ export default function App() {
Remota
+ +
@@ -299,35 +360,141 @@ export default function App() { )}
-
@@ -416,7 +620,27 @@ const banner: CSSProperties = { borderBottom: `1px solid ${colors.border}`, color: colors.text, }; -const sidebar: CSSProperties = { width: 320, borderRight: `1px solid ${colors.border}`, overflow: "hidden", display: "flex", flexDirection: "column" }; +const sidebar: CSSProperties = { + borderRight: `1px solid ${colors.border}`, + overflow: "hidden", + display: "flex", + flexDirection: "column", + transition: "width 0.15s ease", + flexShrink: 0, +}; +const iconBtn: CSSProperties = { + background: "transparent", + border: "none", + color: colors.dim, + cursor: "pointer", + padding: "2px 6px", + borderRadius: 4, + fontSize: 12, + lineHeight: 1, + display: "flex", + alignItems: "center", + justifyContent: "center", +}; const sidebarHead: CSSProperties = { padding: "8px 12px", fontSize: 11, diff --git a/src/components/TabBar.tsx b/src/components/TabBar.tsx index fb7ee8b..457a367 100644 --- a/src/components/TabBar.tsx +++ b/src/components/TabBar.tsx @@ -6,20 +6,42 @@ const protoColor: Record = { ssh: "#7ee787", rdp: "#79c0ff", vnc type Props = { tabs: SessionTab[]; - activeId: string; // "editor" | id da sessão + activeId: string; // "editor" | id da sessão | "" + editorExpanded: boolean; + onToggleEditor: () => void; onActivate: (id: string) => void; onClose: (id: string) => void; onReconnect: (id: string) => void; onDuplicate: (id: string) => void; }; -export function TabBar({ tabs, activeId, onActivate, onClose, onReconnect, onDuplicate }: Props) { +export function TabBar({ tabs, activeId, editorExpanded, onToggleEditor, onActivate, onClose, onReconnect, onDuplicate }: Props) { const [menu, setMenu] = useState<{ id: string; x: number; y: number } | null>(null); + const isEditorActive = activeId === "editor" && editorExpanded; + return (
-
onActivate("editor")} style={tabStyle(activeId === "editor")}> - ✎ Editor +
+ ✎ Editor + + {editorExpanded ? "▾" : "▸"} +
{tabs.map((t) => (