← Files DataARCHIVED FILE

templates/data-app/base/src/components/DataAppHandoffDialog.jsx

3.32 KB · Sep 30, 2026 · 23:19 UTC

↓ Download file

import React, { useEffect, useLayoutEffect, useRef, useState } from "react";

import { navigateDataAppHandoff, registerDataAppHandoff } from "../data-app-handoff.js";
import { readHandoffDestination, writeHandoffDestination } from "../handoff-preference.js";
import { dataAppPromptTarget } from "../runtime-environment.js";
import { refreshCoachmarkDomain } from "../refresh-coachmark.js";
import { Checkbox, Dialog } from "./ui.jsx";

export function DataAppHandoffDialog({ onStatus }) {
  const closeTimer = useRef();
  const [pending, setPending] = useState(null);
  const [remember, setRemember] = useState(false);

  useLayoutEffect(() => registerDataAppHandoff(request => {
    const hrefs = {};
    function getHref(destination) {
      if (!Object.hasOwn(hrefs, destination)) {
        try { hrefs[destination] = request.getHref(destination); }
        catch { hrefs[destination] = null; }
      }
      return hrefs[destination];
    }
    const destination = readHandoffDestination();
    try {
      if (destination && getHref(destination) && !request.requireTap)
        return navigateDataAppHandoff(hrefs[destination], request.onNavigate);
    } catch { /* Keep the chooser available if remembered navigation fails. */ }
    const available = ["desktop", "web"].map(getHref).filter(Boolean);
    if (available.length === 1) return navigateDataAppHandoff(available[0], request.onNavigate);
    window.clearTimeout(closeTimer.current);
    setRemember(false);
    setPending({ onNavigate: request.onNavigate, hrefs: { desktop: getHref("desktop"), web: getHref("web") } });
    return true;
  }), []);
  useEffect(() => () => window.clearTimeout(closeTimer.current), []);

  return <Dialog open={Boolean(pending)} title="Open in ChatGPT"
    className="data-app-handoff-dialog" backdropClassName="data-app-handoff-backdrop" onClose={() => setPending(null)}>
    {pending && <>
      <p className="data-app-handoff-question">Where would you like to open this?</p>
      <div className="data-app-handoff-choices">
        {[["desktop", "Open in desktop"], ["web", "Open on web"]].map(([value, label]) => {
          const href = pending.hrefs[value];
          if (!href) return <button key={value} type="button" className="button" disabled>{label}</button>;
          return <a key={value} className="button" href={href} target={dataAppPromptTarget(href)}
            rel="noopener noreferrer" onClick={() => {
              if (remember && !writeHandoffDestination(value)) {
                onStatus?.("Your choice couldn't be saved in this browser. You'll be asked again next time.");
              }
              pending.onNavigate?.();
              // Keep the chosen anchor mounted through native activation.
              closeTimer.current = window.setTimeout(() => setPending(null), 0);
            }}>{label}</a>;
        })}
      </div>
      {!pending.hrefs.web && <p className="data-app-handoff-question">This preview is local to this task. Open it in desktop, or publish it before using ChatGPT on the web.</p>}
      <footer className="data-app-handoff-footer">
        {refreshCoachmarkDomain() && <Checkbox checked={remember} onChange={event => setRemember(event.target.checked)}>
          Remember in this browser
        </Checkbox>}
        <button type="button" className="button" onClick={() => setPending(null)}>Cancel</button>
      </footer>
    </>}
  </Dialog>;
}

SHA-256: 3bd1db7e4c0a5804d2dbed3625b096f3b25b1d4dc8441e8817a278355faea2bb