reopen-project-menu-manager.js
1 const { CompositeDisposable } = require('event-kit'); 2 const path = require('path'); 3 4 module.exports = class ReopenProjectMenuManager { 5 constructor({ menu, commands, history, config, open }) { 6 this.menuManager = menu; 7 this.historyManager = history; 8 this.config = config; 9 this.open = open; 10 this.projects = []; 11 12 this.subscriptions = new CompositeDisposable(); 13 this.subscriptions.add( 14 history.onDidChangeProjects(this.update.bind(this)), 15 config.onDidChange( 16 'core.reopenProjectMenuCount', 17 ({ oldValue, newValue }) => { 18 this.update(); 19 } 20 ), 21 commands.add('atom-workspace', { 22 'application:reopen-project': this.reopenProjectCommand.bind(this) 23 }) 24 ); 25 26 this.applyWindowsJumpListRemovals(); 27 } 28 29 reopenProjectCommand(e) { 30 if (e.detail != null && e.detail.index != null) { 31 this.open(this.projects[e.detail.index].paths); 32 } else { 33 this.createReopenProjectListView(); 34 } 35 } 36 37 createReopenProjectListView() { 38 if (this.reopenProjectListView == null) { 39 const ReopenProjectListView = require('./reopen-project-list-view'); 40 this.reopenProjectListView = new ReopenProjectListView(paths => { 41 if (paths != null) { 42 this.open(paths); 43 } 44 }); 45 } 46 this.reopenProjectListView.toggle(); 47 } 48 49 update() { 50 this.disposeProjectMenu(); 51 this.projects = this.historyManager 52 .getProjects() 53 .slice(0, this.config.get('core.reopenProjectMenuCount')); 54 const newMenu = ReopenProjectMenuManager.createProjectsMenu(this.projects); 55 this.lastProjectMenu = this.menuManager.add([newMenu]); 56 this.updateWindowsJumpList(); 57 } 58 59 static taskDescription(paths) { 60 return paths 61 .map(path => `${ReopenProjectMenuManager.betterBaseName(path)} (${path})`) 62 .join(' '); 63 } 64 65 // Windows users can right-click Atom taskbar and remove project from the jump list. 66 // We have to honor that or the group stops working. As we only get a partial list 67 // each time we remove them from history entirely. 68 async applyWindowsJumpListRemovals() { 69 if (process.platform !== 'win32') return; 70 if (this.app === undefined) { 71 this.app = require('remote').app; 72 } 73 74 const removed = this.app 75 .getJumpListSettings() 76 .removedItems.map(i => i.description); 77 if (removed.length === 0) return; 78 for (let project of this.historyManager.getProjects()) { 79 if ( 80 removed.includes( 81 ReopenProjectMenuManager.taskDescription(project.paths) 82 ) 83 ) { 84 await this.historyManager.removeProject(project.paths); 85 } 86 } 87 } 88 89 updateWindowsJumpList() { 90 if (process.platform !== 'win32') return; 91 if (this.app === undefined) { 92 this.app = require('remote').app; 93 } 94 95 this.app.setJumpList([ 96 { 97 type: 'custom', 98 name: 'Recent Projects', 99 items: this.projects.map(project => ({ 100 type: 'task', 101 title: project.paths 102 .map(ReopenProjectMenuManager.betterBaseName) 103 .join(', '), 104 description: ReopenProjectMenuManager.taskDescription(project.paths), 105 program: process.execPath, 106 args: project.paths.map(path => `"${path}"`).join(' '), 107 iconPath: path.join( 108 path.dirname(process.execPath), 109 'resources', 110 'cli', 111 'folder.ico' 112 ), 113 iconIndex: 0 114 })) 115 }, 116 { type: 'recent' }, 117 { 118 items: [ 119 { 120 type: 'task', 121 title: 'New Window', 122 program: process.execPath, 123 args: '--new-window', 124 description: 'Opens a new Atom window' 125 } 126 ] 127 } 128 ]); 129 } 130 131 dispose() { 132 this.subscriptions.dispose(); 133 this.disposeProjectMenu(); 134 if (this.reopenProjectListView != null) { 135 this.reopenProjectListView.dispose(); 136 } 137 } 138 139 disposeProjectMenu() { 140 if (this.lastProjectMenu) { 141 this.lastProjectMenu.dispose(); 142 this.lastProjectMenu = null; 143 } 144 } 145 146 static createProjectsMenu(projects) { 147 return { 148 label: 'File', 149 submenu: [ 150 { 151 label: 'Reopen Project', 152 submenu: projects.map((project, index) => ({ 153 label: this.createLabel(project), 154 command: 'application:reopen-project', 155 commandDetail: { index: index, paths: project.paths } 156 })) 157 } 158 ] 159 }; 160 } 161 162 static createLabel(project) { 163 return project.paths.length === 1 164 ? project.paths[0] 165 : project.paths.map(this.betterBaseName).join(', '); 166 } 167 168 static betterBaseName(directory) { 169 // Handles Windows roots better than path.basename which returns '' for 'd:' and 'd:\' 170 const match = directory.match(/^([a-z]:)[\\]?$/i); 171 return match ? match[1] + '\\' : path.basename(directory); 172 } 173 };