/**
* The "wrapper" control: a floating bottom-right button that frames every generated prompt
* with a START and an END snippet (the v3 root layer = open + middle + close). Clicking it
* opens a small popover listing saved wrapper presets (apply one, or "None"); a "Manage
* presets" button there opens a modal with the two side-by-side Start/End editors plus
* preset naming/management. The selected wrapper lives in `settings.wrapper` (so it is
* shared by the share-link); the preset library lives in localStorage.
* @module gui/components/WrapperFab
*/
import { useIntl, defineMessages } from "react-intl";
import DplEditor from "./DplEditor.jsx";
import { useWrapperPresets } from "../lib/wrapper/useWrapperPresets.js";
import { jsxDEV as _jsxDEV, Fragment as _Fragment } from "react/jsx-dev-runtime";
const ico = {
width: 18,
height: 18,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
strokeWidth: 2,
strokeLinecap: "round",
strokeLinejoin: "round"
};
// Brackets — a "wraps around your prompt" glyph.
const WrapIcon = () => /*#__PURE__*/_jsxDEV("svg", {
...ico,
"aria-hidden": "true",
children: [/*#__PURE__*/_jsxDEV("path", {
d: "M8 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h2"
}, void 0, false), /*#__PURE__*/_jsxDEV("path", {
d: "M16 4h2a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-2"
}, void 0, false)]
}, void 0, true);
// Counter-clockwise arrow — "revert to default".
const RevertIcon = () => /*#__PURE__*/_jsxDEV("svg", {
...ico,
width: 15,
height: 15,
"aria-hidden": "true",
children: [/*#__PURE__*/_jsxDEV("path", {
d: "M3 4v6h6"
}, void 0, false), /*#__PURE__*/_jsxDEV("path", {
d: "M3.5 10a8 8 0 1 1-.5 6"
}, void 0, false)]
}, void 0, true);
const msgs = defineMessages({
triggerTitle: {
id: "wrapper.triggerTitle",
defaultMessage: "Wrapper: {name} — frames every prompt with a start and end"
},
presetsAria: {
id: "wrapper.presetsAria",
defaultMessage: "Wrapper presets"
},
wrapperHead: {
id: "wrapper.head",
defaultMessage: "Wrapper"
},
manage: {
id: "wrapper.manage",
defaultMessage: "Manage Wrappers"
},
autoToggleTitle: {
id: "wrapper.autoToggleTitle",
defaultMessage: "When on, blocks may add their own Auto Begin / Auto End framing to the prompt. When off, only this wrapper frames it."
},
autoToggleLabel: {
id: "wrapper.autoToggleLabel",
defaultMessage: "Allow used blocks to extend current wrapper"
},
nameDefault: {
id: "wrapper.nameDefault",
defaultMessage: "Default"
},
nameNone: {
id: "wrapper.nameNone",
defaultMessage: "None"
},
noSaved: {
id: "wrapper.noSaved",
defaultMessage: "No saved wrappers — Manage presets to add one."
},
manageAria: {
id: "wrapper.manageAria",
defaultMessage: "Manage wrappers"
},
close: {
id: "wrapper.close",
defaultMessage: "Close"
},
editDefault: {
id: "wrapper.editDefault",
defaultMessage: "Edit the Default wrapper"
},
editPreset: {
id: "wrapper.editPreset",
defaultMessage: "Edit “{name}”"
},
deletePreset: {
id: "wrapper.deletePreset",
defaultMessage: "Delete “{name}”"
},
deletePresetAria: {
id: "wrapper.deletePresetAria",
defaultMessage: "Delete {name}"
},
newPreset: {
id: "wrapper.newPreset",
defaultMessage: "+ New"
},
namePlaceholder: {
id: "wrapper.namePlaceholder",
defaultMessage: "Wrapper name…"
},
nameAria: {
id: "wrapper.nameAria",
defaultMessage: "Wrapper name"
},
start: {
id: "wrapper.start",
defaultMessage: "Start"
},
end: {
id: "wrapper.end",
defaultMessage: "End"
},
revertStartDefault: {
id: "wrapper.revertStartDefault",
defaultMessage: "Reset Start to the built-in default"
},
revertStartNormal: {
id: "wrapper.revertStartNormal",
defaultMessage: "Reset Start to the default wrapper"
},
revertEndDefault: {
id: "wrapper.revertEndDefault",
defaultMessage: "Reset End to the built-in default"
},
revertEndNormal: {
id: "wrapper.revertEndNormal",
defaultMessage: "Reset End to the default wrapper"
},
revertStartAria: {
id: "wrapper.revertStartAria",
defaultMessage: "Revert Start to default"
},
revertEndAria: {
id: "wrapper.revertEndAria",
defaultMessage: "Revert End to default"
},
startAria: {
id: "wrapper.startAria",
defaultMessage: "Wrapper start (DPL)"
},
endAria: {
id: "wrapper.endAria",
defaultMessage: "Wrapper end (DPL)"
},
startPlaceholderNew: {
id: "wrapper.startPlaceholderNew",
defaultMessage: "Rendered before the prompt…"
},
startPlaceholder: {
id: "wrapper.startPlaceholder",
defaultMessage: "Rendered before the prompt — e.g. a style or framing lead-in"
},
endPlaceholderNew: {
id: "wrapper.endPlaceholderNew",
defaultMessage: "Rendered after the prompt…"
},
endPlaceholder: {
id: "wrapper.endPlaceholder",
defaultMessage: "Rendered after the prompt — e.g. '{#fx}', '{#artists}'"
},
footHint: {
id: "wrapper.footHint",
defaultMessage: "Wraps every generated prompt: start, your prompt, end."
},
cancel: {
id: "wrapper.cancel",
defaultMessage: "Cancel"
},
save: {
id: "wrapper.save",
defaultMessage: "Save"
}
});
/**
* @param {object} props
* @param {object} props.settings Current settings (reads `wrapper` / `wrapperName`).
* @param {Function} props.setSettings Update settings.
* @returns {JSX.Element}
*/
export default function WrapperFab({
settings,
setSettings
}) {
const intl = useIntl();
// The built-in "Default" / "None" presets are stored under those literal keys but shown
// localized; custom preset names are user content and shown verbatim.
const displayName = n => n === "Default" ? intl.formatMessage(msgs.nameDefault) : n === "None" ? intl.formatMessage(msgs.nameNone) : n;
const {
view,
setView,
pos,
btnRef,
openList,
names,
activeName,
isActive,
def,
isNew,
sel,
name,
setName,
start,
setStart,
end,
setEnd,
applyWrapper,
openManage,
editPreset,
newPreset,
save,
del,
revertPane
} = useWrapperPresets({
settings,
setSettings
});
return /*#__PURE__*/_jsxDEV(_Fragment, {
children: [/*#__PURE__*/_jsxDEV("button", {
ref: btnRef,
className: `field-act wrap-trigger${isActive ? " on" : ""}`,
onClick: () => view === "list" ? setView("") : openList(),
title: intl.formatMessage(msgs.triggerTitle, {
name: displayName(activeName)
}),
"aria-label": intl.formatMessage(msgs.presetsAria),
"aria-pressed": view === "list",
children: /*#__PURE__*/_jsxDEV(WrapIcon, {}, void 0, false)
}, void 0, false), view === "list" && /*#__PURE__*/_jsxDEV(_Fragment, {
children: [/*#__PURE__*/_jsxDEV("div", {
className: "wrap-pop-scrim",
onClick: () => setView("")
}, void 0, false), /*#__PURE__*/_jsxDEV("div", {
className: "wrap-pop",
role: "menu",
"aria-label": intl.formatMessage(msgs.presetsAria),
style: pos ? {
position: "fixed",
right: pos.right,
bottom: pos.bottom,
maxHeight: pos.maxHeight
} : undefined,
children: [/*#__PURE__*/_jsxDEV("div", {
className: "wrap-pop-head",
children: [/*#__PURE__*/_jsxDEV("span", {
children: intl.formatMessage(msgs.wrapperHead)
}, void 0, false), /*#__PURE__*/_jsxDEV("button", {
className: "wrap-manage-btn",
onClick: () => openManage(),
children: intl.formatMessage(msgs.manage)
}, void 0, false)]
}, void 0, true), /*#__PURE__*/_jsxDEV("label", {
className: "wrap-auto-toggle",
title: intl.formatMessage(msgs.autoToggleTitle),
children: [/*#__PURE__*/_jsxDEV("input", {
type: "checkbox",
checked: settings.useAutoSections !== false,
onChange: e => setSettings({
...settings,
useAutoSections: e.target.checked
})
}, void 0, false), /*#__PURE__*/_jsxDEV("span", {
children: intl.formatMessage(msgs.autoToggleLabel)
}, void 0, false)]
}, void 0, true), /*#__PURE__*/_jsxDEV("div", {
className: "wrap-pop-list",
children: [/*#__PURE__*/_jsxDEV("button", {
className: `wrap-pop-item${activeName === "Default" ? " on" : ""}`,
onClick: () => applyWrapper("Default"),
children: [/*#__PURE__*/_jsxDEV("span", {
className: "wrap-pop-name",
children: intl.formatMessage(msgs.nameDefault)
}, void 0, false), activeName === "Default" && /*#__PURE__*/_jsxDEV("span", {
className: "wrap-check",
children: "✓"
}, void 0, false)]
}, void 0, true), /*#__PURE__*/_jsxDEV("button", {
className: `wrap-pop-item${activeName === "None" ? " on" : ""}`,
onClick: () => applyWrapper("None"),
children: [/*#__PURE__*/_jsxDEV("span", {
className: "wrap-pop-name",
children: intl.formatMessage(msgs.nameNone)
}, void 0, false), activeName === "None" && /*#__PURE__*/_jsxDEV("span", {
className: "wrap-check",
children: "✓"
}, void 0, false)]
}, void 0, true), names.length === 0 && /*#__PURE__*/_jsxDEV("p", {
className: "wrap-empty",
children: intl.formatMessage(msgs.noSaved)
}, void 0, false), names.map(n => /*#__PURE__*/_jsxDEV("button", {
className: `wrap-pop-item${activeName === n ? " on" : ""}`,
onClick: () => applyWrapper(n),
children: [/*#__PURE__*/_jsxDEV("span", {
className: "wrap-pop-name",
children: n
}, void 0, false), activeName === n && /*#__PURE__*/_jsxDEV("span", {
className: "wrap-check",
children: "✓"
}, void 0, false)]
}, n, true))]
}, void 0, true)]
}, void 0, true)]
}, void 0, true), view === "manage" && /*#__PURE__*/_jsxDEV(_Fragment, {
children: [/*#__PURE__*/_jsxDEV("div", {
className: "wrap-modal-overlay",
onClick: () => setView("list")
}, void 0, false), /*#__PURE__*/_jsxDEV("div", {
className: "wrap-modal",
role: "dialog",
"aria-label": intl.formatMessage(msgs.manageAria),
children: [/*#__PURE__*/_jsxDEV("div", {
className: "wrap-modal-head",
children: [/*#__PURE__*/_jsxDEV("h2", {
children: intl.formatMessage(msgs.manage)
}, void 0, false), /*#__PURE__*/_jsxDEV("div", {
className: "grow"
}, void 0, false), /*#__PURE__*/_jsxDEV("button", {
className: "drawer-close",
onClick: () => setView("list"),
"aria-label": intl.formatMessage(msgs.close),
children: "×"
}, void 0, false)]
}, void 0, true), /*#__PURE__*/_jsxDEV("div", {
className: "wrap-modal-body",
children: [/*#__PURE__*/_jsxDEV("div", {
className: "wrap-presets",
children: [/*#__PURE__*/_jsxDEV("div", {
className: "wrap-preset-pills",
children: [/*#__PURE__*/_jsxDEV("span", {
className: `wrap-preset-pill${sel === "Default" ? " on" : ""}`,
children: /*#__PURE__*/_jsxDEV("button", {
className: "wrap-preset-pick",
onClick: () => editPreset("Default"),
title: intl.formatMessage(msgs.editDefault),
children: intl.formatMessage(msgs.nameDefault)
}, void 0, false)
}, void 0, false), names.map(n => /*#__PURE__*/_jsxDEV("span", {
className: `wrap-preset-pill${sel === n ? " on" : ""}`,
children: [/*#__PURE__*/_jsxDEV("button", {
className: "wrap-preset-pick",
onClick: () => editPreset(n),
title: intl.formatMessage(msgs.editPreset, {
name: n
}),
children: n
}, void 0, false), /*#__PURE__*/_jsxDEV("button", {
className: "wrap-preset-del",
onClick: () => del(n),
title: intl.formatMessage(msgs.deletePreset, {
name: n
}),
"aria-label": intl.formatMessage(msgs.deletePresetAria, {
name: n
}),
children: "×"
}, void 0, false)]
}, n, true)), /*#__PURE__*/_jsxDEV("button", {
className: "wrap-preset-new",
onClick: newPreset,
children: intl.formatMessage(msgs.newPreset)
}, void 0, false)]
}, void 0, true), /*#__PURE__*/_jsxDEV("input", {
className: "panel-input wrap-name",
placeholder: intl.formatMessage(msgs.namePlaceholder),
value: name,
onChange: e => setName(e.target.value),
readOnly: sel === "Default",
"aria-label": intl.formatMessage(msgs.nameAria)
}, void 0, false)]
}, void 0, true), /*#__PURE__*/_jsxDEV("div", {
className: "wrap-grid",
children: [/*#__PURE__*/_jsxDEV("label", {
className: "wrap-box",
children: [/*#__PURE__*/_jsxDEV("span", {
className: "wrap-box-head",
children: [/*#__PURE__*/_jsxDEV("span", {
className: "wrap-box-label",
children: intl.formatMessage(msgs.start)
}, void 0, false), /*#__PURE__*/_jsxDEV("button", {
type: "button",
className: "wrap-revert",
onClick: () => revertPane("start"),
title: intl.formatMessage(sel === "Default" ? msgs.revertStartDefault : msgs.revertStartNormal),
"aria-label": intl.formatMessage(msgs.revertStartAria),
children: /*#__PURE__*/_jsxDEV(RevertIcon, {}, void 0, false)
}, void 0, false)]
}, void 0, true), /*#__PURE__*/_jsxDEV(DplEditor, {
value: start,
onChange: setStart,
settings: settings,
ariaLabel: intl.formatMessage(msgs.startAria),
placeholder: isNew ? def.start || intl.formatMessage(msgs.startPlaceholderNew) : intl.formatMessage(msgs.startPlaceholder)
}, void 0, false)]
}, void 0, true), /*#__PURE__*/_jsxDEV("label", {
className: "wrap-box",
children: [/*#__PURE__*/_jsxDEV("span", {
className: "wrap-box-head",
children: [/*#__PURE__*/_jsxDEV("span", {
className: "wrap-box-label",
children: intl.formatMessage(msgs.end)
}, void 0, false), /*#__PURE__*/_jsxDEV("button", {
type: "button",
className: "wrap-revert",
onClick: () => revertPane("end"),
title: intl.formatMessage(sel === "Default" ? msgs.revertEndDefault : msgs.revertEndNormal),
"aria-label": intl.formatMessage(msgs.revertEndAria),
children: /*#__PURE__*/_jsxDEV(RevertIcon, {}, void 0, false)
}, void 0, false)]
}, void 0, true), /*#__PURE__*/_jsxDEV(DplEditor, {
value: end,
onChange: setEnd,
settings: settings,
ariaLabel: intl.formatMessage(msgs.endAria),
placeholder: isNew ? def.end || intl.formatMessage(msgs.endPlaceholderNew) : intl.formatMessage(msgs.endPlaceholder)
}, void 0, false)]
}, void 0, true)]
}, void 0, true)]
}, void 0, true), /*#__PURE__*/_jsxDEV("div", {
className: "wrap-modal-foot",
children: [/*#__PURE__*/_jsxDEV("span", {
className: "wrap-foot-hint",
children: intl.formatMessage(msgs.footHint)
}, void 0, false), /*#__PURE__*/_jsxDEV("div", {
className: "grow"
}, void 0, false), /*#__PURE__*/_jsxDEV("button", {
className: "ghost",
onClick: () => setView("list"),
children: intl.formatMessage(msgs.cancel)
}, void 0, false), /*#__PURE__*/_jsxDEV("button", {
className: "primary",
onClick: save,
disabled: !name.trim(),
children: intl.formatMessage(msgs.save)
}, void 0, false)]
}, void 0, true)]
}, void 0, true)]
}, void 0, true)]
}, void 0, true);
}