drag n drop

This commit is contained in:
Chandler Abraham
2025-05-29 22:27:48 -07:00
parent b312ff63a9
commit ba4db567cf
3 changed files with 151 additions and 0 deletions
+2
View File
@@ -24,6 +24,7 @@ import { initTheme, activateTheme, theme } from './themes.mjs';
import { sliderPlugin, updateSliderWidgets } from './slider.mjs';
import { widgetPlugin, updateWidgets } from './widget.mjs';
import { persistentAtom } from '@nanostores/persistent';
import { dragDropPlugin, dragDropStyles } from './dragdrop.mjs';
const extensions = {
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
@@ -78,6 +79,7 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
javascript(),
sliderPlugin,
widgetPlugin,
dragDropPlugin,
// indentOnInput(), // works without. already brought with javascript extension?
// bracketMatching(), // does not do anything
syntaxHighlighting(defaultHighlightStyle),
+142
View File
@@ -0,0 +1,142 @@
import { ViewPlugin } from '@codemirror/view';
import { logger } from '@strudel/core';
// Helper function to read file content
async function readFileContent(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => resolve(e.target.result);
reader.onerror = reject;
reader.readAsText(file);
});
}
// Helper function to check if file is a code file
function isCodeFile(file) {
const codeExtensions = [
'.js', '.mjs', '.ts', '.tsx', '.jsx',
'.json', '.txt', '.md', '.tidal', '.strudel',
'.html', '.css', '.scss', '.yaml', '.yml',
'.xml', '.csv', '.log', '.ini', '.conf'
];
const fileName = file.name.toLowerCase();
return codeExtensions.some(ext => fileName.endsWith(ext)) || file.type.startsWith('text/');
}
// Create drag and drop extension
export const dragDropPlugin = ViewPlugin.fromClass(
class {
constructor(view) {
this.view = view;
this.handleDrop = this.handleDrop.bind(this);
this.handleDragOver = this.handleDragOver.bind(this);
this.handleDragEnter = this.handleDragEnter.bind(this);
this.handleDragLeave = this.handleDragLeave.bind(this);
// Add event listeners
view.dom.addEventListener('drop', this.handleDrop);
view.dom.addEventListener('dragover', this.handleDragOver);
view.dom.addEventListener('dragenter', this.handleDragEnter);
view.dom.addEventListener('dragleave', this.handleDragLeave);
}
handleDragOver(e) {
e.preventDefault();
e.stopPropagation();
e.dataTransfer.dropEffect = 'copy';
}
handleDragEnter(e) {
e.preventDefault();
e.stopPropagation();
this.view.dom.classList.add('cm-drag-over');
}
handleDragLeave(e) {
e.preventDefault();
e.stopPropagation();
// Only remove the class if we're leaving the editor entirely
if (!this.view.dom.contains(e.relatedTarget)) {
this.view.dom.classList.remove('cm-drag-over');
}
}
async handleDrop(e) {
e.preventDefault();
e.stopPropagation();
this.view.dom.classList.remove('cm-drag-over');
const files = Array.from(e.dataTransfer.files);
// Filter for code files only
const codeFiles = files.filter(isCodeFile);
if (codeFiles.length === 0) {
logger('No code files were dropped. Please drop text-based files.', 'warning');
return;
}
try {
// Read all files
const fileContents = await Promise.all(
codeFiles.map(async (file) => {
const content = await readFileContent(file);
return `// File: ${file.name}\n${content}`;
})
);
// Combine content
const newContent = fileContents.join('\n\n');
// Replace entire editor contents
this.view.dispatch({
changes: { from: 0, to: this.view.state.doc.length, insert: newContent },
selection: { anchor: newContent.length }
});
// Focus the editor
this.view.focus();
// Show success message
const fileNames = codeFiles.map(f => f.name).join(', ');
logger(`Successfully loaded ${codeFiles.length} file(s): ${fileNames}`, 'highlight');
// Dispatch custom event for external notification handling
this.view.dom.dispatchEvent(new CustomEvent('files-dropped', {
detail: {
success: true,
fileCount: codeFiles.length,
fileNames: fileNames
}
}));
} catch (error) {
console.error('Error reading dropped files:', error);
logger(`Error loading files: ${error.message}`, 'error');
// Dispatch error event
this.view.dom.dispatchEvent(new CustomEvent('files-dropped', {
detail: {
success: false,
error: error.message
}
}));
}
}
destroy() {
this.view.dom.removeEventListener('drop', this.handleDrop);
this.view.dom.removeEventListener('dragover', this.handleDragOver);
this.view.dom.removeEventListener('dragenter', this.handleDragEnter);
this.view.dom.removeEventListener('dragleave', this.handleDragLeave);
}
}
);
// CSS for drag over effect
export const dragDropStyles = `
.cm-editor.cm-drag-over {
outline: 2px dashed #4CAF50;
outline-offset: -2px;
background-color: rgba(76, 175, 80, 0.05);
}
`;
+7
View File
@@ -69,3 +69,10 @@
text-decoration: underline 0.18rem;
text-underline-offset: 0.22rem;
}
/* Drag and drop styles */
#code .cm-editor.cm-drag-over {
outline: 2px dashed #4CAF50;
outline-offset: -2px;
background-color: rgba(76, 175, 80, 0.05) !important;
}