FloatingMenu.res.mjs
1// Generated by ReScript, PLEASE EDIT WITH CARE
2
3import * as Editor from "./Editor.res.mjs";
4import * as NanoID from "../ffi/NanoID.res.mjs";
5import * as Preact from "../ffi/Preact.res.mjs";
6import * as PreactDOM from "../ffi/PreactDOM.res.mjs";
7import * as Hooks from "preact/hooks";
8import * as LucidePreact from "lucide-preact";
9import * as Signals from "@preact/signals";
10import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
11import * as JsxRuntime from "preact/jsx-runtime";
12import * as Menus from "@tiptap/react/menus";
13
14let show = Signals.signal(false);
15
16let focusedIndex = Signals.signal(0);
17
18let state = {
19 timeout: null,
20 editor: null
21};
22
23function closeMenu() {
24 let id = state.timeout;
25 if (!(id == null)) {
26 clearTimeout(id);
27 }
28 state.timeout = setTimeout(() => {
29 show.value = false;
30 }, 500);
31}
32
33function stopClose() {
34 let id = state.timeout;
35 if (!(id == null)) {
36 clearTimeout(id);
37 return;
38 }
39}
40
41function setEditor(e) {
42 state.editor = e;
43 if (e.isFocused) {
44 return true;
45 } else {
46 return show.value;
47 }
48}
49
50function $$do(callback) {
51 let e = state.editor;
52 if (!(e == null)) {
53 callback(e);
54 }
55 show.value = false;
56}
57
58function content(c) {
59 let e = state.editor;
60 if (!(e == null)) {
61 e.commands.insertContent(c);
62 }
63 show.value = false;
64}
65
66function html(h) {
67 let e = state.editor;
68 if (!(e == null)) {
69 e.commands.insertContent(h);
70 }
71 show.value = false;
72}
73
74function selectFn(sig) {
75 return result => {
76 let content1 = {
77 type: "waypoint",
78 attrs: {
79 id: NanoID.make(),
80 point: [
81 result.lon,
82 result.lat
83 ],
84 label: result.name,
85 country: result.country,
86 gid: result.gid.toString(),
87 from: undefined,
88 cons: [],
89 nonroutable: false,
90 error: undefined
91 }
92 };
93 let content2 = {
94 type: "text",
95 content: " "
96 };
97 let editor = state.editor;
98 if (!(editor == null)) {
99 editor.commands.insertContent(content1);
100 editor.commands.insertContent(content2);
101 }
102 show.value = false;
103 sig.value = undefined;
104 };
105}
106
107function selectWaypoint(docID) {
108 let sig = Editor.onSearchSelectCache.get(docID);
109 if (sig === undefined) {
110 return;
111 }
112 let sig$1 = Primitive_option.valFromOption(sig);
113 sig$1.value = selectFn(sig$1);
114}
115
116function waypointClickHandler(docID) {
117 return _evt => selectWaypoint(docID);
118}
119
120function FloatingMenu(props) {
121 let doc = props.doc;
122 let baseBtn = "py-3 md:py-1 w-full text-lg md:text-sm hover:cursor-pointer flex flex-row items-center";
123 let highlighted = " bg-[var(--color-blue-100)]";
124 let containerRef = Hooks.useRef(null);
125 let menuRef = Hooks.useRef(null);
126 let item0Id = "floating-menu-waypoint-" + doc;
127 let item1Id = "floating-menu-image-" + doc;
128 let item2Id = "floating-menu-video-" + doc;
129 let getItemId = index => {
130 switch (index) {
131 case 1 :
132 return item1Id;
133 case 2 :
134 return item2Id;
135 default:
136 return item0Id;
137 }
138 };
139 Signals.effect(() => {
140 if (!show.value) {
141 return;
142 }
143 let el = menuRef.current;
144 if (!(el == null)) {
145 el.focus();
146 return;
147 }
148 });
149 Signals.effect(() => {
150 if (!show.value) {
151 focusedIndex.value = 0;
152 return;
153 }
154 });
155 Hooks.useEffect(() => {
156 let handleKeyDown = evt => {
157 if (evt.metaKey && evt.shiftKey && evt.key === "P") {
158 evt.preventDefault();
159 if (!show.value) {
160 show.value = true;
161 return;
162 } else {
163 return;
164 }
165 }
166 };
167 let documentElement = document;
168 let cleanup = PreactDOM.addEventListener(documentElement, "keydown", handleKeyDown);
169 return () => cleanup();
170 }, []);
171 let isOpen = show.value;
172 Hooks.useEffect(() => {
173 if (!isOpen) {
174 return;
175 }
176 let handleKeyDown = evt => {
177 let key = evt.key;
178 let shiftKey = evt.shiftKey;
179 let current = focusedIndex.value;
180 let exit = 0;
181 switch (key) {
182 case "ArrowUp" :
183 evt.preventDefault();
184 let prev = current > 0 ? current - 1 | 0 : 2;
185 focusedIndex.value = prev;
186 break;
187 case "Enter" :
188 if (current === 0) {
189 selectWaypoint(doc);
190 }
191 break;
192 case "Escape" :
193 evt.preventDefault();
194 show.value = false;
195 let e = state.editor;
196 if (!(e == null)) {
197 e.commands.focus();
198 }
199 break;
200 case "ArrowDown" :
201 case "Tab" :
202 exit = 1;
203 break;
204 }
205 if (exit === 1 && !(key === "Tab" && shiftKey)) {
206 evt.preventDefault();
207 let next = current < 2 ? current + 1 | 0 : 0;
208 focusedIndex.value = next;
209 }
210 if (!(key === "Tab" && shiftKey)) {
211 return;
212 }
213 evt.preventDefault();
214 let prev$1 = current > 0 ? current - 1 | 0 : 2;
215 focusedIndex.value = prev$1;
216 };
217 let documentElement = document;
218 let cleanup = PreactDOM.addEventListener(documentElement, "keydown", handleKeyDown);
219 return () => cleanup();
220 }, [isOpen]);
221 Hooks.useEffect(() => {
222 if (!isOpen) {
223 return;
224 }
225 let handleMouseDown = evt => {
226 let target = evt.target;
227 let container = containerRef.current;
228 if (container == null) {
229 return;
230 }
231 let isInside = container.contains(target);
232 if (isInside) {
233 return;
234 }
235 show.value = false;
236 let e = state.editor;
237 if (!(e == null)) {
238 e.commands.focus();
239 return;
240 }
241 };
242 let documentElement = document;
243 let cleanup = PreactDOM.addEventListener(documentElement, "mousedown", handleMouseDown);
244 return () => cleanup();
245 }, [isOpen]);
246 let index = focusedIndex.value;
247 return JsxRuntime.jsx(Menus.FloatingMenu, {
248 options: {
249 placement: "left-start"
250 },
251 shouldShow: props => setEditor(props.editor),
252 children: JsxRuntime.jsxs("div", {
253 children: [
254 JsxRuntime.jsx("button", {
255 children: JsxRuntime.jsx(LucidePreact.GripVertical, {
256 className: "w-5"
257 }),
258 "aria-expanded": show.value,
259 "aria-haspopup": "menu",
260 className: "hover:cursor-pointer hover:bg-[var(--color-bg-2)] rounded-sm text-[var(--color-blue)] my-auto",
261 type: "button",
262 onClick: _evt => {
263 show.value = !show.value;
264 }
265 }),
266 show.value ? JsxRuntime.jsxs("div", {
267 children: [
268 JsxRuntime.jsxs("button", {
269 children: [
270 JsxRuntime.jsx(LucidePreact.LocateFixed, {
271 className: "p-[2px]"
272 }),
273 JsxRuntime.jsx("span", {
274 children: "Add waypoint",
275 className: "ml-2"
276 })
277 ],
278 className: baseBtn + (
279 index === 0 ? highlighted : " bg-[var(--color-paper)]"
280 ),
281 id: item0Id,
282 role: "menuitem",
283 tabIndex: -1,
284 onClick: _evt => selectWaypoint(doc),
285 onMouseEnter: _evt => {
286 focusedIndex.value = 0;
287 }
288 }),
289 JsxRuntime.jsxs("button", {
290 children: [
291 JsxRuntime.jsx(LucidePreact.Image, {
292 className: "p-[2px]"
293 }),
294 JsxRuntime.jsx("span", {
295 children: "Upload image",
296 className: "ml-2"
297 }),
298 JsxRuntime.jsx("span", {
299 children: " (soon!)",
300 className: "italic ml-2 text-xs"
301 })
302 ],
303 "aria-disabled": true,
304 className: baseBtn + (
305 index === 1 ? highlighted : " bg-[var(--color-paper)]"
306 ),
307 id: item1Id,
308 role: "menuitem",
309 tabIndex: -1,
310 disabled: true,
311 onMouseEnter: _evt => {
312 focusedIndex.value = 1;
313 }
314 }),
315 JsxRuntime.jsxs("button", {
316 children: [
317 JsxRuntime.jsx(LucidePreact.SquarePlay, {
318 className: "p-[2px]"
319 }),
320 JsxRuntime.jsx("span", {
321 children: "Link YT Video",
322 className: "ml-2"
323 }),
324 JsxRuntime.jsx("span", {
325 children: " (soon!)",
326 className: "italic ml-2 text-xs"
327 })
328 ],
329 "aria-disabled": true,
330 className: baseBtn + (
331 index === 2 ? highlighted : " bg-[var(--color-paper)]"
332 ),
333 id: item2Id,
334 role: "menuitem",
335 tabIndex: -1,
336 disabled: true,
337 onMouseEnter: _evt => {
338 focusedIndex.value = 2;
339 }
340 })
341 ],
342 ref: Primitive_option.some(menuRef),
343 "aria-activedescendant": getItemId(index),
344 className: "flex flex-col active:outline-0 focus:outline-0",
345 role: "menu",
346 tabIndex: 0,
347 onMouseEnter: _evt => stopClose(),
348 onMouseLeave: _evt => closeMenu()
349 }) : null
350 ],
351 ref: Primitive_option.some(containerRef),
352 className: Preact.cls("fixed left-0 box-border flex flex-row items-center", {
353 "shadow-xl/20 bg-[var(--color-paper)] ring-2 p-[2px] ring-[var(--color-blue)] rounded-sm ml-2": show.value
354 })
355 })
356 });
357}
358
359let make = FloatingMenu;
360
361export {
362 show,
363 focusedIndex,
364 state,
365 closeMenu,
366 stopClose,
367 setEditor,
368 $$do,
369 content,
370 html,
371 selectFn,
372 selectWaypoint,
373 waypointClickHandler,
374 make,
375}
376/* show Not a pure module */