open, interoperable sandbox platform for agents and humans 📦 ✨
pocketenv.io
claude-code
atproto
sandbox
openclaw
agent
1import { useEffect, useRef, useState } from "react";
2import AddVariableModal from "./AddVariableModal";
3import AddFileModal from "./AddFileModal";
4import AddVolumeModal from "./AddVolumeModal";
5import DeleteSandboxModal from "./DeleteSandboxModal";
6import AddSecretModal from "./AddSecretModal";
7import { Link } from "@tanstack/react-router";
8
9type ContextMenuProps = {
10 sandboxId: string;
11 uri?: string;
12};
13
14function ContextMenu({ sandboxId, uri }: ContextMenuProps) {
15 const dropdownRef = useRef<HTMLDivElement>(null);
16 const [open, setOpen] = useState(false);
17 const [
18 isAddEnvironmentVariableModalOpen,
19 setIsAddEnvironmentVariableModalOpen,
20 ] = useState(false);
21 const [isDeleteSandboxModalOpen, setIsDeleteSandboxModalOpen] =
22 useState(false);
23 const [isAddFileModalOpen, setIsAddFileModalOpen] = useState(false);
24 const [isAddSecretModalOpen, setIsAddSecretModalOpen] = useState(false);
25 const [isAddVolumeModalOpen, setIsAddVolumeModalOpen] = useState(false);
26
27 useEffect(() => {
28 if (!open) return;
29 function handleClickOutside(event: MouseEvent) {
30 if (
31 dropdownRef.current &&
32 !dropdownRef.current.contains(event.target as Node)
33 ) {
34 setOpen(false);
35 }
36 }
37 document.addEventListener("mousedown", handleClickOutside);
38 return () => {
39 document.removeEventListener("mousedown", handleClickOutside);
40 };
41 }, [open]);
42
43 const onOpenContextMenu = (e: React.MouseEvent) => {
44 e.stopPropagation();
45 setOpen(!open);
46 };
47
48 const onAddFile = (e: React.MouseEvent) => {
49 e.stopPropagation();
50 setOpen(false);
51 setIsAddFileModalOpen(true);
52 };
53
54 const onAddSecret = (e: React.MouseEvent) => {
55 e.stopPropagation();
56 setOpen(false);
57 setIsAddSecretModalOpen(true);
58 };
59
60 const onAddVariable = (e: React.MouseEvent) => {
61 e.stopPropagation();
62 setOpen(false);
63 setIsAddEnvironmentVariableModalOpen(true);
64 };
65
66 const onAddVolume = (e: React.MouseEvent) => {
67 e.stopPropagation();
68 setOpen(false);
69 setIsAddVolumeModalOpen(true);
70 };
71
72 const onDelete = (e: React.MouseEvent) => {
73 e.stopPropagation();
74 setOpen(false);
75 setIsDeleteSandboxModalOpen(true);
76 };
77
78 return (
79 <>
80 <div
81 ref={dropdownRef}
82 className={`dropdown relative inline-flex items-center [--auto-close:inside] [--offset:8] [--placement:bottom-end] ${
83 open ? "open" : ""
84 }`}
85 >
86 <button
87 type="button"
88 aria-haspopup="menu"
89 aria-expanded={open ? "true" : "false"}
90 aria-label="Dropdown"
91 className="dropdown-toggle btn btn-circle btn-text btn-sm bg-transparent outline-0 flex items-center"
92 onClick={onOpenContextMenu}
93 >
94 <span className="icon-[tabler--dots-vertical] size-5 hover:text-white block mx-auto"></span>
95 </button>
96 <ul
97 className={`dropdown-menu dropdown-open:opacity-100 absolute right-0 top-full mt-2 min-w-60 z-50 ${
98 open ? "" : "hidden"
99 }`}
100 role="menu"
101 aria-orientation="vertical"
102 aria-labelledby="dropdown-avatar"
103 >
104 <li>
105 <div className="dropdown-item cursor-pointer" onClick={onAddFile}>
106 Add File
107 </div>
108 </li>
109 <li>
110 <div className="dropdown-item cursor-pointer" onClick={onAddSecret}>
111 Add Secret
112 </div>
113 </li>
114 <li>
115 <div
116 className="dropdown-item cursor-pointer"
117 onClick={onAddVariable}
118 >
119 Add Variable
120 </div>
121 </li>
122 <li>
123 <div className="dropdown-item cursor-pointer" onClick={onAddVolume}>
124 Add Volume
125 </div>
126 </li>
127 <li>
128 <Link
129 to={`/${uri?.split("at://")[1]?.replace("io.pocketenv.", "")}/settings`}
130 className="dropdown-item cursor-pointer"
131 >
132 Settings
133 </Link>
134 </li>
135 <li>
136 <div className="dropdown-item cursor-pointer" onClick={onDelete}>
137 Delete
138 </div>
139 </li>
140 </ul>
141 </div>
142 <AddVariableModal
143 isOpen={isAddEnvironmentVariableModalOpen}
144 onClose={() => setIsAddEnvironmentVariableModalOpen(false)}
145 sandboxId={sandboxId}
146 />
147 <AddFileModal
148 isOpen={isAddFileModalOpen}
149 onClose={() => setIsAddFileModalOpen(false)}
150 sandboxId={sandboxId}
151 />
152 <AddVolumeModal
153 isOpen={isAddVolumeModalOpen}
154 onClose={() => setIsAddVolumeModalOpen(false)}
155 sandboxId={sandboxId}
156 />
157 <AddSecretModal
158 isOpen={isAddSecretModalOpen}
159 onClose={() => setIsAddSecretModalOpen(false)}
160 sandboxId={sandboxId}
161 />
162 <DeleteSandboxModal
163 isOpen={isDeleteSandboxModalOpen}
164 onClose={() => setIsDeleteSandboxModalOpen(false)}
165 sandboxId={sandboxId}
166 />
167 </>
168 );
169}
170
171export default ContextMenu;