master
svelte 144 lines 4.51 KB
Raw
1 <script lang="ts" context="module">
2 export function computeTreeLeafDepth(node) {
3 let depth = 0;
4
5 if (node == null) return depth;
6
7 let parentNode = node.parentNode;
8
9 while (parentNode != null && parentNode.getAttribute("role") !== "tree") {
10 parentNode = parentNode.parentNode;
11 if (parentNode.tagName === "LI") depth++;
12 }
13
14 return depth;
15 }
16 </script>
17 <script lang="ts">
18 import { createEventDispatcher } from "svelte";
19 import File from "../Icons/File.svelte";
20 import ContextMenu from "../utility/ContextMenu.svelte";
21 import { moveToTrash, openInExplorer, renameFile } from "../File";
22 import { addEditorTab } from "../EditorTabList.svelte";
23 import { clipboard } from "@tauri-apps/api";
24 import { commands } from "../../config/commands";
25
26 export let id;
27 export let name;
28 export let path;
29 export let contextMenuEnabled;
30 export let iconsEnabled;
31 export let canDrag = false;
32
33 let selected = false;
34
35 let ref = null;
36 let refLabel = null;
37 let icon = File;
38
39 let contextmenu = false;
40 let contextmenuitems = [
41 {name: "Open in File Explorer", shortcut: commands.openInExplorer.keybind, action: async () => {commands.openInExplorer.command(path)}},
42 {name: "Copy", shortcut: "Ctrl + C", action: async () => {await clipboard.writeText(path)}},
43 {name: "Cut", shortcut: "Ctrl + X", action: () => {console.warn("Feature not implemented yet.")}},
44 {name: "Copy Filename", shortcut: "", action: async () => {await clipboard.writeText(name)}},
45 {name: "Edit", shortcut: "", action: () => {addEditorTab(path, name)}},
46 {name: "Rename...", shortcut: commands.renameFile.keybind, action: () => {commands.renameFile.command(name, path)}},
47 {name: "Delete", shortcut: "Delete", action: async () => {await moveToTrash(path)}}
48 ]
49
50 function dragstart(e) {
51 let data = {element: `filetree-node-${id}`, id:id, type: "file"};
52 e.dataTransfer.setData("text/plain", JSON.stringify(data));
53 }
54
55 const offset = () => computeTreeLeafDepth(refLabel) + (icon ? 2 : 1);
56
57 const dispatch = createEventDispatcher();
58 function handleSelect(node, event) {
59 for (let nodes of document.getElementsByClassName("tree-label")) {
60 nodes.classList.remove("selected")
61 }
62 if (event.target === refLabel) {
63 event.target.classList.add("selected")
64 }
65 if (!contextmenu) {
66 dispatch("nodeselect", {node});
67 }
68 }
69
70 function handleDoubleSelect(node, event) {
71 for (let nodes of document.getElementsByClassName("tree-label")) {
72 nodes.classList.remove("selected")
73 }
74 if (event.target === refLabel) {
75 event.target.classList.add("selected")
76 }
77 if (!contextmenu) {
78 dispatch("dblnodeselect", {node});
79 }
80 }
81
82 $: if (refLabel) {
83 refLabel.style.marginLeft = `-${offset()}rem`;
84 refLabel.style.paddingLeft = `${offset()}rem`;
85 }
86
87 $:treenode = {id, name, path}
88 </script>
89
90 <svelte:window on:click={(e) => {
91 if (e.target !== refLabel) {
92 refLabel.classList.remove("selected")
93 contextmenu = false;
94 }
95 }}></svelte:window>
96
97 <li id={`filetree-node-${id}`} bind:this={ref} class="treenode" title={path}>
98 <!-- svelte-ignore a11y-click-events-have-key-events -->
99 <div bind:this={refLabel} class="tree-label" class:selected
100 on:click={(e) => {handleSelect(treenode, e)}}
101 on:dblclick={(e) => {handleDoubleSelect(treenode, e)}}
102 draggable={canDrag} on:dragstart={dragstart} on:mouseup={(e) => {
103 if (e.button === 2 && contextMenuEnabled) {
104 contextmenu = true;
105 handleSelect(treenode, e);
106 }
107 }}>
108 <span class="no-arrow"></span>
109 {#if icon && iconsEnabled}
110 <svelte:component this={icon} />
111 {/if}
112 {name}
113 </div>
114 </li>
115
116 {#if contextmenu}
117 <ContextMenu target={refLabel} items={contextmenuitems}></ContextMenu>
118 {/if}
119
120 <style lang="scss">
121 .treenode {
122 display: flex;
123 align-items: center;
124 cursor: pointer;
125 padding-left: 1rem;
126 }
127 .tree-label {
128 display: flex;
129 min-height: 1.5rem;
130 font-size: 14px;
131 align-items: center;
132 flex: 1;
133 white-space: nowrap;
134 :global(svg){
135 min-width: 18px;
136 min-height: 18px;
137 padding: 0 5px 0 0;
138 }
139 }
140 .no-arrow {
141 display: flex;
142 margin-right: 15px;
143 }
144 </style>