added experimental SFTP implementation - fixes #296

This commit is contained in:
Eugene Pankov
2021-06-12 01:18:07 +02:00
parent 019cba06d4
commit a397884d3c
16 changed files with 462 additions and 6 deletions

View File

@@ -88,6 +88,21 @@ export abstract class PlatformService {
abstract startDownload (name: string, size: number): Promise<FileDownload|null>
abstract startUpload (options?: FileUploadOptions): Promise<FileUpload[]>
startUploadFromDragEvent (event: DragEvent): FileUpload[] {
const result: FileUpload[] = []
if (!event.dataTransfer) {
return []
}
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < event.dataTransfer.files.length; i++) {
const file = event.dataTransfer.files[i]
const transfer = new DropUpload(file)
this.fileTransferStarted.next(transfer)
result.push(transfer)
}
return result
}
getConfigPath (): string|null {
return null
}
@@ -144,3 +159,36 @@ export abstract class PlatformService {
abstract showMessageBox (options: MessageBoxOptions): Promise<MessageBoxResult>
abstract quit (): void
}
class DropUpload extends FileUpload {
private stream: ReadableStream
private reader: ReadableStreamDefaultReader
constructor (private file: File) {
super()
this.stream = this.file.stream()
this.reader = this.stream.getReader()
}
getName (): string {
return this.file.name
}
getSize (): number {
return this.file.size
}
async read (): Promise<Buffer> {
const result: any = await this.reader.read()
if (result.done || !result.value) {
return Buffer.from('')
}
const chunk = Buffer.from(result.value)
this.increaseProgress(chunk.length)
return chunk
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
close (): void { }
}

View File

@@ -0,0 +1 @@
i.fas.fa-upload

View File

@@ -0,0 +1,24 @@
.drop-zone-hint {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, .5);
pointer-events: none;
z-index: 1;
display: flex;
transition: .25s opacity ease-out;
opacity: 0;
&.visible {
opacity: 1;
}
i {
font-size: 48px;
align-self: center;
margin: auto;
text-align: center;
}
}

View File

@@ -0,0 +1,49 @@
import { Directive, Output, ElementRef, EventEmitter, AfterViewInit } from '@angular/core'
import { FileUpload, PlatformService } from '../api/platform'
import './dropZone.directive.scss'
/** @hidden */
@Directive({
selector: '[dropZone]',
})
export class DropZoneDirective implements AfterViewInit {
@Output() transfer = new EventEmitter<FileUpload>()
private dropHint?: HTMLElement
constructor (
private el: ElementRef,
private platform: PlatformService,
) { }
ngAfterViewInit (): void {
this.el.nativeElement.addEventListener('dragover', () => {
if (!this.dropHint) {
this.dropHint = document.createElement('div')
this.dropHint.className = 'drop-zone-hint'
this.dropHint.innerHTML = require('./dropZone.directive.pug')
this.el.nativeElement.appendChild(this.dropHint)
setTimeout(() => {
this.dropHint!.classList.add('visible')
})
}
})
this.el.nativeElement.addEventListener('drop', (event: DragEvent) => {
this.removeHint()
for (const transfer of this.platform.startUploadFromDragEvent(event)) {
this.transfer.emit(transfer)
}
})
this.el.nativeElement.addEventListener('dragleave', () => {
this.removeHint()
})
}
private removeHint () {
const element = this.dropHint
delete this.dropHint
element?.classList.remove('visible')
setTimeout(() => {
element?.remove()
}, 500)
}
}

View File

@@ -25,6 +25,7 @@ import { TransfersMenuComponent } from './components/transfersMenu.component'
import { AutofocusDirective } from './directives/autofocus.directive'
import { FastHtmlBindDirective } from './directives/fastHtmlBind.directive'
import { DropZoneDirective } from './directives/dropZone.directive'
import { Theme, CLIHandler, TabContextMenuItemProvider, TabRecoveryProvider, HotkeyProvider, ConfigProvider } from './api'
@@ -83,6 +84,7 @@ const PROVIDERS = [
UnlockVaultModalComponent,
WelcomeTabComponent,
TransfersMenuComponent,
DropZoneDirective,
],
entryComponents: [
RenameTabModalComponent,
@@ -96,6 +98,7 @@ const PROVIDERS = [
CheckboxComponent,
ToggleComponent,
AutofocusDirective,
DropZoneDirective,
],
})
export default class AppModule { // eslint-disable-line @typescript-eslint/no-extraneous-class

View File

@@ -38,7 +38,7 @@ $theme-colors: (
warning: $orange,
danger: $red,
light: $gray-300,
dark: $gray-800,
dark: #0e151d,
rare: $purple
);
@@ -150,7 +150,7 @@ $navbar-padding-x: 0;
$dropdown-bg: $content-bg-solid;
$dropdown-color: $body-color;
$dropdown-border-width: 1px;
$dropdown-box-shadow: 0 .5rem 1rem rgba($black,.175);
$dropdown-box-shadow: 0 0 1rem rgba($black, .25), 0 1px 1px rgba($black, .12);
$dropdown-header-color: $gray-500;
$dropdown-link-color: $body-color;