/ src / pane-element.js
pane-element.js
  1  const path = require('path');
  2  const { CompositeDisposable } = require('event-kit');
  3  
  4  class PaneElement extends HTMLElement {
  5    createdCallback() {
  6      this.attached = false;
  7      this.subscriptions = new CompositeDisposable();
  8      this.inlineDisplayStyles = new WeakMap();
  9      this.initializeContent();
 10      this.subscribeToDOMEvents();
 11    }
 12  
 13    attachedCallback() {
 14      this.attached = true;
 15      if (this.model.isFocused()) {
 16        this.focus();
 17      }
 18    }
 19  
 20    detachedCallback() {
 21      this.attached = false;
 22    }
 23  
 24    initializeContent() {
 25      this.setAttribute('class', 'pane');
 26      this.setAttribute('tabindex', -1);
 27      this.itemViews = document.createElement('div');
 28      this.appendChild(this.itemViews);
 29      this.itemViews.setAttribute('class', 'item-views');
 30    }
 31  
 32    subscribeToDOMEvents() {
 33      const handleFocus = event => {
 34        if (
 35          !(
 36            this.isActivating ||
 37            this.model.isDestroyed() ||
 38            this.contains(event.relatedTarget)
 39          )
 40        ) {
 41          this.model.focus();
 42        }
 43        if (event.target !== this) return;
 44        const view = this.getActiveView();
 45        if (view) {
 46          view.focus();
 47          event.stopPropagation();
 48        }
 49      };
 50      const handleBlur = event => {
 51        if (!this.contains(event.relatedTarget)) {
 52          this.model.blur();
 53        }
 54      };
 55      const handleDragOver = event => {
 56        event.preventDefault();
 57        event.stopPropagation();
 58      };
 59      const handleDrop = event => {
 60        event.preventDefault();
 61        event.stopPropagation();
 62        this.getModel().activate();
 63        const pathsToOpen = [...event.dataTransfer.files].map(file => file.path);
 64        if (pathsToOpen.length > 0) {
 65          this.applicationDelegate.open({ pathsToOpen, here: true });
 66        }
 67      };
 68      this.addEventListener('focus', handleFocus, true);
 69      this.addEventListener('blur', handleBlur, true);
 70      this.addEventListener('dragover', handleDragOver);
 71      this.addEventListener('drop', handleDrop);
 72    }
 73  
 74    initialize(model, { views, applicationDelegate }) {
 75      this.model = model;
 76      this.views = views;
 77      this.applicationDelegate = applicationDelegate;
 78      if (this.views == null) {
 79        throw new Error(
 80          'Must pass a views parameter when initializing PaneElements'
 81        );
 82      }
 83      if (this.applicationDelegate == null) {
 84        throw new Error(
 85          'Must pass an applicationDelegate parameter when initializing PaneElements'
 86        );
 87      }
 88      this.subscriptions.add(this.model.onDidActivate(this.activated.bind(this)));
 89      this.subscriptions.add(
 90        this.model.observeActive(this.activeStatusChanged.bind(this))
 91      );
 92      this.subscriptions.add(
 93        this.model.observeActiveItem(this.activeItemChanged.bind(this))
 94      );
 95      this.subscriptions.add(
 96        this.model.onDidRemoveItem(this.itemRemoved.bind(this))
 97      );
 98      this.subscriptions.add(
 99        this.model.onDidDestroy(this.paneDestroyed.bind(this))
100      );
101      this.subscriptions.add(
102        this.model.observeFlexScale(this.flexScaleChanged.bind(this))
103      );
104      return this;
105    }
106  
107    getModel() {
108      return this.model;
109    }
110  
111    activated() {
112      this.isActivating = true;
113      if (!this.hasFocus()) {
114        // Don't steal focus from children.
115        this.focus();
116      }
117      this.isActivating = false;
118    }
119  
120    activeStatusChanged(active) {
121      if (active) {
122        this.classList.add('active');
123      } else {
124        this.classList.remove('active');
125      }
126    }
127  
128    activeItemChanged(item) {
129      delete this.dataset.activeItemName;
130      delete this.dataset.activeItemPath;
131      if (this.changePathDisposable != null) {
132        this.changePathDisposable.dispose();
133      }
134      if (item == null) {
135        return;
136      }
137      const hasFocus = this.hasFocus();
138      const itemView = this.views.getView(item);
139      const itemPath = typeof item.getPath === 'function' ? item.getPath() : null;
140      if (itemPath) {
141        this.dataset.activeItemName = path.basename(itemPath);
142        this.dataset.activeItemPath = itemPath;
143        if (item.onDidChangePath != null) {
144          this.changePathDisposable = item.onDidChangePath(() => {
145            const itemPath = item.getPath();
146            this.dataset.activeItemName = path.basename(itemPath);
147            this.dataset.activeItemPath = itemPath;
148          });
149        }
150      }
151      if (!this.itemViews.contains(itemView)) {
152        this.itemViews.appendChild(itemView);
153      }
154      for (const child of this.itemViews.children) {
155        if (child === itemView) {
156          if (this.attached) {
157            this.showItemView(child);
158          }
159        } else {
160          this.hideItemView(child);
161        }
162      }
163      if (hasFocus) {
164        itemView.focus();
165      }
166    }
167  
168    showItemView(itemView) {
169      const inlineDisplayStyle = this.inlineDisplayStyles.get(itemView);
170      if (inlineDisplayStyle != null) {
171        itemView.style.display = inlineDisplayStyle;
172      } else {
173        itemView.style.display = '';
174      }
175    }
176  
177    hideItemView(itemView) {
178      const inlineDisplayStyle = itemView.style.display;
179      if (inlineDisplayStyle !== 'none') {
180        if (inlineDisplayStyle != null) {
181          this.inlineDisplayStyles.set(itemView, inlineDisplayStyle);
182        }
183        itemView.style.display = 'none';
184      }
185    }
186  
187    itemRemoved({ item, index, destroyed }) {
188      const viewToRemove = this.views.getView(item);
189      if (viewToRemove) {
190        viewToRemove.remove();
191      }
192    }
193  
194    paneDestroyed() {
195      this.subscriptions.dispose();
196      if (this.changePathDisposable != null) {
197        this.changePathDisposable.dispose();
198      }
199    }
200  
201    flexScaleChanged(flexScale) {
202      this.style.flexGrow = flexScale;
203    }
204  
205    getActiveView() {
206      return this.views.getView(this.model.getActiveItem());
207    }
208  
209    hasFocus() {
210      return (
211        this === document.activeElement || this.contains(document.activeElement)
212      );
213    }
214  }
215  
216  module.exports = document.registerElement('atom-pane', {
217    prototype: PaneElement.prototype
218  });