import './sftpPanel.component.scss'; import { ApplicationRef, ElementRef, EnvironmentInjector, EventEmitter, OnDestroy, OnInit } from '@angular/core'; import { AppService, ConfigService } from 'tabby-core'; import { SidebarPlusNoticesService } from '../notices.service'; import { SidebarPlusI18nService } from '../i18n'; /** * Hosts the SFTP browser inside the sidebar, bound to whatever SSH tab * currently has focus. * * The browser is `SidebarPlusSftpBrowserComponent` — tabby-ssh's own * `SFTPPanelComponent` subclassed with our template, see that file. It is * created imperatively rather than written into this template because the * caching below needs to own its lifecycle: a view built by `*ngFor`/`*ngIf` * is destroyed the moment its condition flips, which is exactly what must not * happen here. */ export declare class SidebarPlusSftpComponent implements OnInit, OnDestroy { private app; private appRef; private environmentInjector; private config; private notices; private i18n; /** Emitted when the hosted panel's own close button is pressed — the sidebar switches back to the profile tree. */ closed: EventEmitter; /** * Whether the sidebar is currently showing this view. * * This component stays mounted while the sidebar is on the profile tree * (an `*ngIf` would destroy it, and with it the panel cache — every * Profils↔SFTP round trip would then open a fresh SFTP channel that * nothing ever closes). But staying mounted must not mean staying busy: * without this flag it would happily open a channel on every SSH tab the * user focuses, for a view they are not looking at. */ set active(value: boolean); private isActive; /** * Pins the visible panel to the session it currently shows, so a focus * change elsewhere in the terminal does not pull the view out from under * a navigation in progress. * * Transient by design (roadmap: "un geste de navigation, pas une * préférence") — a plain field, never a `sidebarPlus` config key, so * piège #16 (undeclared keys silently failing to persist) does not apply * here: this never touches `config.store`. Reset wherever `boundTab` * itself is cleared (dropDeadPanel(), pruneClosedTabs(), * releaseBoundPanel()) — a freeze must never outlive the panel it points * at. */ frozen: boolean; /** Name of the SSH tab the panel is currently bound to, for the header line. */ boundTabTitle: string | null; /** True while no focused tab offers a connected SSH session — drives the "waiting" placeholder. */ get waiting(): boolean; panelContainer: ElementRef; /** * One panel per SSH tab, kept alive across tab switches instead of being * rebuilt. Two reasons, both load-bearing: the inherited * `SFTPPanelComponent.ngOnInit()` calls `session.openSFTP()`, which opens * a *new* SFTP channel every time * (and `SFTPSession` exposes no `close()` — the channel only dies with the * SSH session), so rebuilding on every switch would leak one channel per * switch; and the panel's own `path` is what gives us the roadmap's * per-tab navigation memory for free, with nothing persisted. */ private panels; private boundTab; /** * The session the visible panel was built against. * * Tracked separately from the tab because a tab that loses its connection * and reconnects is *the same object* — only `sshSession` is replaced. The * tab alone was what sync() compared, so it returned early forever and the * rebuild in attachPanel() was never reached: the panel stayed bound to the * dead transport, answering "Session closed" to every gesture, until the * tab itself was killed. */ private boundSession; /** * Last remote directory each tab was browsing, so a rebuilt panel resumes * where the old one was. * * The cache gave that memory for free — a panel that is never destroyed * keeps its own `path`. Rebuilding on a reconnect is the one path that * loses it, and landing back on `/` after every dropped link is exactly * the kind of small regression that makes a feature feel unfinished. * Re-injected before the new panel's ngOnInit reads it; if the directory * is gone by then, the inherited ngOnInit already notifies and falls back * to `/` on its own. */ private lastPaths; /** * When the bound session was first seen as lost, or null while it is fine. * * Drives the grace period rather than a timer: sync() already runs on a * tick, and a timer would have to be cancelled from every path that * rebinds, releases or destroys the panel. */ private sessionLostSince; private subscription; /** Tracks the focus changes *within* the active split tab, re-subscribed whenever the active tab changes. */ private splitFocusSubscription; /** * Angular assigns `@Input()`s before it resolves even a `static: true` * @ViewChild, so a sidebar that starts up already in SFTP mode would run * the `active` setter — and therefore attachPanel() — while * `panelContainer` is still undefined. Nothing may touch the DOM before * ngOnInit has been reached. */ private viewInitialized; constructor(app: AppService, appRef: ApplicationRef, environmentInjector: EnvironmentInjector, config: ConfigService, notices: SidebarPlusNoticesService, i18n: SidebarPlusI18nService); ngOnInit(): void; ngOnDestroy(): void; /** * Puts the visible panel back in the cache: out of the DOM and out of * change detection, but not destroyed, so re-entering the view rebinds it * without opening a second SFTP channel. `boundTab` has to be cleared too, * otherwise the next sync() would see no change and never re-attach. */ private releaseBoundPanel; /** * Header pin button. Guarded on `boundTab` even though the button only * ever renders while a session is shown (see the template's `!waiting` * guard) — a defensive belt for a state that has no meaning without a * bound session, in case that ever stops being true. */ toggleFreeze(): void; /** * Releases a freeze from outside the toggle button — called by * sidebarTree's "open this session's SFTP" shortcut (`openSessionSftp()`) * before it focuses the target tab. That click names one particular * session explicitly, which must win over a freeze left standing from an * earlier navigation: otherwise the shortcut would silently no-op, * leaving the user staring at whatever session the pin was protecting * instead of the one they just asked for. */ unfreeze(): void; private sync; /** * True when the panel on screen is bound to a session that is gone for * good — as opposed to one that has already been replaced by a reconnect, * which is a rebuild and is handled by attachPanel(). */ private boundSessionIsLost; private autoReturnToProfilesEnabled; /** * True once no SSH tab anywhere in the app — splits included, focused or * not — still holds a live session. * * Broader than boundSessionIsLost() on purpose: that one only ever asks * about the single tab this panel is bound to, which says nothing about a * tab elsewhere that dropped its session in the background, or about the * waiting placeholder, where nothing is bound at all. */ private noLiveSSHTabAnywhere; /** * Unlike releaseBoundPanel(), this destroys the panel instead of caching * it: it holds an SFTP channel on a transport that is gone, so nothing in * it can be reused, and keeping it would only hand it back on the next * visit to a tab that is about to get a brand new session anyway. */ private dropDeadPanel; /** * The active tab is not enough: Tabby wraps every tab in a * `SplitTabComponent`, so `app.activeTab` is always the split and only * `getFocusedTab()` names the pane the user is actually looking at. */ private resolveFocusedSSHTab; /** * Follows focus moves between panes of the active split tab, which emit * nothing on AppService. * * Unsubscribes unconditionally before resubscribing: an earlier version * kept the existing subscription whenever there was one, which meant that * after moving from split A to split B the panel went on listening to A * forever. Only visible in a specific sequence — selecting a split does not * itself settle which pane is focused, so a *later* focus change inside B * was the only correction available, and it never arrived. The sidebar's * "open this session's SFTP" shortcut walks exactly that path (selectTab * then focus), so it would have bound the panel to whichever pane B last * had focused instead of the one asked for. */ private watchSplitFocus; /** Drops cached panels whose tab is gone, so a long session doesn't accumulate dead views. */ private pruneClosedTabs; private attachPanel; /** * Takes the panel out of the DOM *and* out of change detection, but keeps * the ComponentRef — see the `panels` field for why it is not destroyed. */ private detachPanel; private rememberPath; private destroyPanel; private rootNodeOf; }