blob: f8faa83d29204a7d2d232809f6f3ce65c0878caa [file]
import 'dart:async';
import 'package:dartpad_editor/dartpad_editor.dart';
sealed class ResourceNode {
final WorkspaceResource resource;
const ResourceNode(this.resource);
}
class FileResourceNode extends ResourceNode {
const FileResourceNode(WorkspaceFile super.resource);
}
class FolderResourceNode extends ResourceNode {
final List<ResourceNode> children;
FolderResourceNode(WorkspaceFolder super.resource, [List<ResourceNode>? children]) : children = children ?? [];
void addNode(String targetPath, ResourceNode newNode) {
final parentPath = workspaceContext.normalize(workspaceContext.dirname(targetPath));
final normalizedNodePath = workspaceContext.normalize(resource.path);
if (parentPath == normalizedNodePath) {
children.removeWhere((child) => child.resource.path == targetPath);
children.add(newNode);
sortChildren();
return;
}
bool foundParentDir = false;
for (final child in children) {
if (child is FolderResourceNode) {
final childPath = workspaceContext.normalize(child.resource.path);
if (parentPath == childPath || parentPath.startsWith('$childPath/')) {
foundParentDir = true;
child.addNode(targetPath, newNode);
break;
}
}
}
if (!foundParentDir &&
(parentPath.startsWith('$normalizedNodePath/') || (normalizedNodePath.isEmpty && parentPath.isNotEmpty))) {
final remaining = normalizedNodePath.isEmpty ? parentPath : parentPath.substring(normalizedNodePath.length + 1);
final nextSegment = remaining.split('/').first;
final nextFolderPath = normalizedNodePath.isEmpty ? nextSegment : '$normalizedNodePath/$nextSegment';
final newFolderNode = FolderResourceNode(
WorkspaceFolder(workspace: resource.workspace, path: nextFolderPath),
);
children.add(newFolderNode);
sortChildren();
newFolderNode.addNode(targetPath, newNode);
}
}
void removeNode(String targetPath) {
final countBefore = children.length;
children.removeWhere((child) => child.resource.path == targetPath);
if (children.length < countBefore) {
return;
}
for (final child in children) {
if (child is FolderResourceNode) {
final childPath = workspaceContext.normalize(child.resource.path);
final normalizedTargetPath = workspaceContext.normalize(targetPath);
if (normalizedTargetPath.startsWith('$childPath/')) {
child.removeNode(targetPath);
break;
}
}
}
}
ResourceNode? findNode(String targetPath) {
final normalizedTargetPath = workspaceContext.normalize(targetPath);
final normalizedParentPath = workspaceContext.normalize(resource.path);
if (normalizedTargetPath == normalizedParentPath) {
return this;
}
for (final child in children) {
final childPath = workspaceContext.normalize(child.resource.path);
if (normalizedTargetPath == childPath) {
return child;
}
if (child is FolderResourceNode) {
if (normalizedTargetPath.startsWith('$childPath/')) {
return child.findNode(targetPath);
}
}
}
return null;
}
void sortChildren() {
children.sort((a, b) {
if (a is FolderResourceNode && b is FileResourceNode) {
return -1;
}
if (a is FileResourceNode && b is FolderResourceNode) {
return 1;
}
return a.resource.shortName.compareTo(b.resource.shortName);
});
}
}
enum CreateTreeEntryKind { file, folder }
class FileTreeState {
const FileTreeState({
required this.root,
required this.collapsedFolders,
required this.selectedFolder,
required this.dropTargetFolder,
required this.draggedFile,
required this.draggedFolder,
required this.activeFile,
required this.creatingTreeEntry,
required this.createTargetFolder,
required this.renamingFile,
required this.renamingFolder,
required this.renameValue,
required this.nameValidationError,
});
final FolderResourceNode root;
final Set<String> collapsedFolders;
final String? selectedFolder;
final String? dropTargetFolder;
final String? draggedFile;
final String? draggedFolder;
final String activeFile;
final CreateTreeEntryKind? creatingTreeEntry;
final String createTargetFolder;
final String? renamingFile;
final String? renamingFolder;
final String renameValue;
final String? nameValidationError;
}
class FileTreeActions {
const FileTreeActions({
required this.onNewTreeEntryNameChanged,
required this.onRenameValueChanged,
required this.onStartAddingFile,
required this.onStartAddingFolder,
required this.onBlurCreateEntry,
required this.onConfirmAddFile,
required this.onConfirmAddFolder,
required this.onCancelAddFile,
required this.onStartRenamingFile,
required this.onStartRenamingFolder,
required this.onConfirmRenameFile,
required this.onConfirmRenameFolder,
required this.onCancelRename,
required this.onSelectFolder,
required this.onToggleFolder,
required this.onClearFolderSelection,
required this.onStartDraggingFile,
required this.onStartDraggingFolder,
required this.onFinishDragging,
required this.onMarkDropTarget,
required this.onClearDropTarget,
required this.onDropIntoFolder,
required this.onOpenFile,
required this.onDeleteFile,
required this.onDeleteFolder,
});
final void Function(String value) onNewTreeEntryNameChanged;
final void Function(String value) onRenameValueChanged;
final void Function() onStartAddingFile;
final void Function() onStartAddingFolder;
final void Function() onBlurCreateEntry;
final void Function() onConfirmAddFile;
final void Function() onConfirmAddFolder;
final void Function() onCancelAddFile;
final void Function(String fileName) onStartRenamingFile;
final void Function(String folderName) onStartRenamingFolder;
final void Function(String fileName) onConfirmRenameFile;
final void Function(String folderName) onConfirmRenameFolder;
final void Function() onCancelRename;
final void Function(String folder) onSelectFolder;
final void Function(String folder) onToggleFolder;
final void Function() onClearFolderSelection;
final void Function(String fileName) onStartDraggingFile;
final void Function(String folder) onStartDraggingFolder;
final void Function() onFinishDragging;
final void Function(String folder) onMarkDropTarget;
final void Function(String folder) onClearDropTarget;
final void Function(String folder) onDropIntoFolder;
final FutureOr<void> Function(String fileName) onOpenFile;
final void Function(String fileName) onDeleteFile;
final void Function(String folderName) onDeleteFolder;
}