@setoelkahfi / svara / commits / ba2ac33

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>&#x25b6</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