mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-08-16 22:50:22 +03:00
Machine decaf tree-view
source
This commit is contained in:
parent
f37b1dd1a8
commit
0c10f0a775
81
packages/tree-view/lib/add-dialog.js
Normal file
81
packages/tree-view/lib/add-dialog.js
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS002: Fix invalid constructor
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
|
||||
*/
|
||||
let AddDialog;
|
||||
const path = require('path');
|
||||
const fs = require('fs-plus');
|
||||
const Dialog = require('./dialog');
|
||||
const {repoForPath} = require('./helpers');
|
||||
|
||||
module.exports =
|
||||
(AddDialog = class AddDialog extends Dialog {
|
||||
constructor(initialPath, isCreatingFile) {
|
||||
let directoryPath;
|
||||
this.isCreatingFile = isCreatingFile;
|
||||
|
||||
if (fs.isFileSync(initialPath)) {
|
||||
directoryPath = path.dirname(initialPath);
|
||||
} else {
|
||||
directoryPath = initialPath;
|
||||
}
|
||||
|
||||
let relativeDirectoryPath = directoryPath;
|
||||
[this.rootProjectPath, relativeDirectoryPath] = atom.project.relativizePath(directoryPath);
|
||||
if (relativeDirectoryPath.length > 0) { relativeDirectoryPath += path.sep; }
|
||||
|
||||
super({
|
||||
prompt: "Enter the path for the new " + (isCreatingFile ? "file." : "folder."),
|
||||
initialPath: relativeDirectoryPath,
|
||||
select: false,
|
||||
iconClass: isCreatingFile ? 'icon-file-add' : 'icon-file-directory-create'
|
||||
});
|
||||
}
|
||||
|
||||
onDidCreateFile(callback) {
|
||||
return this.emitter.on('did-create-file', callback);
|
||||
}
|
||||
|
||||
onDidCreateDirectory(callback) {
|
||||
return this.emitter.on('did-create-directory', callback);
|
||||
}
|
||||
|
||||
onConfirm(newPath) {
|
||||
newPath = newPath.replace(/\s+$/, ''); // Remove trailing whitespace
|
||||
const endsWithDirectorySeparator = newPath[newPath.length - 1] === path.sep;
|
||||
if (!path.isAbsolute(newPath)) {
|
||||
if (this.rootProjectPath == null) {
|
||||
this.showError("You must open a directory to create a file with a relative path");
|
||||
return;
|
||||
}
|
||||
|
||||
newPath = path.join(this.rootProjectPath, newPath);
|
||||
}
|
||||
|
||||
if (!newPath) { return; }
|
||||
|
||||
try {
|
||||
if (fs.existsSync(newPath)) {
|
||||
return this.showError(`'${newPath}' already exists.`);
|
||||
} else if (this.isCreatingFile) {
|
||||
if (endsWithDirectorySeparator) {
|
||||
return this.showError(`File names must not end with a '${path.sep}' character.`);
|
||||
} else {
|
||||
fs.writeFileSync(newPath, '');
|
||||
repoForPath(newPath)?.getPathStatus(newPath);
|
||||
this.emitter.emit('did-create-file', newPath);
|
||||
return this.close();
|
||||
}
|
||||
} else {
|
||||
fs.makeTreeSync(newPath);
|
||||
this.emitter.emit('did-create-directory', newPath);
|
||||
return this.cancel();
|
||||
}
|
||||
} catch (error) {
|
||||
return this.showError(`${error.message}.`);
|
||||
}
|
||||
}
|
||||
});
|
72
packages/tree-view/lib/copy-dialog.js
Normal file
72
packages/tree-view/lib/copy-dialog.js
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS002: Fix invalid constructor
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
|
||||
*/
|
||||
let CopyDialog;
|
||||
const path = require('path');
|
||||
const fs = require('fs-plus');
|
||||
const Dialog = require('./dialog');
|
||||
const {repoForPath} = require("./helpers");
|
||||
|
||||
module.exports =
|
||||
(CopyDialog = class CopyDialog extends Dialog {
|
||||
constructor(initialPath, {onCopy}) {
|
||||
this.initialPath = initialPath;
|
||||
this.onCopy = onCopy;
|
||||
super({
|
||||
prompt: 'Enter the new path for the duplicate.',
|
||||
initialPath: atom.project.relativize(this.initialPath),
|
||||
select: true,
|
||||
iconClass: 'icon-arrow-right'
|
||||
});
|
||||
}
|
||||
|
||||
onConfirm(newPath) {
|
||||
newPath = newPath.replace(/\s+$/, ''); // Remove trailing whitespace
|
||||
if (!path.isAbsolute(newPath)) {
|
||||
const [rootPath] = Array.from(atom.project.relativizePath(this.initialPath));
|
||||
newPath = path.join(rootPath, newPath);
|
||||
if (!newPath) { return; }
|
||||
}
|
||||
|
||||
if (this.initialPath === newPath) {
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (fs.existsSync(newPath)) {
|
||||
this.showError(`'${newPath}' already exists.`);
|
||||
return;
|
||||
}
|
||||
|
||||
let activeEditor = atom.workspace.getActiveTextEditor();
|
||||
if (activeEditor?.getPath() !== this.initialPath) { activeEditor = null; }
|
||||
try {
|
||||
let repo;
|
||||
if (fs.isDirectorySync(this.initialPath)) {
|
||||
fs.copySync(this.initialPath, newPath);
|
||||
this.onCopy?.({initialPath: this.initialPath, newPath});
|
||||
} else {
|
||||
fs.copy(this.initialPath, newPath, () => {
|
||||
this.onCopy?.({initialPath: this.initialPath, newPath});
|
||||
return atom.workspace.open(newPath, {
|
||||
activatePane: true,
|
||||
initialLine: activeEditor?.getLastCursor().getBufferRow(),
|
||||
initialColumn: activeEditor?.getLastCursor().getBufferColumn()
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
if (repo = repoForPath(newPath)) {
|
||||
repo.getPathStatus(this.initialPath);
|
||||
repo.getPathStatus(newPath);
|
||||
}
|
||||
return this.close();
|
||||
} catch (error) {
|
||||
return this.showError(`${error.message}.`);
|
||||
}
|
||||
}
|
||||
});
|
31
packages/tree-view/lib/default-file-icons.js
Normal file
31
packages/tree-view/lib/default-file-icons.js
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
|
||||
*/
|
||||
const fs = require('fs-plus');
|
||||
const path = require('path');
|
||||
|
||||
class DefaultFileIcons {
|
||||
iconClassForPath(filePath) {
|
||||
const extension = path.extname(filePath);
|
||||
|
||||
if (fs.isSymbolicLinkSync(filePath)) {
|
||||
return 'icon-file-symlink-file';
|
||||
} else if (fs.isReadmePath(filePath)) {
|
||||
return 'icon-book';
|
||||
} else if (fs.isCompressedExtension(extension)) {
|
||||
return 'icon-file-zip';
|
||||
} else if (fs.isImageExtension(extension)) {
|
||||
return 'icon-file-media';
|
||||
} else if (fs.isPdfExtension(extension)) {
|
||||
return 'icon-file-pdf';
|
||||
} else if (fs.isBinaryExtension(extension)) {
|
||||
return 'icon-file-binary';
|
||||
} else {
|
||||
return 'icon-file-text';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new DefaultFileIcons;
|
96
packages/tree-view/lib/dialog.js
Normal file
96
packages/tree-view/lib/dialog.js
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
|
||||
*/
|
||||
let Dialog;
|
||||
const {TextEditor, CompositeDisposable, Disposable, Emitter, Range, Point} = require('atom');
|
||||
const path = require('path');
|
||||
const {getFullExtension} = require("./helpers");
|
||||
|
||||
module.exports =
|
||||
(Dialog = class Dialog {
|
||||
constructor(param) {
|
||||
if (param == null) { param = {}; }
|
||||
const {initialPath, select, iconClass, prompt} = param;
|
||||
this.emitter = new Emitter();
|
||||
this.disposables = new CompositeDisposable();
|
||||
|
||||
this.element = document.createElement('div');
|
||||
this.element.classList.add('tree-view-dialog');
|
||||
|
||||
this.promptText = document.createElement('label');
|
||||
this.promptText.classList.add('icon');
|
||||
if (iconClass) { this.promptText.classList.add(iconClass); }
|
||||
this.promptText.textContent = prompt;
|
||||
this.element.appendChild(this.promptText);
|
||||
|
||||
this.miniEditor = new TextEditor({mini: true});
|
||||
const blurHandler = () => {
|
||||
if (document.hasFocus()) { return this.close(); }
|
||||
};
|
||||
this.miniEditor.element.addEventListener('blur', blurHandler);
|
||||
this.disposables.add(new Disposable(() => this.miniEditor.element.removeEventListener('blur', blurHandler)));
|
||||
this.disposables.add(this.miniEditor.onDidChange(() => this.showError()));
|
||||
this.element.appendChild(this.miniEditor.element);
|
||||
|
||||
this.errorMessage = document.createElement('div');
|
||||
this.errorMessage.classList.add('error-message');
|
||||
this.element.appendChild(this.errorMessage);
|
||||
|
||||
atom.commands.add(this.element, {
|
||||
'core:confirm': () => this.onConfirm(this.miniEditor.getText()),
|
||||
'core:cancel': () => this.cancel()
|
||||
}
|
||||
);
|
||||
|
||||
this.miniEditor.setText(initialPath);
|
||||
|
||||
if (select) {
|
||||
let selectionEnd;
|
||||
const extension = getFullExtension(initialPath);
|
||||
const baseName = path.basename(initialPath);
|
||||
const selectionStart = initialPath.length - baseName.length;
|
||||
if (baseName === extension) {
|
||||
selectionEnd = initialPath.length;
|
||||
} else {
|
||||
selectionEnd = initialPath.length - extension.length;
|
||||
}
|
||||
this.miniEditor.setSelectedBufferRange(Range(Point(0, selectionStart), Point(0, selectionEnd)));
|
||||
}
|
||||
}
|
||||
|
||||
attach() {
|
||||
this.panel = atom.workspace.addModalPanel({item: this});
|
||||
this.miniEditor.element.focus();
|
||||
return this.miniEditor.scrollToCursorPosition();
|
||||
}
|
||||
|
||||
close() {
|
||||
const {
|
||||
panel
|
||||
} = this;
|
||||
this.panel = null;
|
||||
panel?.destroy();
|
||||
this.emitter.dispose();
|
||||
this.disposables.dispose();
|
||||
this.miniEditor.destroy();
|
||||
const activePane = atom.workspace.getCenter().getActivePane();
|
||||
if (!activePane.isDestroyed()) { return activePane.activate(); }
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.close();
|
||||
return document.querySelector('.tree-view')?.focus();
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
if (message == null) { message = ''; }
|
||||
this.errorMessage.textContent = message;
|
||||
if (message) {
|
||||
this.element.classList.add('error');
|
||||
return window.setTimeout((() => this.element.classList.remove('error')), 300);
|
||||
}
|
||||
}
|
||||
});
|
36
packages/tree-view/lib/helpers.js
Normal file
36
packages/tree-view/lib/helpers.js
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
|
||||
*/
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
repoForPath(goalPath) {
|
||||
const iterable = atom.project.getPaths();
|
||||
for (let i = 0; i < iterable.length; i++) {
|
||||
const projectPath = iterable[i];
|
||||
if ((goalPath === projectPath) || (goalPath.indexOf(projectPath + path.sep) === 0)) {
|
||||
return atom.project.getRepositories()[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
getStyleObject(el) {
|
||||
const styleProperties = window.getComputedStyle(el);
|
||||
const styleObject = {};
|
||||
for (let property in styleProperties) {
|
||||
const value = styleProperties.getPropertyValue(property);
|
||||
const camelizedAttr = property.replace(/\-([a-z])/g, (a, b) => b.toUpperCase());
|
||||
styleObject[camelizedAttr] = value;
|
||||
}
|
||||
return styleObject;
|
||||
},
|
||||
|
||||
getFullExtension(filePath) {
|
||||
const basename = path.basename(filePath);
|
||||
const position = basename.indexOf('.');
|
||||
if (position > 0) { return basename.slice(position); } else { return ''; }
|
||||
}
|
||||
};
|
41
packages/tree-view/lib/ignored-names.js
Normal file
41
packages/tree-view/lib/ignored-names.js
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS104: Avoid inline assignments
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
|
||||
*/
|
||||
let IgnoredNames;
|
||||
let Minimatch = null; // Defer requiring until actually needed
|
||||
|
||||
module.exports =
|
||||
(IgnoredNames = class IgnoredNames {
|
||||
constructor() {
|
||||
let left;
|
||||
this.ignoredPatterns = [];
|
||||
|
||||
if (Minimatch == null) { ({
|
||||
Minimatch
|
||||
} = require('minimatch')); }
|
||||
|
||||
let ignoredNames = (left = atom.config.get('core.ignoredNames')) != null ? left : [];
|
||||
if (typeof ignoredNames === 'string') { ignoredNames = [ignoredNames]; }
|
||||
for (let ignoredName of Array.from(ignoredNames)) {
|
||||
if (ignoredName) {
|
||||
try {
|
||||
this.ignoredPatterns.push(new Minimatch(ignoredName, {matchBase: true, dot: true}));
|
||||
} catch (error) {
|
||||
atom.notifications.addWarning(`Error parsing ignore pattern (${ignoredName})`, {detail: error.message});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
matches(filePath) {
|
||||
for (let ignoredPattern of Array.from(this.ignoredPatterns)) {
|
||||
if (ignoredPattern.match(filePath)) { return true; }
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
87
packages/tree-view/lib/move-dialog.js
Normal file
87
packages/tree-view/lib/move-dialog.js
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS002: Fix invalid constructor
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
|
||||
*/
|
||||
let MoveDialog;
|
||||
const path = require('path');
|
||||
const fs = require('fs-plus');
|
||||
const Dialog = require('./dialog');
|
||||
const {repoForPath} = require("./helpers");
|
||||
|
||||
module.exports =
|
||||
(MoveDialog = class MoveDialog extends Dialog {
|
||||
constructor(initialPath, {willMove, onMove, onMoveFailed}) {
|
||||
let prompt;
|
||||
this.initialPath = initialPath;
|
||||
this.willMove = willMove;
|
||||
this.onMove = onMove;
|
||||
this.onMoveFailed = onMoveFailed;
|
||||
if (fs.isDirectorySync(this.initialPath)) {
|
||||
prompt = 'Enter the new path for the directory.';
|
||||
} else {
|
||||
prompt = 'Enter the new path for the file.';
|
||||
}
|
||||
|
||||
super({
|
||||
prompt,
|
||||
initialPath: atom.project.relativize(this.initialPath),
|
||||
select: true,
|
||||
iconClass: 'icon-arrow-right'
|
||||
});
|
||||
}
|
||||
|
||||
onConfirm(newPath) {
|
||||
newPath = newPath.replace(/\s+$/, ''); // Remove trailing whitespace
|
||||
if (!path.isAbsolute(newPath)) {
|
||||
const [rootPath] = Array.from(atom.project.relativizePath(this.initialPath));
|
||||
newPath = path.join(rootPath, newPath);
|
||||
if (!newPath) { return; }
|
||||
}
|
||||
|
||||
if (this.initialPath === newPath) {
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isNewPathValid(newPath)) {
|
||||
this.showError(`'${newPath}' already exists.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const directoryPath = path.dirname(newPath);
|
||||
try {
|
||||
let repo;
|
||||
this.willMove?.({initialPath: this.initialPath, newPath});
|
||||
if (!fs.existsSync(directoryPath)) { fs.makeTreeSync(directoryPath); }
|
||||
fs.moveSync(this.initialPath, newPath);
|
||||
this.onMove?.({initialPath: this.initialPath, newPath});
|
||||
if (repo = repoForPath(newPath)) {
|
||||
repo.getPathStatus(this.initialPath);
|
||||
repo.getPathStatus(newPath);
|
||||
}
|
||||
return this.close();
|
||||
} catch (error) {
|
||||
this.showError(`${error.message}.`);
|
||||
return this.onMoveFailed?.({initialPath: this.initialPath, newPath});
|
||||
}
|
||||
}
|
||||
|
||||
isNewPathValid(newPath) {
|
||||
try {
|
||||
const oldStat = fs.statSync(this.initialPath);
|
||||
const newStat = fs.statSync(newPath);
|
||||
|
||||
// New path exists so check if it points to the same file as the initial
|
||||
// path to see if the case of the file name is being changed on a on a
|
||||
// case insensitive filesystem.
|
||||
return (this.initialPath.toLowerCase() === newPath.toLowerCase()) &&
|
||||
(oldStat.dev === newStat.dev) &&
|
||||
(oldStat.ino === newStat.ino);
|
||||
} catch (error) {
|
||||
return true; // new path does not exist so it is valid
|
||||
}
|
||||
}
|
||||
});
|
255
packages/tree-view/lib/root-drag-and-drop.js
Normal file
255
packages/tree-view/lib/root-drag-and-drop.js
Normal file
@ -0,0 +1,255 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
|
||||
*/
|
||||
let RootDragAndDropHandler;
|
||||
const url = require('url');
|
||||
|
||||
const {ipcRenderer, remote} = require('electron');
|
||||
|
||||
// TODO: Support dragging external folders and using the drag-and-drop indicators for them
|
||||
// Currently they're handled in TreeView's drag listeners
|
||||
|
||||
module.exports =
|
||||
(RootDragAndDropHandler = class RootDragAndDropHandler {
|
||||
constructor(treeView) {
|
||||
this.onDragStart = this.onDragStart.bind(this);
|
||||
this.onDragLeave = this.onDragLeave.bind(this);
|
||||
this.onDragEnd = this.onDragEnd.bind(this);
|
||||
this.onDragOver = this.onDragOver.bind(this);
|
||||
this.onDropOnOtherWindow = this.onDropOnOtherWindow.bind(this);
|
||||
this.onDrop = this.onDrop.bind(this);
|
||||
this.treeView = treeView;
|
||||
ipcRenderer.on('tree-view:project-folder-dropped', this.onDropOnOtherWindow);
|
||||
this.handleEvents();
|
||||
}
|
||||
|
||||
dispose() {
|
||||
return ipcRenderer.removeListener('tree-view:project-folder-dropped', this.onDropOnOtherWindow);
|
||||
}
|
||||
|
||||
handleEvents() {
|
||||
// onDragStart is called directly by TreeView's onDragStart
|
||||
// will be cleaned up by tree view, since they are tree-view's handlers
|
||||
this.treeView.element.addEventListener('dragenter', this.onDragEnter.bind(this));
|
||||
this.treeView.element.addEventListener('dragend', this.onDragEnd.bind(this));
|
||||
this.treeView.element.addEventListener('dragleave', this.onDragLeave.bind(this));
|
||||
this.treeView.element.addEventListener('dragover', this.onDragOver.bind(this));
|
||||
return this.treeView.element.addEventListener('drop', this.onDrop.bind(this));
|
||||
}
|
||||
|
||||
onDragStart(e) {
|
||||
if (!this.treeView.list.contains(e.target)) { return; }
|
||||
|
||||
this.prevDropTargetIndex = null;
|
||||
e.dataTransfer.setData('atom-tree-view-root-event', 'true');
|
||||
const projectRoot = e.target.closest('.project-root');
|
||||
const {
|
||||
directory
|
||||
} = projectRoot;
|
||||
|
||||
e.dataTransfer.setData('project-root-index', Array.from(projectRoot.parentElement.children).indexOf(projectRoot));
|
||||
|
||||
let rootIndex = -1;
|
||||
for (let index = 0; index < this.treeView.roots.length; index++) { const root = this.treeView.roots[index]; if (root.directory === directory) { rootIndex = index; break; } }
|
||||
|
||||
e.dataTransfer.setData('from-root-index', rootIndex);
|
||||
e.dataTransfer.setData('from-root-path', directory.path);
|
||||
e.dataTransfer.setData('from-window-id', this.getWindowId());
|
||||
|
||||
e.dataTransfer.setData('text/plain', directory.path);
|
||||
|
||||
if (['darwin', 'linux'].includes(process.platform)) {
|
||||
let pathUri;
|
||||
if (!this.uriHasProtocol(directory.path)) { pathUri = `file://${directory.path}`; }
|
||||
return e.dataTransfer.setData('text/uri-list', pathUri);
|
||||
}
|
||||
}
|
||||
|
||||
uriHasProtocol(uri) {
|
||||
try {
|
||||
return (url.parse(uri).protocol != null);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
onDragEnter(e) {
|
||||
if (!this.treeView.list.contains(e.target)) { return; }
|
||||
if (!this.isAtomTreeViewEvent(e)) { return; }
|
||||
|
||||
return e.stopPropagation();
|
||||
}
|
||||
|
||||
onDragLeave(e) {
|
||||
if (!this.treeView.list.contains(e.target)) { return; }
|
||||
if (!this.isAtomTreeViewEvent(e)) { return; }
|
||||
|
||||
e.stopPropagation();
|
||||
if (e.target === e.currentTarget) { return this.removePlaceholder(); }
|
||||
}
|
||||
|
||||
onDragEnd(e) {
|
||||
if (!e.target.matches('.project-root-header')) { return; }
|
||||
if (!this.isAtomTreeViewEvent(e)) { return; }
|
||||
|
||||
e.stopPropagation();
|
||||
return this.clearDropTarget();
|
||||
}
|
||||
|
||||
onDragOver(e) {
|
||||
let element;
|
||||
if (!this.treeView.list.contains(e.target)) { return; }
|
||||
if (!this.isAtomTreeViewEvent(e)) { return; }
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const entry = e.currentTarget;
|
||||
|
||||
if (this.treeView.roots.length === 0) {
|
||||
this.treeView.list.appendChild(this.getPlaceholder());
|
||||
return;
|
||||
}
|
||||
|
||||
const newDropTargetIndex = this.getDropTargetIndex(e);
|
||||
if (newDropTargetIndex == null) { return; }
|
||||
if (this.prevDropTargetIndex === newDropTargetIndex) { return; }
|
||||
this.prevDropTargetIndex = newDropTargetIndex;
|
||||
|
||||
const projectRoots = this.treeView.roots;
|
||||
|
||||
if (newDropTargetIndex < projectRoots.length) {
|
||||
element = projectRoots[newDropTargetIndex];
|
||||
element.classList.add('is-drop-target');
|
||||
return element.parentElement.insertBefore(this.getPlaceholder(), element);
|
||||
} else {
|
||||
element = projectRoots[newDropTargetIndex - 1];
|
||||
element.classList.add('drop-target-is-after');
|
||||
return element.parentElement.insertBefore(this.getPlaceholder(), element.nextSibling);
|
||||
}
|
||||
}
|
||||
|
||||
onDropOnOtherWindow(e, fromItemIndex) {
|
||||
const paths = atom.project.getPaths();
|
||||
paths.splice(fromItemIndex, 1);
|
||||
atom.project.setPaths(paths);
|
||||
|
||||
return this.clearDropTarget();
|
||||
}
|
||||
|
||||
clearDropTarget() {
|
||||
const element = this.treeView.element.querySelector(".is-dragging");
|
||||
element?.classList.remove('is-dragging');
|
||||
element?.updateTooltip();
|
||||
return this.removePlaceholder();
|
||||
}
|
||||
|
||||
onDrop(e) {
|
||||
let projectPaths;
|
||||
if (!this.treeView.list.contains(e.target)) { return; }
|
||||
if (!this.isAtomTreeViewEvent(e)) { return; }
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const {dataTransfer} = e;
|
||||
|
||||
const fromWindowId = parseInt(dataTransfer.getData('from-window-id'));
|
||||
const fromRootPath = dataTransfer.getData('from-root-path');
|
||||
const fromIndex = parseInt(dataTransfer.getData('project-root-index'));
|
||||
const fromRootIndex = parseInt(dataTransfer.getData('from-root-index'));
|
||||
|
||||
let toIndex = this.getDropTargetIndex(e);
|
||||
|
||||
this.clearDropTarget();
|
||||
|
||||
if (fromWindowId === this.getWindowId()) {
|
||||
if (fromIndex !== toIndex) {
|
||||
projectPaths = atom.project.getPaths();
|
||||
projectPaths.splice(fromIndex, 1);
|
||||
if (toIndex > fromIndex) { toIndex -= 1; }
|
||||
projectPaths.splice(toIndex, 0, fromRootPath);
|
||||
return atom.project.setPaths(projectPaths);
|
||||
}
|
||||
} else {
|
||||
projectPaths = atom.project.getPaths();
|
||||
projectPaths.splice(toIndex, 0, fromRootPath);
|
||||
atom.project.setPaths(projectPaths);
|
||||
|
||||
if (!isNaN(fromWindowId)) {
|
||||
// Let the window where the drag started know that the tab was dropped
|
||||
const browserWindow = remote.BrowserWindow.fromId(fromWindowId);
|
||||
return browserWindow?.webContents.send('tree-view:project-folder-dropped', fromIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getDropTargetIndex(e) {
|
||||
if (this.isPlaceholder(e.target)) { return; }
|
||||
|
||||
const projectRoots = this.treeView.roots;
|
||||
let projectRoot = e.target.closest('.project-root');
|
||||
if (!projectRoot) { projectRoot = projectRoots[projectRoots.length - 1]; }
|
||||
|
||||
if (!projectRoot) { return 0; }
|
||||
|
||||
const projectRootIndex = this.treeView.roots.indexOf(projectRoot);
|
||||
|
||||
const center = projectRoot.getBoundingClientRect().top + (projectRoot.offsetHeight / 2);
|
||||
|
||||
if (e.pageY < center) {
|
||||
return projectRootIndex;
|
||||
} else {
|
||||
return projectRootIndex + 1;
|
||||
}
|
||||
}
|
||||
|
||||
canDragStart(e) {
|
||||
return e.target.closest('.project-root-header');
|
||||
}
|
||||
|
||||
isDragging(e) {
|
||||
for (let item of Array.from(e.dataTransfer.items)) {
|
||||
if (item.type === 'from-root-path') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
isAtomTreeViewEvent(e) {
|
||||
for (let item of Array.from(e.dataTransfer.items)) {
|
||||
if (item.type === 'atom-tree-view-root-event') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
getPlaceholder() {
|
||||
if (!this.placeholderEl) {
|
||||
this.placeholderEl = document.createElement('li');
|
||||
this.placeholderEl.classList.add('placeholder');
|
||||
}
|
||||
return this.placeholderEl;
|
||||
}
|
||||
|
||||
removePlaceholder() {
|
||||
this.placeholderEl?.remove();
|
||||
return this.placeholderEl = null;
|
||||
}
|
||||
|
||||
isPlaceholder(element) {
|
||||
return element.classList.contains('.placeholder');
|
||||
}
|
||||
|
||||
getWindowId() {
|
||||
return this.processId != null ? this.processId : (this.processId = atom.getCurrentWindow().id);
|
||||
}
|
||||
});
|
1593
packages/tree-view/lib/tree-view.js
Normal file
1593
packages/tree-view/lib/tree-view.js
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user