added context menus
mellbacon committed
May 14, 2023 at 17:28 UTC
ba2ac3323b3098be478429ab3971afaf438d7ffe
4 files changed
+193
-8
src/lib/FileTree/FileTreeList.svelte
+28
-3
@@ -9,6 +9,7 @@
9
import FileTreeNode, { computeTreeLeafDepth } from "./FileTreeNode.svelte";
10
import Directory from "../../util/icons/Directory.svelte";
11
import {filetree} from "../FileTree.svelte";
12
+ import ContextMenu from "../utility/ContextMenu.svelte";
13
14
export let root = false;
15
export let isroot = false;
@@ -26,6 +27,18 @@
27
28
let icon = Directory;
29
30
+ let contextmenu = false;
31
+ let contextmenuitems = [
32
+ {name: "Open in File Explorer", shortcut: "AHHH", action: () => {console.log("click")}},
33
+ {name: "Copy", shortcut: "Ctrl + C", action: () => {console.log("click")}},
34
+ {name: "Cut", shortcut: "Ctrl + X", action: () => {console.log("click")}},
35
+ {name: "Paste", shortcut: "Ctrl + X", disabled: true, action: () => {console.log("click")}},
36
+ {name: "Copy Filename", shortcut: "AHHH", action: () => {console.log("click")}},
37
+ {name: "Copy Absolute Path", shortcut: "AHHH", action: () => {console.log("click")}},
38
+ {name: "Rename...", shortcut: "F2", disabled: true, action: () => {console.log("click")}},
39
+ {name: "Delete", shortcut: "Delete", action: () => {console.log("click")}}
40
+ ]
41
+
42
function findNode(nodeid, tree = null) {
43
if (!tree) {
44
tree = $filetree;
@@ -91,7 +104,9 @@
104
event.target.classList.add("selected")
105
}
106
94
- expanded = _expansionState[label] = !expanded;
107
+ if (!contextmenu) {
108
+ expanded = _expansionState[label] = !expanded;
109
+ }
110
};
111
112
function offset () {
@@ -119,9 +134,14 @@
134
{/if}
135
{/each}
136
{:else}
122
- <li id={`filetree-node-${id}`} bind:this={ref} class="treenode" class:root={isroot} on:dragenter|stopPropagation={dragenter} on:dragleave|stopPropagation={dragleave} on:dragend|stopPropagation={dragleave}>
137
+ <li id={`filetree-node-${id}`} bind:this={ref} class="treenode" class:root={isroot} on:dragenter|stopPropagation={dragenter} on:dragleave|stopPropagation={dragleave} on:dragend|stopPropagation={dragleave} title={path}>
138
<!-- svelte-ignore a11y-click-events-have-key-events -->
124
- <div bind:this={refLabel} class:selected class="tree-label" on:click={toggleExpansion} draggable={true} on:dragstart={dragstart}>
139
+ <div bind:this={refLabel} class:selected class="tree-label" on:click={toggleExpansion} draggable={true} on:dragstart={dragstart} on:mouseup={(e) => {
140
+ if (e.button === 2) {
141
+ contextmenu = true;
142
+ }
143
+ //toggleExpansion(e);
144
+ }}>
145
<span class="arrow" class:arrowDown>▶</span>
146
{#if icon}
147
<svelte:component this={icon} />
@@ -142,6 +162,11 @@
162
</li>
163
{/if}
164
165
+{#if contextmenu}
166
+ <ContextMenu target={refLabel} items={contextmenuitems}></ContextMenu>
167
+{/if}
168
+
169
+
170
<style lang="scss">
171
.arrow {
172
display: flex;
src/lib/FileTree/FileTreeNode.svelte
+27
-3
@@ -17,6 +17,7 @@
17
<script lang="ts">
18
import { createEventDispatcher } from "svelte";
19
import File from "../../util/icons/File.svelte";
20
+ import ContextMenu from "../utility/ContextMenu.svelte";
21
22
export let id;
23
export let label;
@@ -28,6 +29,18 @@
29
let refLabel = null;
30
let icon = File;
31
32
+ let contextmenu = false;
33
+ let contextmenuitems = [
34
+ {name: "Open in File Explorer", shortcut: "AHHH", action: () => {console.log("click")}},
35
+ {name: "Copy", shortcut: "Ctrl + C", action: () => {console.log("click")}},
36
+ {name: "Cut", shortcut: "Ctrl + X", action: () => {console.log("click")}},
37
+ {name: "Copy Filename", shortcut: "AHHH", action: () => {console.log("click")}},
38
+ {name: "Copy Absolute Path", shortcut: "AHHH", action: () => {console.log("click")}},
39
+ {name: "Edit", shortcut: "AHHH", action: () => {console.log("click")}},
40
+ {name: "Rename...", shortcut: "F2", disabled: true, action: () => {console.log("click")}},
41
+ {name: "Delete", shortcut: "Delete", action: () => {console.log("click")}}
42
+ ]
43
+
44
function dragstart(e) {
45
let data = {element: `filetree-node-${id}`, id:id, type: "node"};
46
e.dataTransfer.setData("text/plain", JSON.stringify(data));
@@ -43,7 +56,9 @@
56
if (event.target === refLabel) {
57
event.target.classList.add("selected")
58
}
46
- dispatch("nodeselect", {node});
59
+ if (!contextmenu) {
60
+ dispatch("nodeselect", {node});
61
+ }
62
}
63
64
$: if (refLabel) {
@@ -60,9 +75,14 @@
75
}
76
}}></svelte:window>
77
63
-<li id={`filetree-node-${id}`} bind:this={ref} class="treenode">
78
+<li id={`filetree-node-${id}`} bind:this={ref} class="treenode" title={path}>
79
<!-- svelte-ignore a11y-click-events-have-key-events -->
65
- <div bind:this={refLabel} class="tree-label" class:selected on:click={(e) => {handleSelect(treenode, e)}} draggable={true} on:dragstart={dragstart}>
80
+ <div bind:this={refLabel} class="tree-label" class:selected on:click={(e) => {handleSelect(treenode, e)}} draggable={true} on:dragstart={dragstart} on:mouseup={(e) => {
81
+ if (e.button === 2) {
82
+ contextmenu = true;
83
+ handleSelect(treenode, e);
84
+ }
85
+ }}>
86
<span class="no-arrow"></span>
87
{#if icon}
88
<svelte:component this={icon} />
@@ -71,6 +91,10 @@
91
</div>
92
</li>
93
94
+{#if contextmenu}
95
+ <ContextMenu target={refLabel} items={contextmenuitems}></ContextMenu>
96
+{/if}
97
+
98
<style lang="scss">
99
.treenode {
100
display: flex;
src/lib/Tab.svelte
+20
-2
@@ -1,6 +1,6 @@
1
<script lang="ts">
2
- import { setActive } from "./scripts/Tab";
3
- import { closeTab } from "./scripts/Tab";
2
+ import { setActive, closeTab } from "./scripts/Tab";
3
+ import ContextMenu from "./utility/ContextMenu.svelte";
4
5
export let id = 0;
6
export let label = "Untitled-1";
@@ -8,12 +8,26 @@
8
export let active = false;
9
10
let tab = null;
11
+
12
+ let contextmenu = false;
13
+ let contextmenuitems = [
14
+ {name: "Close Tab", shortcut: "AHHH", action: () => {console.log("click")}},
15
+ {name: "Close Others", shortcut: "Ctrl + C", action: () => {console.log("click")}},
16
+ {name: "Close All Right", shortcut: "Ctrl + X", action: () => {console.log("click")}},
17
+ {name: "Close All Left", shortcut: "AHHH", action: () => {console.log("click")}},
18
+ {name: "Close All Saved", shortcut: "AHHH", action: () => {console.log("click")}},
19
+ {name: "Close All", shortcut: "AHHH", action: () => {console.log("click")}},
20
+ {name: "Copy Filename", shortcut: "F2", disabled: true, action: () => {console.log("click")}},
21
+ {name: "Copy Absolute Path", shortcut: "F2", disabled: true, action: () => {console.log("click")}},
22
+ {name: "Show In File Explorer", shortcut: "Delete", action: () => {console.log("click")}}
23
+ ]
24
</script>
25
<div bind:this={tab} title={path} id={`editorTab-${id}`} class="tab" class:active={active}>
26
<!-- svelte-ignore a11y-click-events-have-key-events -->
27
<div class="tab-content" on:mousedown = {
28
(e) => {
29
if (e.button === 0) setActive(id);
30
+ if (e.button === 2) contextmenu = true;
31
}
32
}>
33
<span class="tab-label">{label}</span>
@@ -24,6 +38,10 @@
38
</div>
39
</div>
40
41
+{#if contextmenu}
42
+ <ContextMenu target={tab} items={contextmenuitems}></ContextMenu>
43
+{/if}
44
+
45
<style lang="scss">
46
.tab {
47
height: 100%;
src/lib/utility/ContextMenu.svelte
new
+118
@@ -0,0 +1,118 @@
1
+<script lang="ts">
2
+ import { onMount } from "svelte";
3
+ import { appWindow } from "@tauri-apps/api/window";
4
+
5
+ export let items = [];
6
+ export let target: HTMLElement = null;
7
+
8
+ let ref = null;
9
+ let menuOpen = false;
10
+
11
+ let cursorPos = { x: 0, y: 0 }
12
+ let menuPos = { h: 0, w: 0 }
13
+ let windowSize = { h: 0, w: 0 } // keep track of window borders
14
+
15
+ onMount(() => {
16
+ return () => {
17
+ if (target !== null) {
18
+ target.removeEventListener("contextmenu", openContextMenu)
19
+ }
20
+ }
21
+ })
22
+
23
+ async function openContextMenu(e) {
24
+ const appWindowSize = await appWindow.innerSize();
25
+ let windowWidth = appWindowSize.width;
26
+ let windowHeight = appWindowSize.height;
27
+
28
+ menuOpen = true;
29
+ windowSize = {w: windowWidth, h: windowHeight}
30
+ cursorPos = {x: e.clientX, y: e.clientY}
31
+
32
+ // Adjust context menu position based on where it is on the window
33
+ // If it overlaps with the window border then move it to the right/left accordingly
34
+ if (windowSize.h - cursorPos.y < menuPos.h) {
35
+ cursorPos.y = cursorPos.y - menuPos.h
36
+ }
37
+ if (windowSize.w - cursorPos.x < menuPos.w) {
38
+ cursorPos.x = cursorPos.x - menuPos.w
39
+ }
40
+ }
41
+
42
+ function getContextMenuDimension(node){
43
+ // This function will get context menu dimension
44
+ // when navigation is shown => showMenu = true
45
+ let height = node.offsetHeight;
46
+ let width = node.offsetWidth;
47
+ menuPos = {
48
+ w: width,
49
+ h: height
50
+ }
51
+ }
52
+
53
+ $: if (target !== null) {
54
+ target.addEventListener("contextmenu", openContextMenu)
55
+ }
56
+</script>
57
+
58
+<svelte:window on:contextmenu|preventDefault={(e) => {
59
+ menuOpen = false;
60
+ if (target !== null) return;
61
+ openContextMenu(e);
62
+}} on:click={(e) => {
63
+ menuOpen = false;
64
+}}></svelte:window>
65
+
66
+{#if menuOpen}
67
+ <div use:getContextMenuDimension class="context-menu" bind:this={ref} style="top:{cursorPos.y}px; left:{cursorPos.x}px">
68
+ {#each items as item}
69
+ <!-- svelte-ignore a11y-click-events-have-key-events -->
70
+ <div class="context-menu-option" class:disabled={item.disabled} on:click={() => {
71
+ if (!item.disabled) {
72
+ item.action()
73
+ }
74
+ }}>
75
+ <span class="option-name">{item.name}</span>
76
+ {#if item.shortcut}
77
+ <span class="option-shortcut"></span>
78
+ {/if}
79
+ </div>
80
+ {/each}
81
+ </div>
82
+{/if}
83
+
84
+<style lang="scss">
85
+ .context-menu {
86
+ min-width: 10rem;
87
+ max-width: 18rem;
88
+ background-color: #2D2D2D;
89
+ padding: 0.25rem;
90
+ z-index: 9999;
91
+ position: absolute;
92
+ box-shadow: rgb(0 0 0 / 10%) 0px 0px 4px 2px;
93
+ }
94
+ .context-menu-option {
95
+ display: flex;
96
+ align-items: center;
97
+ justify-content: space-between;
98
+ height: 17px;
99
+ padding: 5px 0;
100
+ width: 100%;
101
+ font-size: 0.875rem;
102
+ cursor: pointer;
103
+ &:hover {
104
+ background-color: #414040;
105
+ }
106
+ }
107
+ .option-name, .option-shortcut {
108
+ padding: 0 10px;
109
+ }
110
+ .option-shortcut {
111
+ color: #8c8c8c;
112
+ margin-left: 10px;
113
+ }
114
+ .disabled {
115
+ color: #6b6b6b;
116
+ cursor: not-allowed;
117
+ }
118
+</style>
\ No newline at end of file