| 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; |
| } |