platform.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import '@vaadin/vaadin-context-menu'
  2. import copyToClipboard from 'copy-text-to-clipboard'
  3. import { Injectable, Inject } from '@angular/core'
  4. import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
  5. import { PlatformService, ClipboardContent, MenuItemOptions, MessageBoxOptions, MessageBoxResult, FileUpload, FileUploadOptions, FileDownload, DirectoryDownload, HTMLFileUpload, DirectoryUpload } from 'tabby-core'
  6. // eslint-disable-next-line no-duplicate-imports
  7. import type { ContextMenuElement, ContextMenuItem } from '@vaadin/vaadin-context-menu'
  8. import { MessageBoxModalComponent } from './components/messageBoxModal.component'
  9. import './styles.scss'
  10. @Injectable()
  11. export class WebPlatformService extends PlatformService {
  12. private menu: ContextMenuElement
  13. private contextMenuHandlers = new Map<ContextMenuItem, () => void>()
  14. private fileSelector: HTMLInputElement
  15. constructor (
  16. // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
  17. @Inject('WEB_CONNECTOR') private connector: any,
  18. private ngbModal: NgbModal,
  19. ) {
  20. super()
  21. this.menu = window.document.createElement('vaadin-context-menu')
  22. this.menu.addEventListener('item-selected', e => {
  23. this.contextMenuHandlers.get(e.detail.value)?.()
  24. })
  25. document.body.appendChild(this.menu)
  26. this.fileSelector = document.createElement('input')
  27. this.fileSelector.type = 'file'
  28. this.fileSelector.style.visibility = 'hidden'
  29. document.body.appendChild(this.fileSelector)
  30. }
  31. readClipboard (): string {
  32. return ''
  33. }
  34. setClipboard (content: ClipboardContent): void {
  35. copyToClipboard(content.text)
  36. }
  37. async loadConfig (): Promise<string> {
  38. return this.connector.loadConfig()
  39. }
  40. async saveConfig (content: string): Promise<void> {
  41. await this.connector.saveConfig(content)
  42. }
  43. getOSRelease (): string {
  44. return '1.0'
  45. }
  46. openExternal (url: string): void {
  47. window.open(url)
  48. }
  49. getAppVersion (): string {
  50. return this.connector.getAppVersion()
  51. }
  52. async listFonts (): Promise<string[]> {
  53. return []
  54. }
  55. popupContextMenu (menu: MenuItemOptions[], event?: MouseEvent): void {
  56. this.contextMenuHandlers.clear()
  57. this.menu.items = menu
  58. .filter(x => x.type !== 'separator')
  59. .map(x => this.remapMenuItem(x))
  60. setTimeout(() => {
  61. this.menu.open(event)
  62. }, 10)
  63. }
  64. private remapMenuItem (item: MenuItemOptions): ContextMenuItem {
  65. const cmi = {
  66. text: item.label,
  67. disabled: !(item.enabled ?? true),
  68. checked: item.checked,
  69. children: item.submenu?.map(i => this.remapMenuItem(i)),
  70. }
  71. if (item.click) {
  72. this.contextMenuHandlers.set(cmi, item.click)
  73. }
  74. return cmi
  75. }
  76. async showMessageBox (options: MessageBoxOptions): Promise<MessageBoxResult> {
  77. const modal = this.ngbModal.open(MessageBoxModalComponent, {
  78. backdrop: 'static',
  79. })
  80. const instance: MessageBoxModalComponent = modal.componentInstance
  81. instance.options = options
  82. try {
  83. const response = await modal.result
  84. return { response }
  85. } catch {
  86. return { response: options.cancelId ?? 1 }
  87. }
  88. }
  89. quit (): void {
  90. window.close()
  91. }
  92. async startDownload (name: string, mode: number, size: number): Promise<FileDownload|null> {
  93. const transfer = new HTMLFileDownload(name, mode, size)
  94. this.fileTransferStarted.next(transfer)
  95. return transfer
  96. }
  97. async startDownloadDirectory (_name: string, _estimatedSize?: number): Promise<DirectoryDownload|null> {
  98. throw new Error('Unsupported')
  99. }
  100. startUpload (options?: FileUploadOptions): Promise<FileUpload[]> {
  101. return new Promise(resolve => {
  102. this.fileSelector.onchange = () => {
  103. const transfers: FileUpload[] = []
  104. const fileList = this.fileSelector.files!
  105. // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
  106. for (let i = 0; i < (fileList.length ?? 0); i++) {
  107. const file = fileList[i]
  108. const transfer = new HTMLFileUpload(file)
  109. this.fileTransferStarted.next(transfer)
  110. transfers.push(transfer)
  111. if (!options?.multiple) {
  112. break
  113. }
  114. }
  115. resolve(transfers)
  116. }
  117. this.fileSelector.click()
  118. })
  119. }
  120. async startUploadDirectory (_paths?: string[]): Promise<DirectoryUpload> {
  121. return new DirectoryUpload()
  122. }
  123. setErrorHandler (handler: (_: any) => void): void {
  124. window.addEventListener('error', handler)
  125. }
  126. async pickDirectory (): Promise<string> {
  127. throw new Error('Unsupported')
  128. }
  129. }
  130. class HTMLFileDownload extends FileDownload {
  131. private buffers: Uint8Array[] = []
  132. constructor (
  133. private name: string,
  134. private mode: number,
  135. private size: number,
  136. ) {
  137. super()
  138. }
  139. getName (): string {
  140. return this.name
  141. }
  142. getMode (): number {
  143. return this.mode
  144. }
  145. getSize (): number {
  146. return this.size
  147. }
  148. async write (buffer: Uint8Array): Promise<void> {
  149. this.buffers.push(Uint8Array.from(buffer))
  150. this.increaseProgress(buffer.length)
  151. if (this.isComplete()) {
  152. this.finish()
  153. }
  154. }
  155. finish () {
  156. const blob = new Blob(this.buffers, { type: 'application/octet-stream' })
  157. const element = window.document.createElement('a')
  158. element.href = window.URL.createObjectURL(blob)
  159. element.download = this.name
  160. document.body.appendChild(element)
  161. element.click()
  162. document.body.removeChild(element)
  163. }
  164. // eslint-disable-next-line @typescript-eslint/no-empty-function
  165. close (): void { }
  166. }