UNPKG

10.3 kBJavaScriptView Raw
1"use strict";
2// *****************************************************************************
3// Copyright (C) 2017 TypeFox and others.
4//
5// This program and the accompanying materials are made available under the
6// terms of the Eclipse Public License v. 2.0 which is available at
7// http://www.eclipse.org/legal/epl-2.0.
8//
9// This Source Code may also be made available under the following Secondary
10// Licenses when the conditions for such availability set forth in the Eclipse
11// Public License v. 2.0 are satisfied: GNU General Public License, version 2
12// with the GNU Classpath Exception which is available at
13// https://www.gnu.org/software/classpath/license.html.
14//
15// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
16// *****************************************************************************
17var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
18 var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
19 if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
20 else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
21 return c > 3 && r && Object.defineProperty(target, key, r), r;
22};
23var __metadata = (this && this.__metadata) || function (k, v) {
24 if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
25};
26var __param = (this && this.__param) || function (paramIndex, decorator) {
27 return function (target, key) { decorator(target, key, paramIndex); }
28};
29Object.defineProperty(exports, "__esModule", { value: true });
30exports.MenuModelRegistry = exports.MenuContribution = void 0;
31const inversify_1 = require("inversify");
32const command_1 = require("../command");
33const contribution_provider_1 = require("../contribution-provider");
34const composite_menu_node_1 = require("./composite-menu-node");
35const menu_types_1 = require("./menu-types");
36const action_menu_node_1 = require("./action-menu-node");
37exports.MenuContribution = Symbol('MenuContribution');
38/**
39 * The MenuModelRegistry allows to register and unregister menus, submenus and actions
40 * via strings and {@link MenuAction}s without the need to access the underlying UI
41 * representation.
42 */
43let MenuModelRegistry = class MenuModelRegistry {
44 constructor(contributions, commands) {
45 this.contributions = contributions;
46 this.commands = commands;
47 this.root = new composite_menu_node_1.CompositeMenuNode('');
48 this.independentSubmenus = new Map();
49 }
50 onStart() {
51 for (const contrib of this.contributions.getContributions()) {
52 contrib.registerMenus(this);
53 }
54 }
55 /**
56 * Adds the given menu action to the menu denoted by the given path.
57 *
58 * @returns a disposable which, when called, will remove the menu action again.
59 */
60 registerMenuAction(menuPath, item) {
61 const menuNode = new action_menu_node_1.ActionMenuNode(item, this.commands);
62 return this.registerMenuNode(menuPath, menuNode);
63 }
64 /**
65 * Adds the given menu node to the menu denoted by the given path.
66 *
67 * @returns a disposable which, when called, will remove the menu node again.
68 */
69 registerMenuNode(menuPath, menuNode, group) {
70 const parent = this.getMenuNode(menuPath, group);
71 return parent.addNode(menuNode);
72 }
73 getMenuNode(menuPath, group) {
74 if (typeof menuPath === 'string') {
75 const target = this.independentSubmenus.get(menuPath);
76 if (!target) {
77 throw new Error(`Could not find submenu with id ${menuPath}`);
78 }
79 if (group) {
80 return this.findSubMenu(target, group);
81 }
82 return target;
83 }
84 else {
85 return this.findGroup(group ? menuPath.concat(group) : menuPath);
86 }
87 }
88 /**
89 * Register a new menu at the given path with the given label.
90 * (If the menu already exists without a label, iconClass or order this method can be used to set them.)
91 *
92 * @param menuPath the path for which a new submenu shall be registered.
93 * @param label the label to be used for the new submenu.
94 * @param options optionally allows to set an icon class and specify the order of the new menu.
95 *
96 * @returns if the menu was successfully created a disposable will be returned which,
97 * when called, will remove the menu again. If the menu already existed a no-op disposable
98 * will be returned.
99 *
100 * Note that if the menu already existed and was registered with a different label an error
101 * will be thrown.
102 */
103 registerSubmenu(menuPath, label, options) {
104 if (menuPath.length === 0) {
105 throw new Error('The sub menu path cannot be empty.');
106 }
107 const index = menuPath.length - 1;
108 const menuId = menuPath[index];
109 const groupPath = index === 0 ? [] : menuPath.slice(0, index);
110 const parent = this.findGroup(groupPath, options);
111 let groupNode = this.findSubMenu(parent, menuId, options);
112 if (!groupNode) {
113 groupNode = new composite_menu_node_1.CompositeMenuNode(menuId, label, options, parent);
114 return parent.addNode(groupNode);
115 }
116 else {
117 if (!groupNode.label) {
118 groupNode.label = label;
119 }
120 else if (groupNode.label !== label) {
121 throw new Error("The group '" + menuPath.join('/') + "' already has a different label.");
122 }
123 if (options) {
124 if (!groupNode.iconClass) {
125 groupNode.iconClass = options.iconClass;
126 }
127 if (!groupNode.order) {
128 groupNode.order = options.order;
129 }
130 }
131 return { dispose: () => { } };
132 }
133 }
134 registerIndependentSubmenu(id, label, options) {
135 if (this.independentSubmenus.has(id)) {
136 console.debug(`Independent submenu with path ${id} registered, but given ID already exists.`);
137 }
138 this.independentSubmenus.set(id, new composite_menu_node_1.CompositeMenuNode(id, label, options));
139 return { dispose: () => this.independentSubmenus.delete(id) };
140 }
141 linkSubmenu(parentPath, childId, options, group) {
142 const child = this.getMenuNode(childId);
143 const parent = this.getMenuNode(parentPath, group);
144 const wrapper = new composite_menu_node_1.CompositeMenuNodeWrapper(child, parent, options);
145 return parent.addNode(wrapper);
146 }
147 unregisterMenuAction(itemOrCommandOrId, menuPath) {
148 const id = menu_types_1.MenuAction.is(itemOrCommandOrId) ? itemOrCommandOrId.commandId
149 : command_1.Command.is(itemOrCommandOrId) ? itemOrCommandOrId.id
150 : itemOrCommandOrId;
151 if (menuPath) {
152 const parent = this.findGroup(menuPath);
153 parent.removeNode(id);
154 return;
155 }
156 this.unregisterMenuNode(id);
157 }
158 /**
159 * Recurse all menus, removing any menus matching the `id`.
160 *
161 * @param id technical identifier of the `MenuNode`.
162 */
163 unregisterMenuNode(id) {
164 const recurse = (root) => {
165 root.children.forEach(node => {
166 if (node instanceof composite_menu_node_1.CompositeMenuNode) {
167 node.removeNode(id);
168 recurse(node);
169 }
170 });
171 };
172 recurse(this.root);
173 }
174 /**
175 * Finds a submenu as a descendant of the `root` node.
176 * See {@link MenuModelRegistry.findSubMenu findSubMenu}.
177 */
178 findGroup(menuPath, options) {
179 let currentMenu = this.root;
180 for (const segment of menuPath) {
181 currentMenu = this.findSubMenu(currentMenu, segment, options);
182 }
183 return currentMenu;
184 }
185 /**
186 * Finds or creates a submenu as an immediate child of `current`.
187 * @throws if a node with the given `menuId` exists but is not a {@link CompositeMenuNode}.
188 */
189 findSubMenu(current, menuId, options) {
190 const sub = current.children.find(e => e.id === menuId);
191 if (sub instanceof composite_menu_node_1.CompositeMenuNode) {
192 return sub;
193 }
194 if (sub) {
195 throw new Error(`'${menuId}' is not a menu group.`);
196 }
197 const newSub = new composite_menu_node_1.CompositeMenuNode(menuId, undefined, options, current);
198 current.addNode(newSub);
199 return newSub;
200 }
201 /**
202 * Returns the menu at the given path.
203 *
204 * @param menuPath the path specifying the menu to return. If not given the empty path will be used.
205 *
206 * @returns the root menu when `menuPath` is empty. If `menuPath` is not empty the specified menu is
207 * returned if it exists, otherwise an error is thrown.
208 */
209 getMenu(menuPath = []) {
210 return this.findGroup(menuPath);
211 }
212 /**
213 * Returns the {@link MenuPath path} at which a given menu node can be accessed from this registry, if it can be determined.
214 * Returns `undefined` if the `parent` of any node in the chain is unknown.
215 */
216 getPath(node) {
217 const identifiers = [];
218 const visited = [];
219 let next = node;
220 while (next && !visited.includes(next)) {
221 if (next === this.root) {
222 return identifiers.reverse();
223 }
224 visited.push(next);
225 identifiers.push(next.id);
226 next = next.parent;
227 }
228 return undefined;
229 }
230};
231MenuModelRegistry = __decorate([
232 (0, inversify_1.injectable)(),
233 __param(0, (0, inversify_1.inject)(contribution_provider_1.ContributionProvider)),
234 __param(0, (0, inversify_1.named)(exports.MenuContribution)),
235 __param(1, (0, inversify_1.inject)(command_1.CommandRegistry)),
236 __metadata("design:paramtypes", [Object, command_1.CommandRegistry])
237], MenuModelRegistry);
238exports.MenuModelRegistry = MenuModelRegistry;
239//# sourceMappingURL=menu-model-registry.js.map
\No newline at end of file