appRoot.component.ts 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
  2. import { Component, Inject, Input, HostListener, HostBinding, ViewChildren } from '@angular/core'
  3. import { trigger, style, animate, transition, state } from '@angular/animations'
  4. import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
  5. import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'
  6. import { HostAppService, Platform } from '../api/hostApp'
  7. import { HotkeysService } from '../services/hotkeys.service'
  8. import { Logger, LogService } from '../services/log.service'
  9. import { ConfigService } from '../services/config.service'
  10. import { ThemesService } from '../services/themes.service'
  11. import { UpdaterService } from '../services/updater.service'
  12. import { BaseTabComponent } from './baseTab.component'
  13. import { SafeModeModalComponent } from './safeModeModal.component'
  14. import { TabBodyComponent } from './tabBody.component'
  15. import { AppService, FileTransfer, HostWindowService, PlatformService, ToolbarButton, ToolbarButtonProvider } from '../api'
  16. /** @hidden */
  17. @Component({
  18. selector: 'app-root',
  19. template: require('./appRoot.component.pug'),
  20. styles: [require('./appRoot.component.scss')],
  21. animations: [
  22. trigger('animateTab', [
  23. state('in', style({
  24. 'flex-basis': '200px',
  25. width: '200px',
  26. })),
  27. transition(':enter', [
  28. style({
  29. 'flex-basis': '1px',
  30. width: '1px',
  31. }),
  32. animate('250ms ease-in-out', style({
  33. 'flex-basis': '200px',
  34. width: '200px',
  35. })),
  36. ]),
  37. transition(':leave', [
  38. style({
  39. 'flex-basis': '200px',
  40. width: '200px',
  41. }),
  42. animate('250ms ease-in-out', style({
  43. 'flex-basis': '1px',
  44. width: '1px',
  45. })),
  46. ]),
  47. ]),
  48. ],
  49. })
  50. export class AppRootComponent {
  51. Platform = Platform
  52. @Input() ready = false
  53. @Input() leftToolbarButtons: ToolbarButton[]
  54. @Input() rightToolbarButtons: ToolbarButton[]
  55. @HostBinding('class.platform-win32') platformClassWindows = process.platform === 'win32'
  56. @HostBinding('class.platform-darwin') platformClassMacOS = process.platform === 'darwin'
  57. @HostBinding('class.platform-linux') platformClassLinux = process.platform === 'linux'
  58. @HostBinding('class.no-tabs') noTabs = true
  59. @ViewChildren(TabBodyComponent) tabBodies: TabBodyComponent[]
  60. unsortedTabs: BaseTabComponent[] = []
  61. updatesAvailable = false
  62. activeTransfers: FileTransfer[] = []
  63. activeTransfersDropdownOpen = false
  64. private logger: Logger
  65. constructor (
  66. private hotkeys: HotkeysService,
  67. private updater: UpdaterService,
  68. public hostWindow: HostWindowService,
  69. public hostApp: HostAppService,
  70. public config: ConfigService,
  71. public app: AppService,
  72. @Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
  73. platform: PlatformService,
  74. log: LogService,
  75. ngbModal: NgbModal,
  76. _themes: ThemesService,
  77. ) {
  78. this.logger = log.create('main')
  79. this.logger.info('v', platform.getAppVersion())
  80. this.hotkeys.hotkey$.subscribe((hotkey: string) => {
  81. if (hotkey.startsWith('tab-')) {
  82. const index = parseInt(hotkey.split('-')[1])
  83. if (index <= this.app.tabs.length) {
  84. this.app.selectTab(this.app.tabs[index - 1])
  85. }
  86. }
  87. if (this.app.activeTab) {
  88. if (hotkey === 'close-tab') {
  89. this.app.closeTab(this.app.activeTab, true)
  90. }
  91. if (hotkey === 'toggle-last-tab') {
  92. this.app.toggleLastTab()
  93. }
  94. if (hotkey === 'next-tab') {
  95. this.app.nextTab()
  96. }
  97. if (hotkey === 'previous-tab') {
  98. this.app.previousTab()
  99. }
  100. if (hotkey === 'move-tab-left') {
  101. this.app.moveSelectedTabLeft()
  102. }
  103. if (hotkey === 'move-tab-right') {
  104. this.app.moveSelectedTabRight()
  105. }
  106. if (hotkey === 'reopen-tab') {
  107. this.app.reopenLastTab()
  108. }
  109. }
  110. if (hotkey === 'toggle-fullscreen') {
  111. hostWindow.toggleFullscreen()
  112. }
  113. })
  114. this.hostWindow.windowCloseRequest$.subscribe(async () => {
  115. this.app.closeWindow()
  116. })
  117. if (window['safeModeReason']) {
  118. ngbModal.open(SafeModeModalComponent)
  119. }
  120. this.app.tabOpened$.subscribe(tab => {
  121. this.unsortedTabs.push(tab)
  122. this.noTabs = false
  123. this.app.emitTabDragEnded()
  124. })
  125. this.app.tabRemoved$.subscribe(tab => {
  126. for (const tabBody of this.tabBodies) {
  127. if (tabBody.tab === tab) {
  128. tabBody.detach()
  129. }
  130. }
  131. this.unsortedTabs = this.unsortedTabs.filter(x => x !== tab)
  132. this.noTabs = app.tabs.length === 0
  133. this.app.emitTabDragEnded()
  134. })
  135. platform.fileTransferStarted$.subscribe(transfer => {
  136. this.activeTransfers.push(transfer)
  137. this.activeTransfersDropdownOpen = true
  138. })
  139. config.ready$.toPromise().then(() => {
  140. this.leftToolbarButtons = this.getToolbarButtons(false)
  141. this.rightToolbarButtons = this.getToolbarButtons(true)
  142. setInterval(() => {
  143. if (this.config.store.enableAutomaticUpdates) {
  144. this.updater.check().then(available => {
  145. this.updatesAvailable = available
  146. })
  147. }
  148. }, 3600 * 12 * 1000)
  149. })
  150. }
  151. async ngOnInit () {
  152. this.config.ready$.toPromise().then(() => {
  153. this.ready = true
  154. this.app.emitReady()
  155. })
  156. }
  157. @HostListener('dragover')
  158. onDragOver () {
  159. return false
  160. }
  161. @HostListener('drop')
  162. onDrop () {
  163. return false
  164. }
  165. hasVerticalTabs () {
  166. return this.config.store.appearance.tabsLocation === 'left' || this.config.store.appearance.tabsLocation === 'right'
  167. }
  168. onTabDragStart () {
  169. this.app.emitTabDragStarted()
  170. }
  171. onTabDragEnd () {
  172. setTimeout(() => {
  173. this.app.emitTabDragEnded()
  174. this.app.emitTabsChanged()
  175. })
  176. }
  177. async generateButtonSubmenu (button: ToolbarButton) {
  178. if (button.submenu) {
  179. button.submenuItems = await button.submenu()
  180. }
  181. }
  182. hasIcons (submenuItems: ToolbarButton[]): boolean {
  183. return submenuItems.some(x => !!x.icon)
  184. }
  185. onTabsReordered (event: CdkDragDrop<BaseTabComponent[]>) {
  186. moveItemInArray(this.app.tabs, event.previousIndex, event.currentIndex)
  187. this.app.emitTabsChanged()
  188. }
  189. private getToolbarButtons (aboveZero: boolean): ToolbarButton[] {
  190. let buttons: ToolbarButton[] = []
  191. this.config.enabledServices(this.toolbarButtonProviders).forEach(provider => {
  192. buttons = buttons.concat(provider.provide())
  193. })
  194. return buttons
  195. .filter(button => (button.weight ?? 0) > 0 === aboveZero)
  196. .sort((a: ToolbarButton, b: ToolbarButton) => (a.weight ?? 0) - (b.weight ?? 0))
  197. }
  198. }