tabby-sftp-ui 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -6
- package/dist/index.js +455 -388
- package/dist/index.js.map +1 -1
- package/dist/sftp-manager-tab.component.d.ts +3 -1
- package/dist/sftp-recovery-provider.d.ts +8 -0
- package/package.json +44 -44
package/dist/index.js
CHANGED
|
@@ -237,6 +237,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
237
237
|
/* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(tabby_core__WEBPACK_IMPORTED_MODULE_6__);
|
|
238
238
|
/* harmony import */ var _local_transfers__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./local-transfers */ "./src/local-transfers.ts");
|
|
239
239
|
/* harmony import */ var _sftp_service__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./sftp.service */ "./src/sftp.service.ts");
|
|
240
|
+
/* harmony import */ var _sftp_recovery_provider__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./sftp-recovery-provider */ "./src/sftp-recovery-provider.ts");
|
|
240
241
|
var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
|
|
241
242
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
242
243
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -255,6 +256,7 @@ var __metadata = (undefined && undefined.__metadata) || function (k, v) {
|
|
|
255
256
|
|
|
256
257
|
|
|
257
258
|
|
|
259
|
+
|
|
258
260
|
let SftpManagerTabComponent = class SftpManagerTabComponent extends tabby_core__WEBPACK_IMPORTED_MODULE_6__.BaseTabComponent {
|
|
259
261
|
constructor(injector, sftp, profilesService, app) {
|
|
260
262
|
// Tabby runtime BaseTabComponent expects Injector in constructor, but typings in this SDK may differ.
|
|
@@ -266,6 +268,7 @@ let SftpManagerTabComponent = class SftpManagerTabComponent extends tabby_core__
|
|
|
266
268
|
// injected from the SSH tab when opened via SFTP-UI button
|
|
267
269
|
this.sshSession = null;
|
|
268
270
|
this.profile = null;
|
|
271
|
+
this.recoveredStub = false;
|
|
269
272
|
this.connecting = false;
|
|
270
273
|
this.connected = false;
|
|
271
274
|
// legacy UI fields kept for now (not used when opened from SSH tab)
|
|
@@ -353,9 +356,9 @@ let SftpManagerTabComponent = class SftpManagerTabComponent extends tabby_core__
|
|
|
353
356
|
// If there's no live SSH session, this tab was likely restored across
|
|
354
357
|
// restart or opened in an invalid context. Close it immediately to avoid
|
|
355
358
|
// an empty, nameless SFTP tab lingering after restart.
|
|
356
|
-
if (!this.sshSession) {
|
|
359
|
+
if (this.recoveredStub || !this.sshSession) {
|
|
357
360
|
try {
|
|
358
|
-
this.app.closeTab(this);
|
|
361
|
+
this.app.closeTab(this.topmostParent ?? this);
|
|
359
362
|
}
|
|
360
363
|
catch (e) {
|
|
361
364
|
console.error('[SFTP-UI] Failed to close invalid SFTP tab', e);
|
|
@@ -1591,7 +1594,7 @@ let SftpManagerTabComponent = class SftpManagerTabComponent extends tabby_core__
|
|
|
1591
1594
|
getDefaultRemotePath() {
|
|
1592
1595
|
const username = (this.profile && (this.profile.options?.username || this.profile.options?.user)) || '';
|
|
1593
1596
|
if (username) {
|
|
1594
|
-
return `/home/${username}`;
|
|
1597
|
+
return username === 'root' ? `/root` : `/home/${username}`;
|
|
1595
1598
|
}
|
|
1596
1599
|
return '/';
|
|
1597
1600
|
}
|
|
@@ -2036,12 +2039,16 @@ let SftpManagerTabComponent = class SftpManagerTabComponent extends tabby_core__
|
|
|
2036
2039
|
}
|
|
2037
2040
|
super.destroy();
|
|
2038
2041
|
}
|
|
2039
|
-
//
|
|
2040
|
-
//
|
|
2041
|
-
//
|
|
2042
|
+
// A custom recovery token ensures that a SplitTab wrapper won't become empty
|
|
2043
|
+
// after restart. On recovery we restore a stub SFTP tab which immediately
|
|
2044
|
+
// closes its `topmostParent` (the SplitTab wrapper).
|
|
2042
2045
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2043
2046
|
async getRecoveryToken(_options) {
|
|
2044
|
-
return
|
|
2047
|
+
return {
|
|
2048
|
+
type: _sftp_recovery_provider__WEBPACK_IMPORTED_MODULE_9__.SFTP_RECOVERY_TYPE,
|
|
2049
|
+
version: 1,
|
|
2050
|
+
state: {},
|
|
2051
|
+
};
|
|
2045
2052
|
}
|
|
2046
2053
|
async confirmDelete() {
|
|
2047
2054
|
if (!this.deleteConfirmVisible) {
|
|
@@ -2339,388 +2346,388 @@ __decorate([
|
|
|
2339
2346
|
SftpManagerTabComponent = __decorate([
|
|
2340
2347
|
(0,_angular_core__WEBPACK_IMPORTED_MODULE_5__.Component)({
|
|
2341
2348
|
selector: 'tabby-sftp-manager-tab',
|
|
2342
|
-
template: `
|
|
2343
|
-
<div class="sftp-root" tabindex="0" (keydown)="onKeyDown($event)">
|
|
2344
|
-
<div class="top-profiles" *ngIf="profile || recentProfiles.length">
|
|
2345
|
-
<div class="current" *ngIf="profile">
|
|
2346
|
-
<span class="label">Device:</span>
|
|
2347
|
-
<span class="value">{{ getProfileLabel(profile) }}</span>
|
|
2348
|
-
</div>
|
|
2349
|
-
<div class="recent" *ngIf="recentProfiles.length">
|
|
2350
|
-
<span class="label">Recent:</span>
|
|
2351
|
-
<button
|
|
2352
|
-
class="profile-chip"
|
|
2353
|
-
*ngFor="let p of recentProfiles"
|
|
2354
|
-
(click)="launchProfileFromSFTP(p)"
|
|
2355
|
-
>
|
|
2356
|
-
{{ getProfileLabel(p) }}
|
|
2357
|
-
</button>
|
|
2358
|
-
</div>
|
|
2359
|
-
</div>
|
|
2360
|
-
<div class="sftp-body">
|
|
2361
|
-
<div class="pane">
|
|
2362
|
-
<div class="pane-title">
|
|
2363
|
-
<div class="pane-label">Local</div>
|
|
2364
|
-
<div class="pane-path">
|
|
2365
|
-
<input
|
|
2366
|
-
[(ngModel)]="localPathInput"
|
|
2367
|
-
(keyup.enter)="goToLocalPathInput()"
|
|
2368
|
-
/>
|
|
2369
|
-
</div>
|
|
2370
|
-
<div class="pane-actions">
|
|
2371
|
-
<select class="path-preset" (change)="onLocalPresetChange($event.target.value)">
|
|
2372
|
-
<option value="">Go to…</option>
|
|
2373
|
-
<option *ngFor="let p of localPathPresets" [value]="p.id">
|
|
2374
|
-
{{ p.label }}
|
|
2375
|
-
</option>
|
|
2376
|
-
</select>
|
|
2377
|
-
<button
|
|
2378
|
-
class="fav-toggle"
|
|
2379
|
-
[class.active]="isCurrentFavorite()"
|
|
2380
|
-
(click)="toggleCurrentFavorite()"
|
|
2381
|
-
title="Toggle favorite for this path"
|
|
2382
|
-
>
|
|
2383
|
-
★
|
|
2384
|
-
</button>
|
|
2385
|
-
<select class="path-favorite" (change)="onLocalFavoriteSelect($event.target.value)">
|
|
2386
|
-
<option value="">Favorites…</option>
|
|
2387
|
-
<option *ngFor="let f of localFavorites" [value]="f.id">
|
|
2388
|
-
{{ f.label }}
|
|
2389
|
-
</option>
|
|
2390
|
-
</select>
|
|
2391
|
-
<button (click)="localUp()" [disabled]="!canLocalUp()">Up</button>
|
|
2392
|
-
<button (click)="goToLocalPathInput()">Go</button>
|
|
2393
|
-
<button (click)="refreshLocal()">Refresh</button>
|
|
2394
|
-
</div>
|
|
2395
|
-
</div>
|
|
2396
|
-
<div class="pane-filters">
|
|
2397
|
-
<div class="breadcrumbs">
|
|
2398
|
-
<ng-container *ngFor="let part of getLocalBreadcrumbs(); let i = index; let last = last">
|
|
2399
|
-
<button
|
|
2400
|
-
class="crumb-button"
|
|
2401
|
-
(click)="navigateLocalBreadcrumb(i)"
|
|
2402
|
-
(contextmenu)="onLocalBreadcrumbContextMenu(i, $event)"
|
|
2403
|
-
>
|
|
2404
|
-
{{ part.label }}
|
|
2405
|
-
</button>
|
|
2406
|
-
<span class="crumb-separator" *ngIf="!last">›</span>
|
|
2407
|
-
</ng-container>
|
|
2408
|
-
</div>
|
|
2409
|
-
<input [(ngModel)]="localFilter" placeholder="Filter files..." />
|
|
2410
|
-
<label class="show-hidden-toggle">
|
|
2411
|
-
<input type="checkbox" [(ngModel)]="showHiddenLocal" />
|
|
2412
|
-
<span>Show hidden</span>
|
|
2413
|
-
</label>
|
|
2414
|
-
</div>
|
|
2415
|
-
<div class="pane-list"
|
|
2416
|
-
(dragover)="onDragOver($event)"
|
|
2417
|
-
(drop)="onDropOnLocal($event)"
|
|
2418
|
-
>
|
|
2419
|
-
<div class="entry header">
|
|
2420
|
-
<span class="icon"></span>
|
|
2421
|
-
<span class="name sortable" (click)="setLocalSort('name')">Name</span>
|
|
2422
|
-
<span class="size sortable" (click)="setLocalSort('size')">Size</span>
|
|
2423
|
-
<span class="date sortable" (click)="setLocalSort('modified')">Modified</span>
|
|
2424
|
-
</div>
|
|
2425
|
-
<div
|
|
2426
|
-
class="entry"
|
|
2427
|
-
*ngIf="canLocalUp()"
|
|
2428
|
-
(dblclick)="localUp()"
|
|
2429
|
-
>
|
|
2430
|
-
<span class="icon">⬆</span>
|
|
2431
|
-
<span class="name">Go up</span>
|
|
2432
|
-
<span class="size"></span>
|
|
2433
|
-
<span class="date"></span>
|
|
2434
|
-
</div>
|
|
2435
|
-
<div
|
|
2436
|
-
class="entry"
|
|
2437
|
-
*ngFor="let e of getFilteredLocalEntries()"
|
|
2438
|
-
(click)="selectLocal(e, $event)"
|
|
2439
|
-
(dblclick)="openLocal(e)"
|
|
2440
|
-
(mousedown)="onLocalMouseDown(e, $event)"
|
|
2441
|
-
(contextmenu)="onLocalContextMenu(e, $event)"
|
|
2442
|
-
(dragover)="onLocalEntryDragOver(e, $event)"
|
|
2443
|
-
(drop)="onLocalEntryDrop(e, $event)"
|
|
2444
|
-
[class.drop-target]="localDropActive"
|
|
2445
|
-
[class.selected]="isLocalSelected(e)"
|
|
2446
|
-
[draggable]="true"
|
|
2447
|
-
(dragstart)="onDragStartLocal($event, e)"
|
|
2448
|
-
>
|
|
2449
|
-
<span class="icon">{{ e.isDirectory ? '📁' : '📄' }}</span>
|
|
2450
|
-
<span class="name">{{ e.name }}</span>
|
|
2451
|
-
<span class="size">{{ getLocalSizeDisplay(e) }}</span>
|
|
2452
|
-
<span class="date">{{ e.mtimeMs ? (e.mtimeMs | date:'yyyy-MM-dd HH:mm') : '' }}</span>
|
|
2453
|
-
</div>
|
|
2454
|
-
</div>
|
|
2455
|
-
<div class="pane-actions-bar">
|
|
2456
|
-
<div class="selection" *ngIf="selectedLocal.length">
|
|
2457
|
-
Selected: {{ selectedLocal.length === 1 ? selectedLocal[0].name : (selectedLocal.length + ' items') }}
|
|
2458
|
-
</div>
|
|
2459
|
-
<div class="action-inputs">
|
|
2460
|
-
<input [(ngModel)]="localActionName" placeholder="Name / new name" />
|
|
2461
|
-
<input [(ngModel)]="localActionPerms" placeholder="Perms (e.g. 755)" />
|
|
2462
|
-
</div>
|
|
2463
|
-
<div class="action-buttons">
|
|
2464
|
-
<button (click)="localRename()" [disabled]="selectedLocal.length !== 1">Rename</button>
|
|
2465
|
-
<button (click)="refreshLocal()">Refresh</button>
|
|
2466
|
-
<button (click)="localDelete()" [disabled]="!selectedLocal.length">Delete</button>
|
|
2467
|
-
<button (click)="localNewFolder()">New Folder</button>
|
|
2468
|
-
<button (click)="localEditPermissions()" [disabled]="selectedLocal.length !== 1 || !localActionPerms">Edit Permissions</button>
|
|
2469
|
-
<button (click)="localShowSize()" [disabled]="selectedLocal.length !== 1 || !selectedLocal[0].isDirectory">Show Size</button>
|
|
2470
|
-
</div>
|
|
2471
|
-
</div>
|
|
2472
|
-
</div>
|
|
2473
|
-
|
|
2474
|
-
<div class="pane">
|
|
2475
|
-
<div class="pane-title">
|
|
2476
|
-
<div class="pane-label">
|
|
2477
|
-
Remote
|
|
2478
|
-
<span *ngIf="connected && profile?.options?.host" class="pane-sub">
|
|
2479
|
-
— {{ profile.options.host }}
|
|
2480
|
-
</span>
|
|
2481
|
-
</div>
|
|
2482
|
-
<div class="pane-path">
|
|
2483
|
-
<input
|
|
2484
|
-
[(ngModel)]="remotePathInput"
|
|
2485
|
-
(keyup.enter)="goToRemotePathInput()"
|
|
2486
|
-
[disabled]="!connected"
|
|
2487
|
-
/>
|
|
2488
|
-
</div>
|
|
2489
|
-
<div class="pane-actions">
|
|
2490
|
-
<button (click)="remoteUp()" [disabled]="!connected || remotePath === '/'">Up</button>
|
|
2491
|
-
<button (click)="goToRemotePathInput()" [disabled]="!connected">Go</button>
|
|
2492
|
-
<button (click)="refreshRemote()" [disabled]="!connected">Refresh</button>
|
|
2493
|
-
</div>
|
|
2494
|
-
</div>
|
|
2495
|
-
<div class="pane-filters">
|
|
2496
|
-
<div class="breadcrumbs" *ngIf="connected">
|
|
2497
|
-
<ng-container *ngFor="let part of getRemoteBreadcrumbs(); let i = index; let last = last">
|
|
2498
|
-
<button
|
|
2499
|
-
class="crumb-button"
|
|
2500
|
-
(click)="navigateRemoteBreadcrumb(i)"
|
|
2501
|
-
>
|
|
2502
|
-
{{ part.label }}
|
|
2503
|
-
</button>
|
|
2504
|
-
<span class="crumb-separator" *ngIf="!last">›</span>
|
|
2505
|
-
</ng-container>
|
|
2506
|
-
</div>
|
|
2507
|
-
<input [(ngModel)]="remoteFilter" placeholder="Filter files..." />
|
|
2508
|
-
<label class="show-hidden-toggle">
|
|
2509
|
-
<input type="checkbox" [(ngModel)]="showHiddenRemote" />
|
|
2510
|
-
<span>Show hidden</span>
|
|
2511
|
-
</label>
|
|
2512
|
-
</div>
|
|
2513
|
-
<div class="pane-list"
|
|
2514
|
-
(dragover)="onDragOver($event)"
|
|
2515
|
-
(drop)="onDropOnRemote($event)"
|
|
2516
|
-
>
|
|
2517
|
-
<div class="entry dim" *ngIf="!connected">
|
|
2518
|
-
<span class="name">Not connected</span>
|
|
2519
|
-
</div>
|
|
2520
|
-
<div class="entry header" *ngIf="connected">
|
|
2521
|
-
<span class="icon"></span>
|
|
2522
|
-
<span class="name sortable" (click)="setRemoteSort('name')">Name</span>
|
|
2523
|
-
<span class="size sortable" (click)="setRemoteSort('size')">Size</span>
|
|
2524
|
-
<span class="date sortable" (click)="setRemoteSort('modified')">Modified</span>
|
|
2525
|
-
</div>
|
|
2526
|
-
<div
|
|
2527
|
-
class="entry"
|
|
2528
|
-
*ngIf="connected && remotePath !== '/'"
|
|
2529
|
-
(dblclick)="remoteUp()"
|
|
2530
|
-
>
|
|
2531
|
-
<span class="icon">⬆</span>
|
|
2532
|
-
<span class="name">Go up</span>
|
|
2533
|
-
<span class="size"></span>
|
|
2534
|
-
<span class="date"></span>
|
|
2535
|
-
</div>
|
|
2536
|
-
<div
|
|
2537
|
-
class="entry"
|
|
2538
|
-
*ngFor="let e of getFilteredRemoteEntries()"
|
|
2539
|
-
(click)="selectRemote(e, $event)"
|
|
2540
|
-
(dblclick)="openRemote(e)"
|
|
2541
|
-
(mousedown)="onRemoteMouseDown(e, $event)"
|
|
2542
|
-
(contextmenu)="onRemoteContextMenu(e, $event)"
|
|
2543
|
-
(dragover)="onRemoteEntryDragOver(e, $event)"
|
|
2544
|
-
(drop)="onRemoteEntryDrop(e, $event)"
|
|
2545
|
-
[class.drop-target]="remoteDropActive"
|
|
2546
|
-
[class.selected]="isRemoteSelected(e)"
|
|
2547
|
-
[draggable]="connected"
|
|
2548
|
-
(dragstart)="onDragStartRemote($event, e)"
|
|
2549
|
-
>
|
|
2550
|
-
<span class="icon">{{ e.isDirectory ? '📁' : '📄' }}</span>
|
|
2551
|
-
<span class="name">{{ e.name }}</span>
|
|
2552
|
-
<span class="size">{{ getRemoteSizeDisplay(e) }}</span>
|
|
2553
|
-
<span class="date">{{ e.modified | date:'yyyy-MM-dd HH:mm' }}</span>
|
|
2554
|
-
</div>
|
|
2555
|
-
</div>
|
|
2556
|
-
<div class="pane-actions-bar">
|
|
2557
|
-
<div class="selection" *ngIf="selectedRemote.length">
|
|
2558
|
-
Selected: {{ selectedRemote.length === 1 ? selectedRemote[0].name : (selectedRemote.length + ' items') }}
|
|
2559
|
-
</div>
|
|
2560
|
-
<div class="action-inputs">
|
|
2561
|
-
<input [(ngModel)]="remoteActionName" placeholder="Name / new name" />
|
|
2562
|
-
<input [(ngModel)]="remoteActionPerms" placeholder="Perms (e.g. 755)" />
|
|
2563
|
-
</div>
|
|
2564
|
-
<div class="action-buttons">
|
|
2565
|
-
<button (click)="remoteRename()" [disabled]="selectedRemote.length !== 1">Rename</button>
|
|
2566
|
-
<button (click)="refreshRemote()" [disabled]="!connected">Refresh</button>
|
|
2567
|
-
<button (click)="remoteDelete()" [disabled]="!selectedRemote.length">Delete</button>
|
|
2568
|
-
<button (click)="remoteNewFolder()" [disabled]="!connected">New Folder</button>
|
|
2569
|
-
<button (click)="remoteEditPermissions()" [disabled]="selectedRemote.length !== 1 || !remoteActionPerms">Edit Permissions</button>
|
|
2570
|
-
<button (click)="remoteShowSize()" [disabled]="selectedRemote.length !== 1 || !selectedRemote[0].isDirectory">Show Size</button>
|
|
2571
|
-
<button (click)="remoteDownload()" [disabled]="!selectedRemote.length">Download</button>
|
|
2572
|
-
</div>
|
|
2573
|
-
</div>
|
|
2574
|
-
</div>
|
|
2575
|
-
</div>
|
|
2576
|
-
<div class="sftp-transfers" *ngIf="transfers.length">
|
|
2577
|
-
<div class="transfer" *ngFor="let t of transfers">
|
|
2578
|
-
<div class="transfer-main">
|
|
2579
|
-
<div class="transfer-title">
|
|
2580
|
-
<span class="direction">{{ t.direction === 'upload' ? 'Upload' : 'Download' }}</span>
|
|
2581
|
-
<span class="name">{{ t.name }}</span>
|
|
2582
|
-
</div>
|
|
2583
|
-
<div class="transfer-path">
|
|
2584
|
-
<span class="label">Remote:</span>
|
|
2585
|
-
<span class="value">{{ t.remotePath }}</span>
|
|
2586
|
-
</div>
|
|
2587
|
-
<div class="transfer-path">
|
|
2588
|
-
<span class="label">Local:</span>
|
|
2589
|
-
<span class="value">{{ t.localPath }}</span>
|
|
2590
|
-
</div>
|
|
2591
|
-
<div class="bar">
|
|
2592
|
-
<div class="fill" [style.width.%]="getTransferProgress(t.transfer)"></div>
|
|
2593
|
-
</div>
|
|
2594
|
-
</div>
|
|
2595
|
-
<div class="transfer-stats">
|
|
2596
|
-
<div class="percent">{{ getTransferProgress(t.transfer) | number:'1.0-0' }}%</div>
|
|
2597
|
-
<div class="speed">{{ formatSpeed(t.transfer.getSpeed()) }}</div>
|
|
2598
|
-
<button class="btn-cancel" (click)="cancelTransfer(t)" [disabled]="t.transfer.isComplete() || t.transfer.isCancelled()">Cancel</button>
|
|
2599
|
-
</div>
|
|
2600
|
-
</div>
|
|
2601
|
-
</div>
|
|
2602
|
-
|
|
2603
|
-
<div class="delete-overlay" *ngIf="deleteConfirmVisible">
|
|
2604
|
-
<div class="delete-dialog">
|
|
2605
|
-
<div class="delete-text">{{ deleteConfirmText }}</div>
|
|
2606
|
-
<div class="delete-buttons">
|
|
2607
|
-
<button class="danger" (click)="confirmDelete()">Delete</button>
|
|
2608
|
-
<button (click)="cancelDelete()">Cancel</button>
|
|
2609
|
-
</div>
|
|
2610
|
-
</div>
|
|
2611
|
-
</div>
|
|
2612
|
-
|
|
2613
|
-
<div class="delete-overlay" *ngIf="replaceConfirmVisible">
|
|
2614
|
-
<div class="delete-dialog">
|
|
2615
|
-
<div class="delete-text">{{ replaceConfirmText }}</div>
|
|
2616
|
-
<div class="delete-buttons">
|
|
2617
|
-
<button class="danger" (click)="confirmReplace()">Replace</button>
|
|
2618
|
-
<button (click)="cancelReplace()">Cancel</button>
|
|
2619
|
-
</div>
|
|
2620
|
-
</div>
|
|
2621
|
-
</div>
|
|
2622
|
-
|
|
2623
|
-
<div class="delete-overlay" *ngIf="inputDialogVisible">
|
|
2624
|
-
<div class="delete-dialog" (click)="$event.stopPropagation()">
|
|
2625
|
-
<div class="delete-text">{{ inputDialogTitle }}</div>
|
|
2626
|
-
<input
|
|
2627
|
-
class="dialog-input"
|
|
2628
|
-
[(ngModel)]="inputDialogValue"
|
|
2629
|
-
[placeholder]="inputDialogPlaceholder"
|
|
2630
|
-
(keyup.enter)="confirmInputDialog()"
|
|
2631
|
-
/>
|
|
2632
|
-
<div class="delete-buttons">
|
|
2633
|
-
<button class="danger" (click)="confirmInputDialog()" [disabled]="!inputDialogValue.trim()">OK</button>
|
|
2634
|
-
<button (click)="cancelInputDialog()">Cancel</button>
|
|
2635
|
-
</div>
|
|
2636
|
-
</div>
|
|
2637
|
-
</div>
|
|
2638
|
-
|
|
2639
|
-
<div
|
|
2640
|
-
class="local-menu"
|
|
2641
|
-
*ngIf="localMenuVisible"
|
|
2642
|
-
[style.left.px]="localMenuX"
|
|
2643
|
-
[style.top.px]="localMenuY"
|
|
2644
|
-
(click)="$event.stopPropagation()"
|
|
2645
|
-
>
|
|
2646
|
-
<div class="local-menu-item" *ngFor="let item of localMenuItems" (click)="onLocalMenuItemClick(item)">
|
|
2647
|
-
{{ item.label }}
|
|
2648
|
-
</div>
|
|
2649
|
-
</div>
|
|
2650
|
-
</div>
|
|
2349
|
+
template: `
|
|
2350
|
+
<div class="sftp-root" tabindex="0" (keydown)="onKeyDown($event)">
|
|
2351
|
+
<div class="top-profiles" *ngIf="profile || recentProfiles.length">
|
|
2352
|
+
<div class="current" *ngIf="profile">
|
|
2353
|
+
<span class="label">Device:</span>
|
|
2354
|
+
<span class="value">{{ getProfileLabel(profile) }}</span>
|
|
2355
|
+
</div>
|
|
2356
|
+
<div class="recent" *ngIf="recentProfiles.length">
|
|
2357
|
+
<span class="label">Recent:</span>
|
|
2358
|
+
<button
|
|
2359
|
+
class="profile-chip"
|
|
2360
|
+
*ngFor="let p of recentProfiles"
|
|
2361
|
+
(click)="launchProfileFromSFTP(p)"
|
|
2362
|
+
>
|
|
2363
|
+
{{ getProfileLabel(p) }}
|
|
2364
|
+
</button>
|
|
2365
|
+
</div>
|
|
2366
|
+
</div>
|
|
2367
|
+
<div class="sftp-body">
|
|
2368
|
+
<div class="pane">
|
|
2369
|
+
<div class="pane-title">
|
|
2370
|
+
<div class="pane-label">Local</div>
|
|
2371
|
+
<div class="pane-path">
|
|
2372
|
+
<input
|
|
2373
|
+
[(ngModel)]="localPathInput"
|
|
2374
|
+
(keyup.enter)="goToLocalPathInput()"
|
|
2375
|
+
/>
|
|
2376
|
+
</div>
|
|
2377
|
+
<div class="pane-actions">
|
|
2378
|
+
<select class="path-preset" (change)="onLocalPresetChange($event.target.value)">
|
|
2379
|
+
<option value="">Go to…</option>
|
|
2380
|
+
<option *ngFor="let p of localPathPresets" [value]="p.id">
|
|
2381
|
+
{{ p.label }}
|
|
2382
|
+
</option>
|
|
2383
|
+
</select>
|
|
2384
|
+
<button
|
|
2385
|
+
class="fav-toggle"
|
|
2386
|
+
[class.active]="isCurrentFavorite()"
|
|
2387
|
+
(click)="toggleCurrentFavorite()"
|
|
2388
|
+
title="Toggle favorite for this path"
|
|
2389
|
+
>
|
|
2390
|
+
★
|
|
2391
|
+
</button>
|
|
2392
|
+
<select class="path-favorite" (change)="onLocalFavoriteSelect($event.target.value)">
|
|
2393
|
+
<option value="">Favorites…</option>
|
|
2394
|
+
<option *ngFor="let f of localFavorites" [value]="f.id">
|
|
2395
|
+
{{ f.label }}
|
|
2396
|
+
</option>
|
|
2397
|
+
</select>
|
|
2398
|
+
<button (click)="localUp()" [disabled]="!canLocalUp()">Up</button>
|
|
2399
|
+
<button (click)="goToLocalPathInput()">Go</button>
|
|
2400
|
+
<button (click)="refreshLocal()">Refresh</button>
|
|
2401
|
+
</div>
|
|
2402
|
+
</div>
|
|
2403
|
+
<div class="pane-filters">
|
|
2404
|
+
<div class="breadcrumbs">
|
|
2405
|
+
<ng-container *ngFor="let part of getLocalBreadcrumbs(); let i = index; let last = last">
|
|
2406
|
+
<button
|
|
2407
|
+
class="crumb-button"
|
|
2408
|
+
(click)="navigateLocalBreadcrumb(i)"
|
|
2409
|
+
(contextmenu)="onLocalBreadcrumbContextMenu(i, $event)"
|
|
2410
|
+
>
|
|
2411
|
+
{{ part.label }}
|
|
2412
|
+
</button>
|
|
2413
|
+
<span class="crumb-separator" *ngIf="!last">›</span>
|
|
2414
|
+
</ng-container>
|
|
2415
|
+
</div>
|
|
2416
|
+
<input [(ngModel)]="localFilter" placeholder="Filter files..." />
|
|
2417
|
+
<label class="show-hidden-toggle">
|
|
2418
|
+
<input type="checkbox" [(ngModel)]="showHiddenLocal" />
|
|
2419
|
+
<span>Show hidden</span>
|
|
2420
|
+
</label>
|
|
2421
|
+
</div>
|
|
2422
|
+
<div class="pane-list"
|
|
2423
|
+
(dragover)="onDragOver($event)"
|
|
2424
|
+
(drop)="onDropOnLocal($event)"
|
|
2425
|
+
>
|
|
2426
|
+
<div class="entry header">
|
|
2427
|
+
<span class="icon"></span>
|
|
2428
|
+
<span class="name sortable" (click)="setLocalSort('name')">Name</span>
|
|
2429
|
+
<span class="size sortable" (click)="setLocalSort('size')">Size</span>
|
|
2430
|
+
<span class="date sortable" (click)="setLocalSort('modified')">Modified</span>
|
|
2431
|
+
</div>
|
|
2432
|
+
<div
|
|
2433
|
+
class="entry"
|
|
2434
|
+
*ngIf="canLocalUp()"
|
|
2435
|
+
(dblclick)="localUp()"
|
|
2436
|
+
>
|
|
2437
|
+
<span class="icon">⬆</span>
|
|
2438
|
+
<span class="name">Go up</span>
|
|
2439
|
+
<span class="size"></span>
|
|
2440
|
+
<span class="date"></span>
|
|
2441
|
+
</div>
|
|
2442
|
+
<div
|
|
2443
|
+
class="entry"
|
|
2444
|
+
*ngFor="let e of getFilteredLocalEntries()"
|
|
2445
|
+
(click)="selectLocal(e, $event)"
|
|
2446
|
+
(dblclick)="openLocal(e)"
|
|
2447
|
+
(mousedown)="onLocalMouseDown(e, $event)"
|
|
2448
|
+
(contextmenu)="onLocalContextMenu(e, $event)"
|
|
2449
|
+
(dragover)="onLocalEntryDragOver(e, $event)"
|
|
2450
|
+
(drop)="onLocalEntryDrop(e, $event)"
|
|
2451
|
+
[class.drop-target]="localDropActive"
|
|
2452
|
+
[class.selected]="isLocalSelected(e)"
|
|
2453
|
+
[draggable]="true"
|
|
2454
|
+
(dragstart)="onDragStartLocal($event, e)"
|
|
2455
|
+
>
|
|
2456
|
+
<span class="icon">{{ e.isDirectory ? '📁' : '📄' }}</span>
|
|
2457
|
+
<span class="name">{{ e.name }}</span>
|
|
2458
|
+
<span class="size">{{ getLocalSizeDisplay(e) }}</span>
|
|
2459
|
+
<span class="date">{{ e.mtimeMs ? (e.mtimeMs | date:'yyyy-MM-dd HH:mm') : '' }}</span>
|
|
2460
|
+
</div>
|
|
2461
|
+
</div>
|
|
2462
|
+
<div class="pane-actions-bar">
|
|
2463
|
+
<div class="selection" *ngIf="selectedLocal.length">
|
|
2464
|
+
Selected: {{ selectedLocal.length === 1 ? selectedLocal[0].name : (selectedLocal.length + ' items') }}
|
|
2465
|
+
</div>
|
|
2466
|
+
<div class="action-inputs">
|
|
2467
|
+
<input [(ngModel)]="localActionName" placeholder="Name / new name" />
|
|
2468
|
+
<input [(ngModel)]="localActionPerms" placeholder="Perms (e.g. 755)" />
|
|
2469
|
+
</div>
|
|
2470
|
+
<div class="action-buttons">
|
|
2471
|
+
<button (click)="localRename()" [disabled]="selectedLocal.length !== 1">Rename</button>
|
|
2472
|
+
<button (click)="refreshLocal()">Refresh</button>
|
|
2473
|
+
<button (click)="localDelete()" [disabled]="!selectedLocal.length">Delete</button>
|
|
2474
|
+
<button (click)="localNewFolder()">New Folder</button>
|
|
2475
|
+
<button (click)="localEditPermissions()" [disabled]="selectedLocal.length !== 1 || !localActionPerms">Edit Permissions</button>
|
|
2476
|
+
<button (click)="localShowSize()" [disabled]="selectedLocal.length !== 1 || !selectedLocal[0].isDirectory">Show Size</button>
|
|
2477
|
+
</div>
|
|
2478
|
+
</div>
|
|
2479
|
+
</div>
|
|
2480
|
+
|
|
2481
|
+
<div class="pane">
|
|
2482
|
+
<div class="pane-title">
|
|
2483
|
+
<div class="pane-label">
|
|
2484
|
+
Remote
|
|
2485
|
+
<span *ngIf="connected && profile?.options?.host" class="pane-sub">
|
|
2486
|
+
— {{ profile.options.host }}
|
|
2487
|
+
</span>
|
|
2488
|
+
</div>
|
|
2489
|
+
<div class="pane-path">
|
|
2490
|
+
<input
|
|
2491
|
+
[(ngModel)]="remotePathInput"
|
|
2492
|
+
(keyup.enter)="goToRemotePathInput()"
|
|
2493
|
+
[disabled]="!connected"
|
|
2494
|
+
/>
|
|
2495
|
+
</div>
|
|
2496
|
+
<div class="pane-actions">
|
|
2497
|
+
<button (click)="remoteUp()" [disabled]="!connected || remotePath === '/'">Up</button>
|
|
2498
|
+
<button (click)="goToRemotePathInput()" [disabled]="!connected">Go</button>
|
|
2499
|
+
<button (click)="refreshRemote()" [disabled]="!connected">Refresh</button>
|
|
2500
|
+
</div>
|
|
2501
|
+
</div>
|
|
2502
|
+
<div class="pane-filters">
|
|
2503
|
+
<div class="breadcrumbs" *ngIf="connected">
|
|
2504
|
+
<ng-container *ngFor="let part of getRemoteBreadcrumbs(); let i = index; let last = last">
|
|
2505
|
+
<button
|
|
2506
|
+
class="crumb-button"
|
|
2507
|
+
(click)="navigateRemoteBreadcrumb(i)"
|
|
2508
|
+
>
|
|
2509
|
+
{{ part.label }}
|
|
2510
|
+
</button>
|
|
2511
|
+
<span class="crumb-separator" *ngIf="!last">›</span>
|
|
2512
|
+
</ng-container>
|
|
2513
|
+
</div>
|
|
2514
|
+
<input [(ngModel)]="remoteFilter" placeholder="Filter files..." />
|
|
2515
|
+
<label class="show-hidden-toggle">
|
|
2516
|
+
<input type="checkbox" [(ngModel)]="showHiddenRemote" />
|
|
2517
|
+
<span>Show hidden</span>
|
|
2518
|
+
</label>
|
|
2519
|
+
</div>
|
|
2520
|
+
<div class="pane-list"
|
|
2521
|
+
(dragover)="onDragOver($event)"
|
|
2522
|
+
(drop)="onDropOnRemote($event)"
|
|
2523
|
+
>
|
|
2524
|
+
<div class="entry dim" *ngIf="!connected">
|
|
2525
|
+
<span class="name">Not connected</span>
|
|
2526
|
+
</div>
|
|
2527
|
+
<div class="entry header" *ngIf="connected">
|
|
2528
|
+
<span class="icon"></span>
|
|
2529
|
+
<span class="name sortable" (click)="setRemoteSort('name')">Name</span>
|
|
2530
|
+
<span class="size sortable" (click)="setRemoteSort('size')">Size</span>
|
|
2531
|
+
<span class="date sortable" (click)="setRemoteSort('modified')">Modified</span>
|
|
2532
|
+
</div>
|
|
2533
|
+
<div
|
|
2534
|
+
class="entry"
|
|
2535
|
+
*ngIf="connected && remotePath !== '/'"
|
|
2536
|
+
(dblclick)="remoteUp()"
|
|
2537
|
+
>
|
|
2538
|
+
<span class="icon">⬆</span>
|
|
2539
|
+
<span class="name">Go up</span>
|
|
2540
|
+
<span class="size"></span>
|
|
2541
|
+
<span class="date"></span>
|
|
2542
|
+
</div>
|
|
2543
|
+
<div
|
|
2544
|
+
class="entry"
|
|
2545
|
+
*ngFor="let e of getFilteredRemoteEntries()"
|
|
2546
|
+
(click)="selectRemote(e, $event)"
|
|
2547
|
+
(dblclick)="openRemote(e)"
|
|
2548
|
+
(mousedown)="onRemoteMouseDown(e, $event)"
|
|
2549
|
+
(contextmenu)="onRemoteContextMenu(e, $event)"
|
|
2550
|
+
(dragover)="onRemoteEntryDragOver(e, $event)"
|
|
2551
|
+
(drop)="onRemoteEntryDrop(e, $event)"
|
|
2552
|
+
[class.drop-target]="remoteDropActive"
|
|
2553
|
+
[class.selected]="isRemoteSelected(e)"
|
|
2554
|
+
[draggable]="connected"
|
|
2555
|
+
(dragstart)="onDragStartRemote($event, e)"
|
|
2556
|
+
>
|
|
2557
|
+
<span class="icon">{{ e.isDirectory ? '📁' : '📄' }}</span>
|
|
2558
|
+
<span class="name">{{ e.name }}</span>
|
|
2559
|
+
<span class="size">{{ getRemoteSizeDisplay(e) }}</span>
|
|
2560
|
+
<span class="date">{{ e.modified | date:'yyyy-MM-dd HH:mm' }}</span>
|
|
2561
|
+
</div>
|
|
2562
|
+
</div>
|
|
2563
|
+
<div class="pane-actions-bar">
|
|
2564
|
+
<div class="selection" *ngIf="selectedRemote.length">
|
|
2565
|
+
Selected: {{ selectedRemote.length === 1 ? selectedRemote[0].name : (selectedRemote.length + ' items') }}
|
|
2566
|
+
</div>
|
|
2567
|
+
<div class="action-inputs">
|
|
2568
|
+
<input [(ngModel)]="remoteActionName" placeholder="Name / new name" />
|
|
2569
|
+
<input [(ngModel)]="remoteActionPerms" placeholder="Perms (e.g. 755)" />
|
|
2570
|
+
</div>
|
|
2571
|
+
<div class="action-buttons">
|
|
2572
|
+
<button (click)="remoteRename()" [disabled]="selectedRemote.length !== 1">Rename</button>
|
|
2573
|
+
<button (click)="refreshRemote()" [disabled]="!connected">Refresh</button>
|
|
2574
|
+
<button (click)="remoteDelete()" [disabled]="!selectedRemote.length">Delete</button>
|
|
2575
|
+
<button (click)="remoteNewFolder()" [disabled]="!connected">New Folder</button>
|
|
2576
|
+
<button (click)="remoteEditPermissions()" [disabled]="selectedRemote.length !== 1 || !remoteActionPerms">Edit Permissions</button>
|
|
2577
|
+
<button (click)="remoteShowSize()" [disabled]="selectedRemote.length !== 1 || !selectedRemote[0].isDirectory">Show Size</button>
|
|
2578
|
+
<button (click)="remoteDownload()" [disabled]="!selectedRemote.length">Download</button>
|
|
2579
|
+
</div>
|
|
2580
|
+
</div>
|
|
2581
|
+
</div>
|
|
2582
|
+
</div>
|
|
2583
|
+
<div class="sftp-transfers" *ngIf="transfers.length">
|
|
2584
|
+
<div class="transfer" *ngFor="let t of transfers">
|
|
2585
|
+
<div class="transfer-main">
|
|
2586
|
+
<div class="transfer-title">
|
|
2587
|
+
<span class="direction">{{ t.direction === 'upload' ? 'Upload' : 'Download' }}</span>
|
|
2588
|
+
<span class="name">{{ t.name }}</span>
|
|
2589
|
+
</div>
|
|
2590
|
+
<div class="transfer-path">
|
|
2591
|
+
<span class="label">Remote:</span>
|
|
2592
|
+
<span class="value">{{ t.remotePath }}</span>
|
|
2593
|
+
</div>
|
|
2594
|
+
<div class="transfer-path">
|
|
2595
|
+
<span class="label">Local:</span>
|
|
2596
|
+
<span class="value">{{ t.localPath }}</span>
|
|
2597
|
+
</div>
|
|
2598
|
+
<div class="bar">
|
|
2599
|
+
<div class="fill" [style.width.%]="getTransferProgress(t.transfer)"></div>
|
|
2600
|
+
</div>
|
|
2601
|
+
</div>
|
|
2602
|
+
<div class="transfer-stats">
|
|
2603
|
+
<div class="percent">{{ getTransferProgress(t.transfer) | number:'1.0-0' }}%</div>
|
|
2604
|
+
<div class="speed">{{ formatSpeed(t.transfer.getSpeed()) }}</div>
|
|
2605
|
+
<button class="btn-cancel" (click)="cancelTransfer(t)" [disabled]="t.transfer.isComplete() || t.transfer.isCancelled()">Cancel</button>
|
|
2606
|
+
</div>
|
|
2607
|
+
</div>
|
|
2608
|
+
</div>
|
|
2609
|
+
|
|
2610
|
+
<div class="delete-overlay" *ngIf="deleteConfirmVisible">
|
|
2611
|
+
<div class="delete-dialog">
|
|
2612
|
+
<div class="delete-text">{{ deleteConfirmText }}</div>
|
|
2613
|
+
<div class="delete-buttons">
|
|
2614
|
+
<button class="danger" (click)="confirmDelete()">Delete</button>
|
|
2615
|
+
<button (click)="cancelDelete()">Cancel</button>
|
|
2616
|
+
</div>
|
|
2617
|
+
</div>
|
|
2618
|
+
</div>
|
|
2619
|
+
|
|
2620
|
+
<div class="delete-overlay" *ngIf="replaceConfirmVisible">
|
|
2621
|
+
<div class="delete-dialog">
|
|
2622
|
+
<div class="delete-text">{{ replaceConfirmText }}</div>
|
|
2623
|
+
<div class="delete-buttons">
|
|
2624
|
+
<button class="danger" (click)="confirmReplace()">Replace</button>
|
|
2625
|
+
<button (click)="cancelReplace()">Cancel</button>
|
|
2626
|
+
</div>
|
|
2627
|
+
</div>
|
|
2628
|
+
</div>
|
|
2629
|
+
|
|
2630
|
+
<div class="delete-overlay" *ngIf="inputDialogVisible">
|
|
2631
|
+
<div class="delete-dialog" (click)="$event.stopPropagation()">
|
|
2632
|
+
<div class="delete-text">{{ inputDialogTitle }}</div>
|
|
2633
|
+
<input
|
|
2634
|
+
class="dialog-input"
|
|
2635
|
+
[(ngModel)]="inputDialogValue"
|
|
2636
|
+
[placeholder]="inputDialogPlaceholder"
|
|
2637
|
+
(keyup.enter)="confirmInputDialog()"
|
|
2638
|
+
/>
|
|
2639
|
+
<div class="delete-buttons">
|
|
2640
|
+
<button class="danger" (click)="confirmInputDialog()" [disabled]="!inputDialogValue.trim()">OK</button>
|
|
2641
|
+
<button (click)="cancelInputDialog()">Cancel</button>
|
|
2642
|
+
</div>
|
|
2643
|
+
</div>
|
|
2644
|
+
</div>
|
|
2645
|
+
|
|
2646
|
+
<div
|
|
2647
|
+
class="local-menu"
|
|
2648
|
+
*ngIf="localMenuVisible"
|
|
2649
|
+
[style.left.px]="localMenuX"
|
|
2650
|
+
[style.top.px]="localMenuY"
|
|
2651
|
+
(click)="$event.stopPropagation()"
|
|
2652
|
+
>
|
|
2653
|
+
<div class="local-menu-item" *ngFor="let item of localMenuItems" (click)="onLocalMenuItemClick(item)">
|
|
2654
|
+
{{ item.label }}
|
|
2655
|
+
</div>
|
|
2656
|
+
</div>
|
|
2657
|
+
</div>
|
|
2651
2658
|
`,
|
|
2652
|
-
styles: [`
|
|
2653
|
-
.sftp-root { display: flex; flex-direction: column; height: 100%; padding: 10px; gap: 10px; position: relative; }
|
|
2654
|
-
button { padding: 6px 10px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.15); background: rgba(255,255,255,0.06); color: inherit; cursor: pointer; }
|
|
2655
|
-
button:disabled { opacity: 0.5; cursor: default; }
|
|
2656
|
-
.top-profiles { display: flex; justify-content: space-between; align-items: center; padding: 4px 8px 8px; gap: 12px; font-size: 11px; opacity: 0.9; }
|
|
2657
|
-
.top-profiles .current .label,
|
|
2658
|
-
.top-profiles .recent .label { text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.7; margin-right: 4px; }
|
|
2659
|
-
.top-profiles .value { font-weight: 600; }
|
|
2660
|
-
.top-profiles .profile-chip { padding: 2px 8px; border-radius: 999px; border: 1px solid rgba(255,255,255,0.18); background: rgba(255,255,255,0.04); color: inherit; cursor: pointer; font-size: 11px; }
|
|
2661
|
-
.top-profiles .profile-chip:hover { background: rgba(255,255,255,0.12); }
|
|
2662
|
-
.sftp-body { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; flex: 1; min-height: 0; }
|
|
2663
|
-
.pane { display: flex; flex-direction: column; border: 1px solid rgba(255,255,255,0.12); border-radius: 10px; overflow: hidden; min-height: 0; }
|
|
2664
|
-
.pane-title { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 10px; padding: 8px 10px; background: rgba(255,255,255,0.04); border-bottom: 1px solid rgba(255,255,255,0.08); }
|
|
2665
|
-
.pane-label { font-weight: 600; display: flex; align-items: baseline; gap: 6px; }
|
|
2666
|
-
.pane-sub { font-weight: 400; font-size: 11px; opacity: 0.75; }
|
|
2667
|
-
.pane-path { opacity: 0.8; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2668
|
-
.pane-path input { width: 100%; padding: 4px 6px; border-radius: 4px; border: 1px solid rgba(255,255,255,0.18); background: rgba(0,0,0,0.3); color: inherit; font-family: inherit; font-size: 12px; }
|
|
2669
|
-
.pane-actions { display: flex; gap: 8px; align-items: center; }
|
|
2670
|
-
.pane-actions .path-preset,
|
|
2671
|
-
.pane-actions .path-favorite { max-width: 150px; padding: 3px 6px; border-radius: 4px; border: 1px solid rgba(255,255,255,0.22); background: rgba(20,20,20,0.95); color: inherit; font-size: 11px; }
|
|
2672
|
-
.pane-actions .path-preset option { background: #151515; color: #f5f5f5; }
|
|
2673
|
-
.pane-actions .path-favorite option { background: #151515; color: #f5f5f5; }
|
|
2674
|
-
.pane-actions .fav-toggle { padding: 2px 6px; border-radius: 999px; border: 1px solid rgba(255,255,255,0.25); background: rgba(255,255,255,0.05); font-size: 11px; line-height: 1; }
|
|
2675
|
-
.pane-actions .fav-toggle.active { background: rgba(255,215,0,0.2); border-color: rgba(255,215,0,0.6); color: #ffd700; }
|
|
2676
|
-
.pane-filters { display: flex; align-items: center; gap: 8px; padding: 4px 8px; border-bottom: 1px solid rgba(255,255,255,0.06); background: rgba(0,0,0,0.12); }
|
|
2677
|
-
.pane-filters input { flex: 1; padding: 4px 6px; border-radius: 4px; border: 1px solid rgba(255,255,255,0.18); background: rgba(0,0,0,0.3); color: inherit; font-size: 12px; }
|
|
2678
|
-
.show-hidden-toggle { display: flex; align-items: center; gap: 4px; font-size: 11px; opacity: 0.8; white-space: nowrap; }
|
|
2679
|
-
.show-hidden-toggle input[type="checkbox"] { margin: 0; }
|
|
2680
|
-
.breadcrumbs { display: flex; flex-wrap: wrap; gap: 4px; font-size: 11px; opacity: 0.9; align-items: center; }
|
|
2681
|
-
.crumb-button { padding: 2px 6px; border-radius: 999px; border: 1px solid rgba(255,255,255,0.18); background: rgba(255,255,255,0.04); color: inherit; cursor: pointer; font-size: 11px; }
|
|
2682
|
-
.crumb-button:hover { background: rgba(255,255,255,0.10); }
|
|
2683
|
-
.crumb-separator { opacity: 0.6; }
|
|
2684
|
-
.pane-list { flex: 1; overflow: auto; padding: 4px; }
|
|
2685
|
-
.entry { display: grid; grid-template-columns: 24px minmax(0, 1.5fr) 80px 140px; gap: 8px; padding: 6px 8px; border-radius: 8px; user-select: none; align-items: center; }
|
|
2686
|
-
.entry:hover { background: rgba(255,255,255,0.06); }
|
|
2687
|
-
.entry.drop-target { outline: 1px dashed rgba(255,255,255,0.35); background: rgba(80, 160, 255, 0.10); }
|
|
2688
|
-
.entry.dim { opacity: 0.7; }
|
|
2689
|
-
.icon { text-align: center; opacity: 0.85; }
|
|
2690
|
-
.name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2691
|
-
.size { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; text-align: right; opacity: 0.8; }
|
|
2692
|
-
.date { font-size: 11px; opacity: 0.75; text-align: right; white-space: nowrap; }
|
|
2693
|
-
.entry.header { font-weight: 600; opacity: 0.9; background: rgba(255,255,255,0.02); }
|
|
2694
|
-
.sortable { cursor: pointer; }
|
|
2695
|
-
.entry.selected { background: rgba(80,160,255,0.18); }
|
|
2696
|
-
.pane-actions-bar { display: flex; flex-direction: column; gap: 4px; padding: 6px 8px; border-top: 1px solid rgba(255,255,255,0.06); background: rgba(0,0,0,0.18); }
|
|
2697
|
-
.pane-actions-bar .selection { font-size: 11px; opacity: 0.85; }
|
|
2698
|
-
.pane-actions-bar .action-inputs { display: flex; gap: 6px; }
|
|
2699
|
-
.pane-actions-bar .action-inputs input { flex: 1; padding: 3px 6px; border-radius: 4px; border: 1px solid rgba(255,255,255,0.18); background: rgba(0,0,0,0.3); color: inherit; font-size: 11px; }
|
|
2700
|
-
.pane-actions-bar .action-buttons { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px; }
|
|
2701
|
-
.sftp-transfers { margin-top: 8px; display: flex; flex-direction: column; gap: 6px; max-height: 120px; overflow-y: auto; }
|
|
2702
|
-
.transfer { display: grid; grid-template-columns: 1fr auto; gap: 8px; padding: 6px 8px; border-radius: 8px; background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.08); font-size: 11px; }
|
|
2703
|
-
.transfer-title { display: flex; gap: 6px; align-items: baseline; margin-bottom: 2px; }
|
|
2704
|
-
.transfer-title .direction { text-transform: uppercase; letter-spacing: 0.04em; opacity: 0.7; font-weight: 600; font-size: 10px; }
|
|
2705
|
-
.transfer-title .name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2706
|
-
.transfer-path { display: flex; gap: 4px; opacity: 0.75; }
|
|
2707
|
-
.transfer-path .label { min-width: 48px; }
|
|
2708
|
-
.transfer-path .value { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
|
|
2709
|
-
.bar { position: relative; height: 4px; border-radius: 999px; background: rgba(255,255,255,0.07); margin-top: 4px; overflow: hidden; }
|
|
2710
|
-
.bar .fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: inherit; background: linear-gradient(90deg, #4dabff, #78ffce); transition: width 0.15s linear; }
|
|
2711
|
-
.transfer-stats { display: flex; flex-direction: column; justify-content: center; align-items: flex-end; gap: 4px; opacity: 0.8; }
|
|
2712
|
-
.transfer-stats .percent { font-weight: 600; }
|
|
2713
|
-
.transfer-stats .speed { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
|
|
2714
|
-
.btn-cancel { padding: 2px 6px; font-size: 10px; border-radius: 999px; }
|
|
2715
|
-
.delete-overlay { position: absolute; inset: 0; background: rgba(0,0,0,0.55); display: flex; align-items: center; justify-content: center; z-index: 20; }
|
|
2716
|
-
.delete-dialog { min-width: 260px; max-width: 360px; padding: 14px 16px; border-radius: 10px; background: rgba(20,20,20,0.96); border: 1px solid rgba(255,255,255,0.15); box-shadow: 0 18px 45px rgba(0,0,0,0.75); display: flex; flex-direction: column; gap: 10px; }
|
|
2717
|
-
.delete-text { font-size: 13px; }
|
|
2718
|
-
.dialog-input { width: 100%; padding: 8px 10px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.18); background: rgba(0,0,0,0.3); color: inherit; font-size: 13px; }
|
|
2719
|
-
.delete-buttons { display: flex; justify-content: flex-end; gap: 8px; }
|
|
2720
|
-
.delete-buttons .danger { background: rgba(255,80,80,0.85); border-color: rgba(255,120,120,0.85); }
|
|
2721
|
-
.local-menu { position: absolute; min-width: 180px; max-width: 260px; max-height: 260px; overflow-y: auto; padding: 4px 0; border-radius: 10px; background: rgba(18,18,22,0.98); border: 1px solid rgba(255,255,255,0.16); box-shadow: 0 18px 45px rgba(0,0,0,0.8); z-index: 30; backdrop-filter: blur(12px); }
|
|
2722
|
-
.local-menu-item { padding: 6px 12px; font-size: 12px; cursor: pointer; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; }
|
|
2723
|
-
.local-menu-item:hover { background: linear-gradient(90deg, rgba(120,200,255,0.24), rgba(120,255,206,0.15)); }
|
|
2659
|
+
styles: [`
|
|
2660
|
+
.sftp-root { display: flex; flex-direction: column; height: 100%; padding: 10px; gap: 10px; position: relative; }
|
|
2661
|
+
button { padding: 6px 10px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.15); background: rgba(255,255,255,0.06); color: inherit; cursor: pointer; }
|
|
2662
|
+
button:disabled { opacity: 0.5; cursor: default; }
|
|
2663
|
+
.top-profiles { display: flex; justify-content: space-between; align-items: center; padding: 4px 8px 8px; gap: 12px; font-size: 11px; opacity: 0.9; }
|
|
2664
|
+
.top-profiles .current .label,
|
|
2665
|
+
.top-profiles .recent .label { text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.7; margin-right: 4px; }
|
|
2666
|
+
.top-profiles .value { font-weight: 600; }
|
|
2667
|
+
.top-profiles .profile-chip { padding: 2px 8px; border-radius: 999px; border: 1px solid rgba(255,255,255,0.18); background: rgba(255,255,255,0.04); color: inherit; cursor: pointer; font-size: 11px; }
|
|
2668
|
+
.top-profiles .profile-chip:hover { background: rgba(255,255,255,0.12); }
|
|
2669
|
+
.sftp-body { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; flex: 1; min-height: 0; }
|
|
2670
|
+
.pane { display: flex; flex-direction: column; border: 1px solid rgba(255,255,255,0.12); border-radius: 10px; overflow: hidden; min-height: 0; }
|
|
2671
|
+
.pane-title { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 10px; padding: 8px 10px; background: rgba(255,255,255,0.04); border-bottom: 1px solid rgba(255,255,255,0.08); }
|
|
2672
|
+
.pane-label { font-weight: 600; display: flex; align-items: baseline; gap: 6px; }
|
|
2673
|
+
.pane-sub { font-weight: 400; font-size: 11px; opacity: 0.75; }
|
|
2674
|
+
.pane-path { opacity: 0.8; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2675
|
+
.pane-path input { width: 100%; padding: 4px 6px; border-radius: 4px; border: 1px solid rgba(255,255,255,0.18); background: rgba(0,0,0,0.3); color: inherit; font-family: inherit; font-size: 12px; }
|
|
2676
|
+
.pane-actions { display: flex; gap: 8px; align-items: center; }
|
|
2677
|
+
.pane-actions .path-preset,
|
|
2678
|
+
.pane-actions .path-favorite { max-width: 150px; padding: 3px 6px; border-radius: 4px; border: 1px solid rgba(255,255,255,0.22); background: rgba(20,20,20,0.95); color: inherit; font-size: 11px; }
|
|
2679
|
+
.pane-actions .path-preset option { background: #151515; color: #f5f5f5; }
|
|
2680
|
+
.pane-actions .path-favorite option { background: #151515; color: #f5f5f5; }
|
|
2681
|
+
.pane-actions .fav-toggle { padding: 2px 6px; border-radius: 999px; border: 1px solid rgba(255,255,255,0.25); background: rgba(255,255,255,0.05); font-size: 11px; line-height: 1; }
|
|
2682
|
+
.pane-actions .fav-toggle.active { background: rgba(255,215,0,0.2); border-color: rgba(255,215,0,0.6); color: #ffd700; }
|
|
2683
|
+
.pane-filters { display: flex; align-items: center; gap: 8px; padding: 4px 8px; border-bottom: 1px solid rgba(255,255,255,0.06); background: rgba(0,0,0,0.12); }
|
|
2684
|
+
.pane-filters input { flex: 1; padding: 4px 6px; border-radius: 4px; border: 1px solid rgba(255,255,255,0.18); background: rgba(0,0,0,0.3); color: inherit; font-size: 12px; }
|
|
2685
|
+
.show-hidden-toggle { display: flex; align-items: center; gap: 4px; font-size: 11px; opacity: 0.8; white-space: nowrap; }
|
|
2686
|
+
.show-hidden-toggle input[type="checkbox"] { margin: 0; }
|
|
2687
|
+
.breadcrumbs { display: flex; flex-wrap: wrap; gap: 4px; font-size: 11px; opacity: 0.9; align-items: center; }
|
|
2688
|
+
.crumb-button { padding: 2px 6px; border-radius: 999px; border: 1px solid rgba(255,255,255,0.18); background: rgba(255,255,255,0.04); color: inherit; cursor: pointer; font-size: 11px; }
|
|
2689
|
+
.crumb-button:hover { background: rgba(255,255,255,0.10); }
|
|
2690
|
+
.crumb-separator { opacity: 0.6; }
|
|
2691
|
+
.pane-list { flex: 1; overflow: auto; padding: 4px; }
|
|
2692
|
+
.entry { display: grid; grid-template-columns: 24px minmax(0, 1.5fr) 80px 140px; gap: 8px; padding: 6px 8px; border-radius: 8px; user-select: none; align-items: center; }
|
|
2693
|
+
.entry:hover { background: rgba(255,255,255,0.06); }
|
|
2694
|
+
.entry.drop-target { outline: 1px dashed rgba(255,255,255,0.35); background: rgba(80, 160, 255, 0.10); }
|
|
2695
|
+
.entry.dim { opacity: 0.7; }
|
|
2696
|
+
.icon { text-align: center; opacity: 0.85; }
|
|
2697
|
+
.name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2698
|
+
.size { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; text-align: right; opacity: 0.8; }
|
|
2699
|
+
.date { font-size: 11px; opacity: 0.75; text-align: right; white-space: nowrap; }
|
|
2700
|
+
.entry.header { font-weight: 600; opacity: 0.9; background: rgba(255,255,255,0.02); }
|
|
2701
|
+
.sortable { cursor: pointer; }
|
|
2702
|
+
.entry.selected { background: rgba(80,160,255,0.18); }
|
|
2703
|
+
.pane-actions-bar { display: flex; flex-direction: column; gap: 4px; padding: 6px 8px; border-top: 1px solid rgba(255,255,255,0.06); background: rgba(0,0,0,0.18); }
|
|
2704
|
+
.pane-actions-bar .selection { font-size: 11px; opacity: 0.85; }
|
|
2705
|
+
.pane-actions-bar .action-inputs { display: flex; gap: 6px; }
|
|
2706
|
+
.pane-actions-bar .action-inputs input { flex: 1; padding: 3px 6px; border-radius: 4px; border: 1px solid rgba(255,255,255,0.18); background: rgba(0,0,0,0.3); color: inherit; font-size: 11px; }
|
|
2707
|
+
.pane-actions-bar .action-buttons { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px; }
|
|
2708
|
+
.sftp-transfers { margin-top: 8px; display: flex; flex-direction: column; gap: 6px; max-height: 120px; overflow-y: auto; }
|
|
2709
|
+
.transfer { display: grid; grid-template-columns: 1fr auto; gap: 8px; padding: 6px 8px; border-radius: 8px; background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.08); font-size: 11px; }
|
|
2710
|
+
.transfer-title { display: flex; gap: 6px; align-items: baseline; margin-bottom: 2px; }
|
|
2711
|
+
.transfer-title .direction { text-transform: uppercase; letter-spacing: 0.04em; opacity: 0.7; font-weight: 600; font-size: 10px; }
|
|
2712
|
+
.transfer-title .name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2713
|
+
.transfer-path { display: flex; gap: 4px; opacity: 0.75; }
|
|
2714
|
+
.transfer-path .label { min-width: 48px; }
|
|
2715
|
+
.transfer-path .value { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
|
|
2716
|
+
.bar { position: relative; height: 4px; border-radius: 999px; background: rgba(255,255,255,0.07); margin-top: 4px; overflow: hidden; }
|
|
2717
|
+
.bar .fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: inherit; background: linear-gradient(90deg, #4dabff, #78ffce); transition: width 0.15s linear; }
|
|
2718
|
+
.transfer-stats { display: flex; flex-direction: column; justify-content: center; align-items: flex-end; gap: 4px; opacity: 0.8; }
|
|
2719
|
+
.transfer-stats .percent { font-weight: 600; }
|
|
2720
|
+
.transfer-stats .speed { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
|
|
2721
|
+
.btn-cancel { padding: 2px 6px; font-size: 10px; border-radius: 999px; }
|
|
2722
|
+
.delete-overlay { position: absolute; inset: 0; background: rgba(0,0,0,0.55); display: flex; align-items: center; justify-content: center; z-index: 20; }
|
|
2723
|
+
.delete-dialog { min-width: 260px; max-width: 360px; padding: 14px 16px; border-radius: 10px; background: rgba(20,20,20,0.96); border: 1px solid rgba(255,255,255,0.15); box-shadow: 0 18px 45px rgba(0,0,0,0.75); display: flex; flex-direction: column; gap: 10px; }
|
|
2724
|
+
.delete-text { font-size: 13px; }
|
|
2725
|
+
.dialog-input { width: 100%; padding: 8px 10px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.18); background: rgba(0,0,0,0.3); color: inherit; font-size: 13px; }
|
|
2726
|
+
.delete-buttons { display: flex; justify-content: flex-end; gap: 8px; }
|
|
2727
|
+
.delete-buttons .danger { background: rgba(255,80,80,0.85); border-color: rgba(255,120,120,0.85); }
|
|
2728
|
+
.local-menu { position: absolute; min-width: 180px; max-width: 260px; max-height: 260px; overflow-y: auto; padding: 4px 0; border-radius: 10px; background: rgba(18,18,22,0.98); border: 1px solid rgba(255,255,255,0.16); box-shadow: 0 18px 45px rgba(0,0,0,0.8); z-index: 30; backdrop-filter: blur(12px); }
|
|
2729
|
+
.local-menu-item { padding: 6px 12px; font-size: 12px; cursor: pointer; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; }
|
|
2730
|
+
.local-menu-item:hover { background: linear-gradient(90deg, rgba(120,200,255,0.24), rgba(120,255,206,0.15)); }
|
|
2724
2731
|
`],
|
|
2725
2732
|
}),
|
|
2726
2733
|
__metadata("design:paramtypes", [_angular_core__WEBPACK_IMPORTED_MODULE_5__.Injector,
|
|
@@ -2731,6 +2738,57 @@ SftpManagerTabComponent = __decorate([
|
|
|
2731
2738
|
|
|
2732
2739
|
|
|
2733
2740
|
|
|
2741
|
+
/***/ },
|
|
2742
|
+
|
|
2743
|
+
/***/ "./src/sftp-recovery-provider.ts"
|
|
2744
|
+
/*!***************************************!*\
|
|
2745
|
+
!*** ./src/sftp-recovery-provider.ts ***!
|
|
2746
|
+
\***************************************/
|
|
2747
|
+
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
2748
|
+
|
|
2749
|
+
__webpack_require__.r(__webpack_exports__);
|
|
2750
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
2751
|
+
/* harmony export */ SFTP_RECOVERY_TYPE: () => (/* binding */ SFTP_RECOVERY_TYPE),
|
|
2752
|
+
/* harmony export */ SftpTabRecoveryProvider: () => (/* binding */ SftpTabRecoveryProvider)
|
|
2753
|
+
/* harmony export */ });
|
|
2754
|
+
/* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @angular/core */ "@angular/core");
|
|
2755
|
+
/* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_angular_core__WEBPACK_IMPORTED_MODULE_0__);
|
|
2756
|
+
/* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! tabby-core */ "tabby-core");
|
|
2757
|
+
/* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(tabby_core__WEBPACK_IMPORTED_MODULE_1__);
|
|
2758
|
+
/* harmony import */ var _sftp_manager_tab_component__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./sftp-manager-tab.component */ "./src/sftp-manager-tab.component.ts");
|
|
2759
|
+
var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
|
|
2760
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2761
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2762
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
2763
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2764
|
+
};
|
|
2765
|
+
|
|
2766
|
+
|
|
2767
|
+
|
|
2768
|
+
const SFTP_RECOVERY_TYPE = 'tabby-sftp-ui:sftp-tab';
|
|
2769
|
+
let SftpTabRecoveryProvider = class SftpTabRecoveryProvider extends tabby_core__WEBPACK_IMPORTED_MODULE_1__.TabRecoveryProvider {
|
|
2770
|
+
async applicableTo(recoveryToken) {
|
|
2771
|
+
return recoveryToken?.type === SFTP_RECOVERY_TYPE;
|
|
2772
|
+
}
|
|
2773
|
+
async recover(_recoveryToken) {
|
|
2774
|
+
// Restore a stub tab that immediately closes itself and its SplitTab wrapper.
|
|
2775
|
+
return {
|
|
2776
|
+
type: _sftp_manager_tab_component__WEBPACK_IMPORTED_MODULE_2__.SftpManagerTabComponent,
|
|
2777
|
+
inputs: {
|
|
2778
|
+
sshSession: null,
|
|
2779
|
+
profile: null,
|
|
2780
|
+
recoveredStub: true,
|
|
2781
|
+
},
|
|
2782
|
+
};
|
|
2783
|
+
}
|
|
2784
|
+
};
|
|
2785
|
+
SftpTabRecoveryProvider = __decorate([
|
|
2786
|
+
(0,_angular_core__WEBPACK_IMPORTED_MODULE_0__.Injectable)()
|
|
2787
|
+
], SftpTabRecoveryProvider);
|
|
2788
|
+
|
|
2789
|
+
|
|
2790
|
+
|
|
2791
|
+
|
|
2734
2792
|
/***/ },
|
|
2735
2793
|
|
|
2736
2794
|
/***/ "./src/sftp-terminal-decorator.ts"
|
|
@@ -2768,7 +2826,13 @@ let SftpTerminalDecorator = class SftpTerminalDecorator extends tabby_terminal__
|
|
|
2768
2826
|
attach(terminal) {
|
|
2769
2827
|
super.attach(terminal);
|
|
2770
2828
|
// Best-effort DOM injection: place button near the existing Reconnect button if present.
|
|
2829
|
+
// Only inject for SSH tabs (with a live sshSession property).
|
|
2771
2830
|
const tryInsert = () => {
|
|
2831
|
+
// Skip for local terminals (PowerShell, CMD, etc.) - only SSH tabs need this button.
|
|
2832
|
+
const sshSession = terminal.sshSession;
|
|
2833
|
+
if (!sshSession) {
|
|
2834
|
+
return false;
|
|
2835
|
+
}
|
|
2772
2836
|
try {
|
|
2773
2837
|
const host = terminal.element?.nativeElement ?? null;
|
|
2774
2838
|
if (!host) {
|
|
@@ -3162,6 +3226,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
3162
3226
|
/* harmony import */ var _sftp_hotkey__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./sftp-hotkey */ "./src/sftp-hotkey.ts");
|
|
3163
3227
|
/* harmony import */ var _sftp_ui_service__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./sftp-ui.service */ "./src/sftp-ui.service.ts");
|
|
3164
3228
|
/* harmony import */ var _sftp_terminal_decorator__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./sftp-terminal-decorator */ "./src/sftp-terminal-decorator.ts");
|
|
3229
|
+
/* harmony import */ var _sftp_recovery_provider__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./sftp-recovery-provider */ "./src/sftp-recovery-provider.ts");
|
|
3165
3230
|
var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
|
|
3166
3231
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3167
3232
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -3181,6 +3246,7 @@ var __metadata = (undefined && undefined.__metadata) || function (k, v) {
|
|
|
3181
3246
|
|
|
3182
3247
|
|
|
3183
3248
|
|
|
3249
|
+
|
|
3184
3250
|
let SftpUiModule = class SftpUiModule {
|
|
3185
3251
|
constructor(_) { }
|
|
3186
3252
|
};
|
|
@@ -3197,6 +3263,7 @@ SftpUiModule = __decorate([
|
|
|
3197
3263
|
{ provide: tabby_core__WEBPACK_IMPORTED_MODULE_3__.TabContextMenuItemProvider, useClass: _sftp_context_menu__WEBPACK_IMPORTED_MODULE_6__.SftpContextMenuProvider, multi: true },
|
|
3198
3264
|
{ provide: tabby_core__WEBPACK_IMPORTED_MODULE_3__.HotkeyProvider, useClass: _sftp_hotkey__WEBPACK_IMPORTED_MODULE_7__.SftpUiHotkeyProvider, multi: true },
|
|
3199
3265
|
{ provide: tabby_terminal__WEBPACK_IMPORTED_MODULE_4__.TerminalDecorator, useClass: _sftp_terminal_decorator__WEBPACK_IMPORTED_MODULE_9__.SftpTerminalDecorator, multi: true },
|
|
3266
|
+
{ provide: tabby_core__WEBPACK_IMPORTED_MODULE_3__.TabRecoveryProvider, useClass: _sftp_recovery_provider__WEBPACK_IMPORTED_MODULE_10__.SftpTabRecoveryProvider, multi: true },
|
|
3200
3267
|
_sftp_ui_service__WEBPACK_IMPORTED_MODULE_8__.SftpUiService,
|
|
3201
3268
|
],
|
|
3202
3269
|
}),
|