/**
* The header NSFW switch. The whole app defaults to SFW; this is the only control
* that lets adult content through (it flips `settings.includeAdult`, which the engine
* gates on). Turning it ON requires an explicit confirmation; turning it OFF is
* immediate. The choice rides in `settings` and so is remembered in this browser.
*
* In the online build the switch is `locked`: it renders greyed with a tooltip and, when clicked,
* opens the full-version page instead of toggling (adult content needs the local desktop app).
* @module gui/components/NsfwToggle
*/
import { useEffect, useState } from "react";
import { useIntl, defineMessages } from "react-intl";
import { lockedHint, openFullVersion } from "../lib/online.js";
import { jsxDEV as _jsxDEV, Fragment as _Fragment } from "react/jsx-dev-runtime";
const msgs = defineMessages({
nsfwContent: {
id: "nsfw.feature",
defaultMessage: "NSFW content",
description: "Feature noun phrase used in the online-locked tooltip"
},
titleOn: {
id: "nsfw.titleOn",
defaultMessage: "Adult content is on — click to switch back to SFW"
},
titleOff: {
id: "nsfw.titleOff",
defaultMessage: "Show adult (NSFW) content"
},
confirmTitle: {
id: "nsfw.confirmTitle",
defaultMessage: "Show adult content?"
},
confirmBody: {
id: "nsfw.confirmBody",
defaultMessage: "Enabling NSFW unlocks explicit adult words, artists, and generators in prompts and previews. Only continue if you are 18 or older and want to see this material."
},
confirmNote: {
id: "nsfw.confirmNote",
defaultMessage: "Your choice is remembered in this browser. You can switch back to SFW anytime."
},
cancel: {
id: "nsfw.cancel",
defaultMessage: "Cancel"
},
enable: {
id: "nsfw.enable",
defaultMessage: "Enable NSFW"
}
});
/**
* @param {object} props
* @param {object} props.settings The current settings (`includeAdult`).
* @param {Function} props.setSettings Update the settings.
* @param {boolean} [props.locked] When true, the switch is disabled (online build) and clicking it
* opens the full-version page.
* @returns {JSX.Element} The right-aligned NSFW switch (+ its confirm dialog).
*/
export default function NsfwToggle({
settings,
setSettings,
locked = false
}) {
const intl = useIntl();
const on = !locked && settings.includeAdult === true;
const [confirming, setConfirming] = useState(false);
// Escape cancels the confirm dialog.
useEffect(() => {
if (!confirming) return undefined;
const onKey = e => e.key === "Escape" && setConfirming(false);
document.addEventListener("keydown", onKey);
return () => document.removeEventListener("keydown", onKey);
}, [confirming]);
// Locked (online): clicking sends people to the full version. Otherwise: disabling is immediate;
// enabling asks first.
const toggle = () => {
if (locked) {
openFullVersion();
return;
}
if (on) setSettings(s => ({
...s,
includeAdult: false
}));else setConfirming(true);
};
const enable = () => {
setSettings(s => ({
...s,
includeAdult: true
}));
setConfirming(false);
};
return /*#__PURE__*/_jsxDEV("div", {
className: "nsfw-toggle",
children: [/*#__PURE__*/_jsxDEV("button", {
type: "button",
role: "switch",
"aria-checked": on,
"aria-disabled": locked || undefined,
className: `switch${on ? " is-on" : ""}${locked ? " is-locked" : ""}`,
onClick: toggle,
title: locked ? lockedHint(intl, intl.formatMessage(msgs.nsfwContent)) : on ? intl.formatMessage(msgs.titleOn) : intl.formatMessage(msgs.titleOff),
children: [/*#__PURE__*/_jsxDEV("span", {
className: "switch-label",
children: "NSFW"
}, void 0, false), /*#__PURE__*/_jsxDEV("span", {
className: "switch-track",
"aria-hidden": "true",
children: /*#__PURE__*/_jsxDEV("span", {
className: "switch-thumb"
}, void 0, false)
}, void 0, false)]
}, void 0, true), confirming && /*#__PURE__*/_jsxDEV(_Fragment, {
children: [/*#__PURE__*/_jsxDEV("div", {
className: "modal-overlay",
onClick: () => setConfirming(false)
}, void 0, false), /*#__PURE__*/_jsxDEV("div", {
className: "modal",
role: "dialog",
"aria-modal": "true",
"aria-labelledby": "nsfw-confirm-title",
children: [/*#__PURE__*/_jsxDEV("h2", {
id: "nsfw-confirm-title",
children: intl.formatMessage(msgs.confirmTitle)
}, void 0, false), /*#__PURE__*/_jsxDEV("p", {
children: intl.formatMessage(msgs.confirmBody)
}, void 0, false), /*#__PURE__*/_jsxDEV("p", {
className: "modal-note",
children: intl.formatMessage(msgs.confirmNote)
}, void 0, false), /*#__PURE__*/_jsxDEV("div", {
className: "modal-actions",
children: [/*#__PURE__*/_jsxDEV("button", {
type: "button",
className: "btn-ghost",
onClick: () => setConfirming(false),
children: intl.formatMessage(msgs.cancel)
}, void 0, false), /*#__PURE__*/_jsxDEV("button", {
type: "button",
className: "btn-danger",
onClick: enable,
children: intl.formatMessage(msgs.enable)
}, void 0, false)]
}, void 0, true)]
}, void 0, true)]
}, void 0, true)]
}, void 0, true);
}