Files
tabby/tabby-core/src/api/platform.ts
2025-07-23 08:38:25 +02:00

312 lines
8.5 KiB
TypeScript

import { MenuItemOptions } from './menu'
import { Subject, Observable } from 'rxjs'
/* eslint-disable @typescript-eslint/no-unused-vars */
export interface ClipboardContent {
text: string
html?: string
}
export interface MessageBoxOptions {
type: 'warning'|'error'
message: string
detail?: string
buttons: string[]
defaultId?: number
cancelId?: number
}
export interface MessageBoxResult {
response: number
}
export abstract class FileTransfer {
abstract getName (): string
abstract getSize (): number
abstract close (): void
getSpeed (): number {
return this.lastChunkSpeed
}
getCompletedBytes (): number {
return this.completedBytes
}
getStatus (): string {
return this.status
}
getTotalSize (): number {
return this.totalSize
}
isComplete (): boolean {
return this.completed
}
isCancelled (): boolean {
return this.cancelled
}
cancel (): void {
this.cancelled = true
this.close()
}
setStatus (status: string): void {
this.status = status
}
setTotalSize (size: number): void {
this.totalSize = size
}
setCompleted (completed: boolean): void {
this.completed = completed
}
protected increaseProgress (bytes: number): void {
if (!bytes) {
return
}
this.completedBytes += bytes
this.lastChunkSpeed = bytes * 1000 / (Date.now() - this.lastChunkStartTime)
this.lastChunkStartTime = Date.now()
}
private completedBytes = 0
private totalSize = 0
private lastChunkStartTime = Date.now()
private lastChunkSpeed = 0
private cancelled = false
private completed = false
private status = ''
}
export abstract class FileDownload extends FileTransfer {
abstract write (buffer: Uint8Array): Promise<void>
}
export abstract class DirectoryDownload extends FileTransfer {
abstract createDirectory (relativePath: string): Promise<void>
abstract createFile (relativePath: string, mode: number, size: number): Promise<FileDownload>
}
export abstract class FileUpload extends FileTransfer {
abstract getMode (): number
abstract read (): Promise<Uint8Array>
async readAll (): Promise<Uint8Array> {
const result = new Uint8Array(this.getSize())
let pos = 0
while (true) {
const buf = await this.read()
if (!buf.length) {
break
}
result.set(buf, pos)
pos += buf.length
}
return result
}
}
export interface FileUploadOptions {
multiple: boolean
}
export class DirectoryUpload {
private childrens: (FileUpload|DirectoryUpload)[] = []
constructor (private name = '') {
// Just set name for now.
}
getName (): string {
return this.name
}
getChildrens (): (FileUpload|DirectoryUpload)[] {
return this.childrens
}
pushChildren (item: FileUpload|DirectoryUpload): void {
this.childrens.push(item)
}
}
export type PlatformTheme = 'light'|'dark'
export abstract class PlatformService {
supportsWindowControls = false
get fileTransferStarted$ (): Observable<FileTransfer> { return this.fileTransferStarted }
get displayMetricsChanged$ (): Observable<void> { return this.displayMetricsChanged }
get themeChanged$ (): Observable<PlatformTheme> { return this.themeChanged }
protected fileTransferStarted = new Subject<FileTransfer>()
protected displayMetricsChanged = new Subject<void>()
protected themeChanged = new Subject<PlatformTheme>()
abstract readClipboard (): string
abstract setClipboard (content: ClipboardContent): void
abstract loadConfig (): Promise<string>
abstract saveConfig (content: string): Promise<void>
abstract startDownload (name: string, mode: number, size: number): Promise<FileDownload|null>
abstract startDownloadDirectory (name: string, estimatedSize?: number): Promise<DirectoryDownload|null>
abstract startUpload (options?: FileUploadOptions): Promise<FileUpload[]>
abstract startUploadDirectory (paths?: string[]): Promise<DirectoryUpload>
async startUploadFromDragEvent (event: DragEvent, multiple = false): Promise<DirectoryUpload> {
const result = new DirectoryUpload()
if (!event.dataTransfer) {
return Promise.resolve(result)
}
const traverseFileTree = (item: any, root: DirectoryUpload = result): Promise<void> => {
return new Promise((resolve) => {
if (item.isFile) {
item.file((file: File) => {
const transfer = new HTMLFileUpload(file)
this.fileTransferStarted.next(transfer)
root.pushChildren(transfer)
resolve()
})
} else if (item.isDirectory) {
const dirReader = item.createReader()
const childrenFolder = new DirectoryUpload(item.name)
dirReader.readEntries(async (entries: any[]) => {
for (const entry of entries) {
await traverseFileTree(entry, childrenFolder)
}
resolve()
})
root.pushChildren(childrenFolder)
} else {
resolve()
}
})
}
const promises: Promise<void>[] = []
const items = event.dataTransfer.items
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < items.length; i++) {
const item = items[i].webkitGetAsEntry()
if (item) {
promises.push(traverseFileTree(item))
if (!multiple) {
break
}
}
}
return Promise.all(promises).then(() => result)
}
getConfigPath (): string|null {
return null
}
showItemInFolder (path: string): void {
throw new Error('Not implemented')
}
async isProcessRunning (name: string): Promise<boolean> {
return false
}
async installPlugin (name: string, version: string): Promise<void> {
throw new Error('Not implemented')
}
async uninstallPlugin (name: string): Promise<void> {
throw new Error('Not implemented')
}
getWinSCPPath (): string|null {
throw new Error('Not implemented')
}
async exec (app: string, argv: string[]): Promise<void> {
throw new Error('Not implemented')
}
isShellIntegrationSupported (): boolean {
return false
}
async isShellIntegrationInstalled (): Promise<boolean> {
return false
}
async installShellIntegration (): Promise<void> {
throw new Error('Not implemented')
}
async uninstallShellIntegration (): Promise<void> {
throw new Error('Not implemented')
}
openPath (path: string): void {
throw new Error('Not implemented')
}
getTheme (): PlatformTheme {
return 'dark'
}
abstract getOSRelease (): string
abstract getAppVersion (): string
abstract openExternal (url: string): void
abstract listFonts (): Promise<string[]>
abstract setErrorHandler (handler: (_: any) => void): void
abstract popupContextMenu (menu: MenuItemOptions[], event?: MouseEvent): void
abstract showMessageBox (options: MessageBoxOptions): Promise<MessageBoxResult>
abstract pickDirectory (): Promise<string | null>
abstract quit (): void
}
export class HTMLFileUpload 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
}
getMode (): number {
return 0o644
}
getSize (): number {
return this.file.size
}
async read (): Promise<Uint8Array> {
const result: any = await this.reader.read()
if (result.done || !result.value) {
return new Uint8Array(0)
}
const chunk = new Uint8Array(result.value)
this.increaseProgress(chunk.length)
return chunk
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
bringToFront (): void { }
// eslint-disable-next-line @typescript-eslint/no-empty-function
close (): void { }
}