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 */