1 | // *****************************************************************************
|
2 | // Copyright (C) 2018 TypeFox and others.
|
3 | //
|
4 | // This program and the accompanying materials are made available under the
|
5 | // terms of the Eclipse Public License v. 2.0 which is available at
|
6 | // http://www.eclipse.org/legal/epl-2.0.
|
7 | //
|
8 | // This Source Code may also be made available under the following Secondary
|
9 | // Licenses when the conditions for such availability set forth in the Eclipse
|
10 | // Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
11 | // with the GNU Classpath Exception which is available at
|
12 | // https://www.gnu.org/software/classpath/license.html.
|
13 | //
|
14 | // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
15 | // *****************************************************************************
|
16 |
|
17 | import { injectable } from 'inversify';
|
18 | import { Tree, TreeNode } from './tree';
|
19 | import { Event, Emitter, Disposable, DisposableCollection, MaybePromise } from '../../common';
|
20 | import { WidgetDecoration } from '../widget-decoration';
|
21 |
|
22 | /**
|
23 | * The {@link TreeDecorator} allows adapting the look and the style of the tree items within a widget. Changes are reflected in
|
24 | * the form of `decoration data`. This `decoration data` is a map storing {@link TreeDecoration.Data} for affected tree nodes (using the unique node id as key).
|
25 | * It is important to notice that there is no common contribution point for `TreeDecorators`. Instead, each {@link TreeDecoratorService} is
|
26 | * supposed to declare its own contribution provider for `TreeDecorators`.
|
27 | *
|
28 | * ### Example usage
|
29 | * A simple tree decorator that changes the background color of each tree node to `red`.
|
30 | *
|
31 | * ```typescript
|
32 | * @injectable()
|
33 | * export class MyTreeDecorator implements TreeDecorator {
|
34 | * id = 'myTreeDecorator';
|
35 | *
|
36 | * protected readonly emitter = new Emitter<(tree: Tree) => Map<string, TreeDecoration.Data>>();
|
37 | *
|
38 | * get onDidChangeDecorations(): Event<(tree: Tree) => Map<string, TreeDecoration.Data>> {
|
39 | * return this.emitter.event;
|
40 | * }
|
41 | *
|
42 | * decorations(tree: Tree): MaybePromise<Map<string, TreeDecoration.Data>> {
|
43 | * const result = new Map();
|
44 | *
|
45 | * if (tree.root === undefined) {
|
46 | * return result;
|
47 | * }
|
48 | * for (const treeNode of new DepthFirstTreeIterator(tree.root)) {
|
49 | * result.set(treeNode.id,<TreeDecoration.Data>{backgroundColor:'red'})
|
50 | * }
|
51 | * return result;
|
52 | * }
|
53 | * }
|
54 | * ```
|
55 | */
|
56 | export interface TreeDecorator {
|
57 |
|
58 | /**
|
59 | * The unique identifier of the decorator. Ought to be unique in the application.
|
60 | */
|
61 | readonly id: string;
|
62 |
|
63 | /**
|
64 | * Fired when this decorator has calculated all the `decoration data` for the tree nodes.
|
65 | */
|
66 | readonly onDidChangeDecorations: Event<(tree: Tree) => Map<string, TreeDecoration.Data>>;
|
67 |
|
68 | /**
|
69 | * Computes the current `decoration data` for the given tree. Might return a promise if the computation is handled asynchronously.
|
70 | *
|
71 | * @param tree the tree to decorate.
|
72 | *
|
73 | * @returns (a promise of) a map containing the current {@linkTreeDecoration.Data} for each node. Keys are the unique identifier of the tree nodes.
|
74 | */
|
75 | decorations(tree: Tree): MaybePromise<Map<string, TreeDecoration.Data>>;
|
76 |
|
77 | }
|
78 |
|
79 | export const TreeDecoratorService = Symbol('TreeDecoratorService');
|
80 |
|
81 | /**
|
82 | * The {@link TreeDecoratorService} manages a set of known {link TreeDecorator}s and emits events when
|
83 | * any of the known decorators has changes. Typically, a `TreeDecoratorService` provides a contribution point that can be used to
|
84 | * register {@link TreeDecorator}s exclusively for this service.
|
85 | *
|
86 | * ### Example usage
|
87 | * ```typescript
|
88 | * export const MyTreeDecorator = Symbol('MyTreeDecorator');
|
89 | *
|
90 | * @injectable()
|
91 | * export class MyDecorationService extends AbstractTreeDecoratorService {
|
92 | * constructor(@inject(ContributionProvider) @named(MyTreeDecorator) protected readonly contributions: ContributionProvider<TreeDecorator>) {
|
93 | * super(contributions.getContributions());
|
94 | * }
|
95 | * }
|
96 | * ```
|
97 | */
|
98 | export interface TreeDecoratorService extends Disposable {
|
99 |
|
100 | /**
|
101 | * Fired when any of the available tree decorators has changes.
|
102 | */
|
103 | readonly onDidChangeDecorations: Event<void>;
|
104 |
|
105 | /**
|
106 | * Computes the decorations for the tree based on the actual state of this decorator service.
|
107 | *
|
108 | * @param tree the tree to decorate
|
109 | *
|
110 | * @returns (a promise of) the computed `decoration data`
|
111 | */
|
112 | getDecorations(tree: Tree): MaybePromise<Map<string, TreeDecoration.Data[]>>;
|
113 |
|
114 | /**
|
115 | * Transforms the `decoration data` into an object, so that it can be safely serialized into JSON.
|
116 | * @param decorations the `decoration data` that should be deflated
|
117 | *
|
118 | * @returns the `decoration data` as serializable JSON object
|
119 | */
|
120 | deflateDecorators(decorations: Map<string, TreeDecoration.Data[]>): object;
|
121 |
|
122 | /**
|
123 | * Counterpart of the [deflateDecorators](#deflateDecorators) method. Restores the argument into a Map
|
124 | * of tree node IDs and the corresponding decorations data array (`decoration data`).
|
125 | *
|
126 | * @returns the deserialized `decoration data
|
127 | */
|
128 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
129 | inflateDecorators(state: any): Map<string, TreeDecoration.Data[]>;
|
130 |
|
131 | }
|
132 |
|
133 | /**
|
134 | * The default tree decorator service. Does nothing at all. One has to rebind to a concrete implementation
|
135 | * if decorators have to be supported in the tree widget.
|
136 | */
|
137 | ()
|
138 | export class NoopTreeDecoratorService implements TreeDecoratorService {
|
139 |
|
140 | protected readonly emitter = new Emitter<void>();
|
141 | readonly onDidChangeDecorations = this.emitter.event;
|
142 |
|
143 | dispose(): void {
|
144 | this.emitter.dispose();
|
145 | }
|
146 |
|
147 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
148 | getDecorations(): Map<any, any> {
|
149 | return new Map();
|
150 | }
|
151 |
|
152 | deflateDecorators(): object {
|
153 | return {};
|
154 | }
|
155 |
|
156 | inflateDecorators(): Map<string, TreeDecoration.Data[]> {
|
157 | return new Map();
|
158 | }
|
159 |
|
160 | }
|
161 |
|
162 | /**
|
163 | * Abstract decorator service implementation which emits events from all known tree decorators and caches the current state.
|
164 | */
|
165 | ()
|
166 | export abstract class AbstractTreeDecoratorService implements TreeDecoratorService {
|
167 |
|
168 | protected readonly onDidChangeDecorationsEmitter = new Emitter<void>();
|
169 | readonly onDidChangeDecorations = this.onDidChangeDecorationsEmitter.event;
|
170 |
|
171 | protected readonly toDispose = new DisposableCollection();
|
172 |
|
173 | constructor(protected readonly decorators: ReadonlyArray<TreeDecorator>) {
|
174 | this.toDispose.push(this.onDidChangeDecorationsEmitter);
|
175 | this.toDispose.pushAll(this.decorators.map(decorator =>
|
176 | decorator.onDidChangeDecorations(data =>
|
177 | this.onDidChangeDecorationsEmitter.fire(undefined)
|
178 | ))
|
179 | );
|
180 | }
|
181 |
|
182 | dispose(): void {
|
183 | this.toDispose.dispose();
|
184 | }
|
185 |
|
186 | async getDecorations(tree: Tree): Promise<Map<string, TreeDecoration.Data[]>> {
|
187 | const changes = new Map();
|
188 | for (const decorator of this.decorators) {
|
189 | for (const [id, data] of (await decorator.decorations(tree)).entries()) {
|
190 | if (changes.has(id)) {
|
191 | changes.get(id)!.push(data);
|
192 | } else {
|
193 | changes.set(id, [data]);
|
194 | }
|
195 | }
|
196 | }
|
197 | return changes;
|
198 | }
|
199 |
|
200 | deflateDecorators(decorations: Map<string, TreeDecoration.Data[]>): object {
|
201 | // eslint-disable-next-line no-null/no-null
|
202 | const state = Object.create(null);
|
203 | for (const [id, data] of decorations) {
|
204 | state[id] = data;
|
205 | }
|
206 | return state;
|
207 | }
|
208 |
|
209 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
210 | inflateDecorators(state: any): Map<string, TreeDecoration.Data[]> {
|
211 | const decorators = new Map<string, TreeDecoration.Data[]>();
|
212 | for (const id of Object.keys(state)) {
|
213 | decorators.set(id, state[id]);
|
214 | }
|
215 | return decorators;
|
216 | }
|
217 |
|
218 | }
|
219 |
|
220 | /**
|
221 | * @deprecated import from `@theia/core/lib/browser/widget-decoration` instead.
|
222 | */
|
223 | export import TreeDecoration = WidgetDecoration;
|
224 |
|
225 | export interface DecoratedTreeNode extends TreeNode {
|
226 | /**
|
227 | * The additional tree decoration data attached to the tree node itself.
|
228 | */
|
229 | readonly decorationData: TreeDecoration.Data;
|
230 | }
|
231 | export namespace DecoratedTreeNode {
|
232 | /**
|
233 | * Type-guard for decorated tree nodes.
|
234 | */
|
235 | export function is(node: TreeNode | undefined): node is DecoratedTreeNode {
|
236 | return !!node && 'decorationData' in node;
|
237 | }
|
238 | }
|