UNPKG

@activecollab/components

Version:

ActiveCollab Components

391 lines (379 loc) 21.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LinkingCommandsButton = void 0; var _react = _interopRequireWildcard(require("react")); var _styledComponents = _interopRequireDefault(require("styled-components")); var _Button = require("../../components/Button"); var _ChooseV = require("../../components/ChooseV2"); var _Dialog = require("../../components/Dialog"); var _IconButton = require("../../components/IconButton"); var _Icons = require("../../components/Icons"); var _Loaders = require("../../components/Loaders"); var _Tooltip = require("../../components/Tooltip"); var _Typography = require("../../components/Typography"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function _arrayWithHoles(r) { if (Array.isArray(r)) return r; } /** * "Linking and Commands" button — a plain design-system secondary button (small, * rounded, no icon, no AI gradient: this is not an AI feature). Clicking it opens * a read-only reference dialog. * * This mock mirrors the shipped `activecollab-next` implementation * (`src/components/tasks/SourceCode/LinkingAndCommandsButton.jsx` + * `LinkingAndCommandsDialog.jsx`, task #457754): a 640px dialog with a short * pitch, a centered ChooseV2 to switch conventions (Linking default), and two * tabs. Linking shows the reference forms for this task and a copyable * `git checkout -b …` suggestion; Commands shows an API-shaped table of action * words and slash commands, plus a fixed example commit. Both the reference * guide and the suggested-branch request are represented by short skeleton * loads, the way the real dialog fetches them once it is opened. */ var SANS = '-apple-system, BlinkMacSystemFont, "Roboto", "Helvetica Neue", Arial, sans-serif'; var MONO = "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace"; /** The example task these instructions are written against. */ var TASK_ID = 4231; /** Task URL, in production built with `~/utils/urls` `getTaskUrl`. */ var TASK_URL = "https://app.activecollab.com/1/projects/2110/tasks/".concat(TASK_ID); /** * Suggested branch name for this task — in production it is resolved per repo * via GET .../tasks/{task_id}/source/suggest-branch-name. Mocked here. */ var SUGGESTED_BRANCH = "ilija/".concat(TASK_ID, "-add-cacak-region-support"); /** POSIX-safe single-quote escaping for a value dropped into a shell command. */ var shellQuote = function shellQuote(value) { return "'".concat(value.replace(/'/g, "'\\''"), "'"); }; /* ------------------------------------------------------------------ */ /* Reference-guide data — shaped like the GET .../source/reference-guide */ /* payload the real dialog is driven by: a map of action words plus a */ /* flat list of slash commands. Descriptions are localised server-side; */ /* tokens and examples never translate. */ /* ------------------------------------------------------------------ */ /** Action words act on the linked task from a title, commit, or PR body. */ var ACTION_WORDS = { close: { primary: "Close", example: "Close #".concat(TASK_ID), description: "Closes the task when its pull request is merged.", aliases: ["Closes", "Fixes", "Fixed", "Resolves", "Resolved"] } }; /** Slash commands act on the task from a commit message or PR description. */ var COMMANDS = [{ name: "spend", syntax: "/spend {duration} [of {job type}]", example: "/spend 1h 30m of Design", description: "Logs the time you spent on the task." }, { name: "estimate", syntax: "/estimate {duration}", example: "/estimate 4h", description: "Sets the task's time estimate." }, { name: "comment", syntax: "/comment {text}", example: "/comment Deployed to staging", description: "Posts a comment on the task." }, { name: "done", syntax: "/done", example: "/done", description: "Marks the task complete.", aliases: ["/complete"] }, { name: "reopen", syntax: "/reopen", example: "/reopen", description: "Reopens a completed task." }, { name: "assign", syntax: "/assign @{member}", example: "/assign @ilija", description: "Assigns the task to a team member." }, { name: "label", syntax: "/label {name}", example: "/label In Progress", description: "Adds a label to the task." }]; /** Action words first, then the slash commands — the shipped row order. */ var TABLE_ROWS = [...Object.values(ACTION_WORDS).map(function (word) { return { key: "action-".concat(word.primary), example: word.example, description: word.description, aliases: word.aliases }; }), ...COMMANDS.map(function (command) { return { key: "command-".concat(command.name), example: command.example, format: command.syntax, description: command.description, aliases: command.aliases }; })]; /** * Illustrative commit message: closes the task on the subject line, logs time, * leaves a note. Fixed sample text — only the task id is interpolated, and * durations are printed verbatim (never reformatted). */ var EXAMPLE_COMMIT = "Fix ranks reset when a contract is archived, closes #".concat(TASK_ID, "\n\n/spend 1h 30m\n/comment Verified on staging, ranks hold after archive"); /* ------------------------------------------------------------------ */ /* Trigger button — plain DS secondary button, rounded to the compact */ /* "Suggest Subtasks" shape (6px), no icon, no gradient. */ /* ------------------------------------------------------------------ */ var TriggerButton = (0, _styledComponents.default)(_Button.Button).withConfig({ displayName: "LinkingCommandsButton__TriggerButton", componentId: "sc-11q4kcm-0" })(["&&{border-radius:6px;}"]); /** Wider than the default 540px dialog, matching the shipped 640px width. */ var StyledDialog = (0, _styledComponents.default)(_Dialog.Dialog).withConfig({ displayName: "LinkingCommandsButton__StyledDialog", componentId: "sc-11q4kcm-1" })(["&&{width:640px;max-width:calc(100vw - 64px);}"]); var StyledTitleRow = _styledComponents.default.div.withConfig({ displayName: "LinkingCommandsButton__StyledTitleRow", componentId: "sc-11q4kcm-2" })(["display:flex;align-items:center;justify-content:space-between;gap:12px;"]); var StyledBody = _styledComponents.default.div.withConfig({ displayName: "LinkingCommandsButton__StyledBody", componentId: "sc-11q4kcm-3" })(["font-family:", ";display:flex;flex-direction:column;gap:16px;max-height:60vh;overflow-y:auto;.code{font-family:", ";background:var(--color-theme-200);color:var(--color-theme-900);border-radius:4px;padding:2px 6px;font-size:12px;}.lc-term{font-weight:600;color:var(--color-theme-900);}.lc-leads{display:flex;flex-direction:column;gap:8px;}.lc-lead{display:flex;align-items:flex-start;gap:8px;}.lc-lead svg{flex-shrink:0;width:16px;height:16px;margin-top:2px;color:var(--color-theme-600);}.lc-choosectl > div{justify-content:center;}.lc-tabbody{display:flex;flex-direction:column;gap:20px;}.lc-commands{gap:16px;}.lc-block{display:flex;flex-direction:column;gap:8px;}.lc-list{display:flex;flex-direction:column;gap:6px;margin:0;padding-left:20px;list-style:disc;}.lc-table{display:grid;grid-template-columns:max-content 1fr;column-gap:24px;}.lc-th{padding-bottom:8px;border-bottom:1px solid var(--border-primary);}.lc-td{padding:12px 0;border-bottom:1px solid var(--border-primary);}.lc-format{display:block;margin-top:4px;font-family:", ";}.lc-aliases{margin-top:12px;}.lc-skeleton{display:flex;flex-direction:column;gap:12px;}.lc-skel{border-radius:6px;}.lc-skel-line{height:20px;}.lc-skel-block{height:36px;width:100%;}.lc-skel-2-3{width:66%;}.lc-skel-1-2{width:50%;}.lc-branch-skel{width:100%;height:36px;border-radius:6px;}"], SANS, MONO, MONO); /* Copyable code snippet — mirrors the shared CopySnippet: a theme-200 <pre> with a copy control that reveals on hover (hidden when hideCopyButton). */ var StyledSnippet = _styledComponents.default.div.withConfig({ displayName: "LinkingCommandsButton__StyledSnippet", componentId: "sc-11q4kcm-4" })(["position:relative;pre{margin:0;background:var(--color-theme-200);color:var(--color-theme-900);border-radius:6px;padding:12px 16px;overflow-x:auto;font-family:", ";font-size:13px;line-height:1.5;}.lc-copy-btn{position:absolute;top:6px;right:6px;opacity:0;transition:opacity 0.15s ease;}&:hover .lc-copy-btn{opacity:1;}.lc-copy-btn svg{width:16px;height:16px;}"], MONO); var TAB_OPTIONS = [{ id: "linking", name: "Linking" }, { id: "commands", name: "Commands" }]; var CopySnippet = function CopySnippet(_ref) { var code = _ref.code, _ref$hideCopyButton = _ref.hideCopyButton, hideCopyButton = _ref$hideCopyButton === void 0 ? false : _ref$hideCopyButton; var _useState = (0, _react.useState)(false), _useState2 = _slicedToArray(_useState, 2), copied = _useState2[0], setCopied = _useState2[1]; var copy = function copy() { var _navigator$clipboard; // eslint-disable-next-line compat/compat void ((_navigator$clipboard = navigator.clipboard) === null || _navigator$clipboard === void 0 ? void 0 : _navigator$clipboard.writeText(code)); setCopied(true); window.setTimeout(function () { return setCopied(false); }, 1500); }; return /*#__PURE__*/_react.default.createElement(StyledSnippet, null, /*#__PURE__*/_react.default.createElement("pre", null, /*#__PURE__*/_react.default.createElement("code", null, code)), !hideCopyButton && /*#__PURE__*/_react.default.createElement(_Tooltip.Tooltip, { title: copied ? "Copied!" : "Copy" }, /*#__PURE__*/_react.default.createElement("button", { type: "button", className: "lc-copy-btn", onClick: copy, "aria-label": "Copy command", style: { border: "none", background: "transparent", cursor: "pointer", padding: 4, display: "inline-flex", color: "var(--color-theme-600)" } }, /*#__PURE__*/_react.default.createElement(_Icons.CopyIcon, null)))); }; var LinkingTab = function LinkingTab(_ref2) { var branchLoading = _ref2.branchLoading, branch = _ref2.branch; var command = branch ? "git checkout -b ".concat(shellQuote(branch)) : ""; return /*#__PURE__*/_react.default.createElement("div", { className: "lc-tabbody" }, /*#__PURE__*/_react.default.createElement("div", { className: "lc-block" }, /*#__PURE__*/_react.default.createElement(_Typography.Body2, { color: "secondary" }, "Mention the task in your git activity and it links to this task on its own:"), /*#__PURE__*/_react.default.createElement("ul", { className: "lc-list" }, /*#__PURE__*/_react.default.createElement("li", null, /*#__PURE__*/_react.default.createElement(_Typography.Body2, { color: "secondary" }, /*#__PURE__*/_react.default.createElement("span", { className: "lc-term" }, "In a branch name:"), " the raw task ID", " ", /*#__PURE__*/_react.default.createElement("span", { className: "code" }, TASK_ID))), /*#__PURE__*/_react.default.createElement("li", null, /*#__PURE__*/_react.default.createElement(_Typography.Body2, { color: "secondary" }, /*#__PURE__*/_react.default.createElement("span", { className: "lc-term" }, "In a title, commit message, or description:"), " ", /*#__PURE__*/_react.default.createElement("span", { className: "code" }, "#", TASK_ID), " or", " ", /*#__PURE__*/_react.default.createElement("span", { className: "code" }, "AC-", TASK_ID))), /*#__PURE__*/_react.default.createElement("li", null, /*#__PURE__*/_react.default.createElement(_Typography.Body2, { color: "secondary" }, /*#__PURE__*/_react.default.createElement("span", { className: "lc-term" }, "Anywhere:"), " the task URL", " ", /*#__PURE__*/_react.default.createElement("span", { className: "code" }, TASK_URL))))), branchLoading || command ? /*#__PURE__*/_react.default.createElement("div", { className: "lc-block" }, /*#__PURE__*/_react.default.createElement(_Typography.Body2, { color: "secondary" }, "Give the branch a name with the task number in it and it links the moment you push. Create one from the suggested name:"), branchLoading ? /*#__PURE__*/_react.default.createElement(_Loaders.SkeletonLoader, { className: "lc-branch-skel" }) : /*#__PURE__*/_react.default.createElement(CopySnippet, { code: command })) : null); }; var CommandsTab = function CommandsTab() { return /*#__PURE__*/_react.default.createElement("div", { className: "lc-tabbody lc-commands" }, /*#__PURE__*/_react.default.createElement(_Typography.Body2, { color: "secondary" }, "The table below lists action words and slash commands that you can use in a commit message or a pull request description to act on the linked task. Example:"), /*#__PURE__*/_react.default.createElement(CopySnippet, { code: EXAMPLE_COMMIT, hideCopyButton: true }), /*#__PURE__*/_react.default.createElement("div", { className: "lc-table" }, /*#__PURE__*/_react.default.createElement(_Typography.Caption1, { color: "tertiary", className: "lc-th" }, "Command"), /*#__PURE__*/_react.default.createElement(_Typography.Caption1, { color: "tertiary", className: "lc-th" }, "What it does"), TABLE_ROWS.map(function (row) { var _row$aliases; return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, { key: row.key }, /*#__PURE__*/_react.default.createElement("div", { className: "lc-td" }, /*#__PURE__*/_react.default.createElement("span", { className: "code" }, row.example), row.format ? /*#__PURE__*/_react.default.createElement(_Typography.Caption1, { color: "tertiary", className: "lc-format" }, row.format) : null), /*#__PURE__*/_react.default.createElement("div", { className: "lc-td" }, /*#__PURE__*/_react.default.createElement(_Typography.Body2, { color: "secondary" }, row.description), (_row$aliases = row.aliases) !== null && _row$aliases !== void 0 && _row$aliases.length ? /*#__PURE__*/_react.default.createElement(_Typography.Body2, { color: "secondary", className: "lc-aliases" }, "Aliases: ", row.aliases.join(", ")) : null)); }))); }; var LinkingCommandsButton = exports.LinkingCommandsButton = function LinkingCommandsButton() { var _useState3 = (0, _react.useState)(false), _useState4 = _slicedToArray(_useState3, 2), open = _useState4[0], setOpen = _useState4[1]; var _useState5 = (0, _react.useState)("linking"), _useState6 = _slicedToArray(_useState5, 2), tab = _useState6[0], setTab = _useState6[1]; var _useState7 = (0, _react.useState)(true), _useState8 = _slicedToArray(_useState7, 2), guideLoading = _useState8[0], setGuideLoading = _useState8[1]; var _useState9 = (0, _react.useState)(true), _useState0 = _slicedToArray(_useState9, 2), branchLoading = _useState0[0], setBranchLoading = _useState0[1]; var _useState1 = (0, _react.useState)(null), _useState10 = _slicedToArray(_useState1, 2), suggestedBranch = _useState10[0], setSuggestedBranch = _useState10[1]; // Mock the two endpoint loads the real dialog fires once it opens: the // reference guide resolves first (~0.6s), then the suggested branch name // (~1.1s), each showing its own skeleton until it lands. (0, _react.useEffect)(function () { if (!open) return undefined; setGuideLoading(true); setBranchLoading(true); setSuggestedBranch(null); var guideTimer = window.setTimeout(function () { return setGuideLoading(false); }, 600); var branchTimer = window.setTimeout(function () { setSuggestedBranch(SUGGESTED_BRANCH); setBranchLoading(false); }, 1100); return function () { window.clearTimeout(guideTimer); window.clearTimeout(branchTimer); }; }, [open]); var openDialog = function openDialog() { setTab("linking"); setOpen(true); }; return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(TriggerButton, { type: "button", variant: "secondary", size: "small", onClick: openDialog }, "Linking and Commands"), /*#__PURE__*/_react.default.createElement(StyledDialog, { open: open, onClose: function onClose() { return setOpen(false); }, enableBackgroundClick: true }, /*#__PURE__*/_react.default.createElement(_Dialog.Dialog.Title, { disableDefaultHeading: true }, /*#__PURE__*/_react.default.createElement(StyledTitleRow, null, /*#__PURE__*/_react.default.createElement(_Typography.Header3, null, "Linking and Commands"), /*#__PURE__*/_react.default.createElement(_IconButton.IconButton, { type: "button", variant: "text gray", "aria-label": "Close", onClick: function onClick() { return setOpen(false); } }, /*#__PURE__*/_react.default.createElement(_Icons.CancelCrossIcon, null)))), /*#__PURE__*/_react.default.createElement(_Dialog.Dialog.ContentDivider, null), /*#__PURE__*/_react.default.createElement(_Dialog.Dialog.Content, null, /*#__PURE__*/_react.default.createElement(StyledBody, null, guideLoading ? /*#__PURE__*/_react.default.createElement("div", { className: "lc-skeleton" }, /*#__PURE__*/_react.default.createElement(_Loaders.SkeletonLoader, { className: "lc-skel lc-skel-line lc-skel-2-3" }), /*#__PURE__*/_react.default.createElement(_Loaders.SkeletonLoader, { className: "lc-skel lc-skel-block" }), /*#__PURE__*/_react.default.createElement(_Loaders.SkeletonLoader, { className: "lc-skel lc-skel-block" }), /*#__PURE__*/_react.default.createElement(_Loaders.SkeletonLoader, { className: "lc-skel lc-skel-line lc-skel-1-2" })) : /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_Typography.Body2, null, "Keep a task in sync with your code without leaving your editor."), /*#__PURE__*/_react.default.createElement("div", { className: "lc-leads" }, /*#__PURE__*/_react.default.createElement("div", { className: "lc-lead" }, /*#__PURE__*/_react.default.createElement(_Icons.HashIcon, null), /*#__PURE__*/_react.default.createElement(_Typography.Body2, { color: "secondary" }, /*#__PURE__*/_react.default.createElement("span", { className: "lc-term" }, "Linking:"), " Mention a task in a branch, pull request, or commit and they will be connected automatically.")), /*#__PURE__*/_react.default.createElement("div", { className: "lc-lead" }, /*#__PURE__*/_react.default.createElement(_Icons.CommandLineIcon, null), /*#__PURE__*/_react.default.createElement(_Typography.Body2, { color: "secondary" }, /*#__PURE__*/_react.default.createElement("span", { className: "lc-term" }, "Commands:"), " Add short instructions in a commit or pull request description to act on the task: log time, close it, or leave a comment."))), /*#__PURE__*/_react.default.createElement(_ChooseV.ChooseV2, { className: "lc-choosectl", options: TAB_OPTIONS, selected: [tab], onChange: function onChange(ids) { return setTab(ids[0]); } }), tab === "linking" ? /*#__PURE__*/_react.default.createElement(LinkingTab, { branchLoading: branchLoading, branch: suggestedBranch }) : /*#__PURE__*/_react.default.createElement(CommandsTab, null)))))); }; LinkingCommandsButton.displayName = "LinkingCommandsButton"; //# sourceMappingURL=LinkingCommandsButton.js.map