3d06c8ddbe0cfbdbe7154e7fe3f5b5e5ae6ca3c3
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / CollectionContentView.js
1 /*
2  * Copyright (C) 2016 Devin Rousso <webkit@devinrousso.com>. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WI.CollectionContentView = class CollectionContentView extends WI.ContentView
27 {
28     constructor(collection, contentViewConstructor, contentPlaceholderText)
29     {
30         console.assert(collection instanceof WI.Collection);
31
32         super(collection);
33
34         this.element.classList.add("collection");
35
36         this._contentPlaceholderText = contentPlaceholderText || WI.CollectionContentView.titleForCollection(collection);
37         this._contentViewConstructor = contentViewConstructor;
38         this._contentViewMap = new Map;
39         this._handleClickMap = new WeakMap;
40         this._selectedItem = null;
41         this._selectionEnabled = false;
42     }
43
44     static titleForCollection(collection)
45     {
46         switch (collection.typeVerifier) {
47         case WI.Collection.TypeVerifier.Frame:
48             return WI.UIString("Frames");
49         case WI.Collection.TypeVerifier.Resource:
50             return WI.UIString("Resources");
51         case WI.Collection.TypeVerifier.Script:
52             return WI.UIString("Scripts");
53         case WI.Collection.TypeVerifier.CSSStyleSheet:
54             return WI.UIString("Stylesheets");
55         case WI.Collection.TypeVerifier.Canvas:
56             return WI.UIString("Canvases");
57         case WI.Collection.TypeVerifier.ShaderProgram:
58             return WI.UIString("Shader Programs");
59         }
60
61         console.warn("No default title for Collection type verifier.", collection.typeVerifier);
62         return WI.UIString("Collection");
63     }
64
65     // Public
66
67     get supplementalRepresentedObjects()
68     {
69         if (this._selectedItem)
70             return [this._selectedItem];
71         return [];
72     }
73
74     get selectionEnabled()
75     {
76         return this._selectionEnabled;
77     }
78
79     set selectionEnabled(value)
80     {
81         if (this._selectionEnabled === value)
82             return;
83
84         this._selectionEnabled = value;
85         if (!this._selectionEnabled)
86             this._selectItem(null);
87     }
88
89     setSelectedItem(item)
90     {
91         console.assert(this._selectionEnabled, "Attempted to set selected item when selection is disabled.");
92         if (!this._selectionEnabled)
93             return;
94
95         let contentView = this._contentViewMap.get(item);
96         console.assert(contentView, "Missing contet view for item.", item);
97         if (!contentView)
98             return;
99
100         this._selectItem(item);
101         contentView.element.scrollIntoViewIfNeeded();
102     }
103
104     // Protected
105
106     addContentViewForItem(item)
107     {
108         if (!this._contentViewConstructor)
109             return;
110
111         if (this._contentViewMap.has(item)) {
112             console.assert(false, "Already added ContentView for item.", item);
113             return;
114         }
115
116         this._hideContentPlaceholder();
117
118         let contentView = new this._contentViewConstructor(item);
119         console.assert(contentView instanceof WI.ContentView);
120
121         let handleClick = (event) => {
122             if (event.button !== 0 || event.ctrlKey)
123                 return;
124
125             if (this._selectionEnabled)
126                 this._selectItem(item);
127             else
128                 WI.showRepresentedObject(item);
129         };
130
131         this._contentViewMap.set(item, contentView);
132         this._handleClickMap.set(item, handleClick);
133         contentView.element.addEventListener("click", handleClick);
134
135         this.addSubview(contentView);
136         this.contentViewAdded(contentView);
137
138         contentView.shown();
139     }
140
141     removeContentViewForItem(item)
142     {
143         if (!this._contentViewConstructor)
144             return;
145
146         let contentView = this._contentViewMap.get(item);
147         console.assert(contentView);
148         if (!contentView)
149             return;
150
151         if (this._selectedItem === item)
152             this._selectItem(null);
153
154         this.removeSubview(contentView);
155         this._contentViewMap.delete(item);
156         this.contentViewRemoved(contentView);
157
158         contentView.hidden();
159
160         contentView.removeEventListener(null, null, this);
161
162         let handleClick = this._handleClickMap.get(item);
163         console.assert(handleClick);
164
165         if (handleClick) {
166             contentView.element.removeEventListener("click", handleClick);
167             this._handleClickMap.delete(item);
168         }
169
170         if (!this.subviews.length)
171             this._showContentPlaceholder();
172     }
173
174     contentViewAdded(contentView)
175     {
176         // Implemented by subclasses.
177     }
178
179     contentViewRemoved(contentView)
180     {
181         // Implemented by subclasses.
182     }
183
184     initialLayout()
185     {
186         let items = this.representedObject.items;
187         if (!items.size || !this._contentViewConstructor) {
188             this._showContentPlaceholder();
189             return;
190         }
191
192         for (let item of items)
193             this.addContentViewForItem(item);
194     }
195
196     attached()
197     {
198         super.attached();
199
200         this.representedObject.addEventListener(WI.Collection.Event.ItemAdded, this._handleItemAdded, this);
201         this.representedObject.addEventListener(WI.Collection.Event.ItemRemoved, this._handleItemRemoved, this);
202
203         for (let item of this._contentViewMap.keys()) {
204             if (this.representedObject.items.has(item))
205                 continue;
206
207             this.removeContentViewForItem(item);
208             if (this._selectedItem === item)
209                 this._selectItem(null);
210         }
211
212         for (let item of this.representedObject.items) {
213             if (!this._contentViewMap.has(item))
214                 this.addContentViewForItem(item);
215         }
216     }
217
218     detached()
219     {
220         this.representedObject.removeEventListener(null, null, this);
221
222         super.detached();
223     }
224
225      // Private
226
227     _handleItemAdded(event)
228     {
229         let item = event.data.item;
230         if (!item)
231             return;
232
233         this.addContentViewForItem(item);
234     }
235
236     _handleItemRemoved(event)
237     {
238         let item = event.data.item;
239         if (!item)
240             return;
241
242         this.removeContentViewForItem(item);
243     }
244
245     _handleContentError(event)
246     {
247         if (event && event.target)
248             this._removeContentViewForItem(event.target.representedObject);
249     }
250
251     _selectItem(item)
252     {
253         if (this._selectedItem === item)
254             return;
255
256         if (this._selectedItem) {
257             let contentView = this._contentViewMap.get(this._selectedItem);
258             console.assert(contentView, "Missing ContentView for deselected item.", this._selectedItem);
259             contentView.element.classList.remove("selected");
260         }
261
262         this._selectedItem = item;
263
264         if (this._selectedItem) {
265             let selectedContentView = this._contentViewMap.get(this._selectedItem);
266             console.assert(selectedContentView, "Missing ContentView for selected item.", this._selectedItem);
267             selectedContentView.element.classList.add("selected");
268         }
269
270         this.dispatchEventToListeners(WI.ContentView.Event.SupplementalRepresentedObjectsDidChange);
271     }
272
273     _showContentPlaceholder()
274     {
275         if (!this._contentPlaceholder)
276             this._contentPlaceholder = new WI.TitleView(this._contentPlaceholderText);
277
278         if (!this._contentPlaceholder.parentView)
279             this.debounce(250).addSubview(this._contentPlaceholder);
280     }
281
282     _hideContentPlaceholder()
283     {
284         this.addSubview.cancelDebounce();
285
286         if (this._contentPlaceholder && this._contentPlaceholder.parentView)
287             this.removeSubview(this._contentPlaceholder);
288     }
289 };