appRoot.component.ts 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
  2. import { Component, Input, HostListener, HostBinding, ViewChildren, ViewChild } from '@angular/core'
  3. import { trigger, style, animate, transition, state } from '@angular/animations'
  4. import { NgbDropdown, 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 { CommandService } from '../services/commands.service'
  13. import { BaseTabComponent } from './baseTab.component'
  14. import { SafeModeModalComponent } from './safeModeModal.component'
  15. import { TabBodyComponent } from './tabBody.component'
  16. import { SplitTabComponent } from './splitTab.component'
  17. import { AppService, Command, CommandLocation, FileTransfer, HostWindowService, PlatformService } from '../api'
  18. import { CommonModule } from '@angular/common'
  19. function makeTabAnimation (dimension: string, size: number) {
  20. return [
  21. state('in', style({
  22. 'flex-basis': '{{size}}',
  23. [dimension]: '{{size}}',
  24. }), {
  25. params: { size: `${size}px` },
  26. }),
  27. transition(':enter', [
  28. style({
  29. 'flex-basis': '1px',
  30. [dimension]: '1px',
  31. }),
  32. animate('250ms ease-out', style({
  33. 'flex-basis': '{{size}}',
  34. [dimension]: '{{size}}',
  35. })),
  36. ]),
  37. transition(':leave', [
  38. style({
  39. 'flex-basis': 'auto',
  40. 'padding-left': '*',
  41. 'padding-right': '*',
  42. [dimension]: '*',
  43. }),
  44. animate('250ms ease-in-out', style({
  45. 'padding-left': 0,
  46. 'padding-right': 0,
  47. [dimension]: '0',
  48. })),
  49. ]),
  50. ]
  51. }
  52. /** @hidden */
  53. @Component({
  54. imports: [CommonModule],
  55. standalone: true,
  56. selector: 'app-root',
  57. template: require('./appRoot.component.pug'),
  58. styles: [require('./appRoot.component.scss')],
  59. animations: [
  60. trigger('animateTab', makeTabAnimation('width', 200)),
  61. ],
  62. })
  63. export class AppRootComponent {
  64. Platform = Platform
  65. @Input() ready = false
  66. @Input() leftToolbarButtons: Command[]
  67. @Input() rightToolbarButtons: Command[]
  68. @HostBinding('class.platform-win32') platformClassWindows = process.platform === 'win32'
  69. @HostBinding('class.platform-darwin') platformClassMacOS = process.platform === 'darwin'
  70. @HostBinding('class.platform-linux') platformClassLinux = process.platform === 'linux'
  71. @HostBinding('class.no-tabs') noTabs = true
  72. @ViewChildren(TabBodyComponent) tabBodies: TabBodyComponent[]
  73. @ViewChild('activeTransfersDropdown') activeTransfersDropdown: NgbDropdown
  74. unsortedTabs: BaseTabComponent[] = []
  75. updatesAvailable = false
  76. activeTransfers: FileTransfer[] = []
  77. private logger: Logger
  78. constructor (
  79. private hotkeys: HotkeysService,
  80. private updater: UpdaterService,
  81. private commands: CommandService,
  82. public hostWindow: HostWindowService,
  83. public hostApp: HostAppService,
  84. public config: ConfigService,
  85. public app: AppService,
  86. platform: PlatformService,
  87. log: LogService,
  88. ngbModal: NgbModal,
  89. _themes: ThemesService,
  90. ) {
  91. this.logger = log.create('main')
  92. this.logger.info('v', platform.getAppVersion())
  93. this.hotkeys.hotkey$.subscribe((hotkey: string) => {
  94. if (hotkey.startsWith('tab-')) {
  95. const index = parseInt(hotkey.split('-')[1])
  96. if (index <= this.app.tabs.length) {
  97. this.app.selectTab(this.app.tabs[index - 1])
  98. }
  99. }
  100. if (this.app.activeTab) {
  101. if (hotkey === 'close-tab') {
  102. this.app.closeTab(this.app.activeTab, true)
  103. }
  104. if (hotkey === 'toggle-last-tab') {
  105. this.app.toggleLastTab()
  106. }
  107. if (hotkey === 'next-tab') {
  108. this.app.nextTab()
  109. }
  110. if (hotkey === 'previous-tab') {
  111. this.app.previousTab()
  112. }
  113. if (hotkey === 'move-tab-left') {
  114. this.app.moveSelectedTabLeft()
  115. }
  116. if (hotkey === 'move-tab-right') {
  117. this.app.moveSelectedTabRight()
  118. }
  119. if (hotkey === 'duplicate-tab') {
  120. this.app.duplicateTab(this.app.activeTab)
  121. }
  122. if (hotkey === 'restart-tab') {
  123. this.app.duplicateTab(this.app.activeTab)
  124. this.app.closeTab(this.app.activeTab, true)
  125. }
  126. if (hotkey === 'explode-tab' && this.app.activeTab instanceof SplitTabComponent) {
  127. this.app.explodeTab(this.app.activeTab)
  128. }
  129. if (hotkey === 'combine-tabs' && this.app.activeTab instanceof SplitTabComponent) {
  130. this.app.combineTabsInto(this.app.activeTab)
  131. }
  132. }
  133. if (hotkey === 'reopen-tab') {
  134. this.app.reopenLastTab()
  135. }
  136. if (hotkey === 'toggle-fullscreen') {
  137. hostWindow.toggleFullscreen()
  138. }
  139. })
  140. this.hostWindow.windowCloseRequest$.subscribe(async () => {
  141. this.app.closeWindow()
  142. })
  143. if (window['safeModeReason']) {
  144. ngbModal.open(SafeModeModalComponent)
  145. }
  146. this.app.tabOpened$.subscribe(tab => {
  147. this.unsortedTabs.push(tab)
  148. this.noTabs = false
  149. this.app.emitTabDragEnded()
  150. })
  151. this.app.tabRemoved$.subscribe(tab => {
  152. for (const tabBody of this.tabBodies) {
  153. if (tabBody.tab === tab) {
  154. tabBody.detach()
  155. }
  156. }
  157. this.unsortedTabs = this.unsortedTabs.filter(x => x !== tab)
  158. this.noTabs = app.tabs.length === 0
  159. this.app.emitTabDragEnded()
  160. })
  161. platform.fileTransferStarted$.subscribe(transfer => {
  162. this.activeTransfers.push(transfer)
  163. this.activeTransfersDropdown.open()
  164. })
  165. config.ready$.toPromise().then(async () => {
  166. this.leftToolbarButtons = await this.getToolbarButtons(false)
  167. this.rightToolbarButtons = await this.getToolbarButtons(true)
  168. setInterval(() => {
  169. if (this.config.store.enableAutomaticUpdates) {
  170. this.updater.check().then(available => {
  171. this.updatesAvailable = available
  172. })
  173. }
  174. }, 3600 * 12 * 1000)
  175. })
  176. }
  177. async ngOnInit () {
  178. this.config.ready$.toPromise().then(() => {
  179. this.ready = true
  180. this.app.emitReady()
  181. })
  182. }
  183. @HostListener('dragover')
  184. onDragOver () {
  185. return false
  186. }
  187. @HostListener('drop')
  188. onDrop () {
  189. return false
  190. }
  191. hasVerticalTabs () {
  192. return this.config.store.appearance.tabsLocation === 'left' || this.config.store.appearance.tabsLocation === 'right'
  193. }
  194. get targetTabSize (): any {
  195. if (this.hasVerticalTabs()) {
  196. return '*'
  197. }
  198. return this.config.store.appearance.flexTabs ? '*' : '200px'
  199. }
  200. onTabsReordered (event: CdkDragDrop<BaseTabComponent[]>) {
  201. const tab: BaseTabComponent = event.item.data
  202. if (!this.app.tabs.includes(tab)) {
  203. if (tab.parent instanceof SplitTabComponent) {
  204. tab.parent.removeTab(tab)
  205. this.app.wrapAndAddTab(tab)
  206. }
  207. }
  208. moveItemInArray(this.app.tabs, event.previousIndex, event.currentIndex)
  209. this.app.emitTabsChanged()
  210. }
  211. onTransfersChange () {
  212. if (this.activeTransfers.length === 0) {
  213. this.activeTransfersDropdown.close()
  214. }
  215. }
  216. @HostBinding('class.vibrant') get isVibrant () {
  217. return this.config.store?.appearance.vibrancy
  218. }
  219. private async getToolbarButtons (aboveZero: boolean): Promise<Command[]> {
  220. return (await this.commands.getCommands({ tab: this.app.activeTab ?? undefined }))
  221. .filter(x => x.locations?.includes(aboveZero ? CommandLocation.RightToolbar : CommandLocation.LeftToolbar))
  222. }
  223. }