mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
Fleet UI: Update the read-only SQL editor to appear non-interactive (#37764)
This commit is contained in:
parent
e171c02a03
commit
360a426224
5 changed files with 119 additions and 9 deletions
1
changes/34124-sql-editor-disabled
Normal file
1
changes/34124-sql-editor-disabled
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Fleet UI: Adjust the read-only SQL editor to appear non-interactive
|
||||
|
|
@ -19,6 +19,10 @@ import {
|
|||
sqlKeyWords,
|
||||
} from "utilities/sql_tools";
|
||||
|
||||
import { stringToClipboard } from "utilities/copy_text";
|
||||
import Button from "components/buttons/Button";
|
||||
import Icon from "components/Icon";
|
||||
|
||||
import "./mode";
|
||||
import "./theme";
|
||||
|
||||
|
|
@ -45,6 +49,7 @@ export interface ISQLEditorProps {
|
|||
onChange?: (value: string) => void;
|
||||
handleSubmit?: () => void;
|
||||
disabled?: boolean;
|
||||
enableCopy?: boolean;
|
||||
}
|
||||
|
||||
const baseClass = "sql-editor";
|
||||
|
|
@ -71,13 +76,29 @@ const SQLEditor = ({
|
|||
onChange,
|
||||
handleSubmit = noop,
|
||||
disabled = false,
|
||||
/** Combine with readOnly to remove ability to select text */
|
||||
enableCopy = false,
|
||||
}: ISQLEditorProps): JSX.Element => {
|
||||
const editorRef = useRef<ReactAce>(null);
|
||||
const [copied, setCopied] = React.useState(false);
|
||||
|
||||
/** Keeps label actions clickable and removes all mouse/keyboard access/hover states of editor */
|
||||
const isReadonlyCopy = _readOnly && enableCopy && !disabled;
|
||||
|
||||
const wrapperClass = classnames(className, wrapperClassName, baseClass, {
|
||||
[`${baseClass}__wrapper--error`]: !!error,
|
||||
[`${baseClass}__wrapper--disabled`]: disabled,
|
||||
// This is for read only that has a copy button so we disallow selecting the text
|
||||
[`${baseClass}__wrapper--readonly-copy`]: !!isReadonlyCopy,
|
||||
});
|
||||
|
||||
const onClickCopy = () => {
|
||||
stringToClipboard(value || "").then(() => {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
});
|
||||
};
|
||||
|
||||
const fixHotkeys = (editor: IAceEditor) => {
|
||||
editor.commands.removeCommand("gotoline");
|
||||
editor.commands.removeCommand("find");
|
||||
|
|
@ -219,6 +240,25 @@ const SQLEditor = ({
|
|||
readOnly: true,
|
||||
});
|
||||
|
||||
if (isReadonlyCopy) {
|
||||
// keep Ace read-only and remove any selection
|
||||
editor.setOption("readOnly", true);
|
||||
editor.selection.on("changeSelection", () => {
|
||||
editor.clearSelection();
|
||||
});
|
||||
|
||||
// make the internal textarea unfocusable via keyboard
|
||||
const textarea = editor.textInput?.getElement?.();
|
||||
if (textarea) {
|
||||
textarea.setAttribute("tabindex", "-1");
|
||||
}
|
||||
|
||||
// if something still manages to focus it, immediately blur
|
||||
editor.on("focus", () => {
|
||||
editor.blur();
|
||||
});
|
||||
}
|
||||
|
||||
onLoad && onLoad(editor);
|
||||
};
|
||||
|
||||
|
|
@ -237,23 +277,44 @@ const SQLEditor = ({
|
|||
};
|
||||
|
||||
const renderLabel = useCallback(() => {
|
||||
const labelText = error || label;
|
||||
const labelClassName = classnames(`${baseClass}__label`, {
|
||||
[`${baseClass}__label--error`]: !!error,
|
||||
[`${baseClass}__label--with-action`]: !!labelActionComponent,
|
||||
});
|
||||
|
||||
if (!label) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const labelText = error || label;
|
||||
|
||||
const labelClassName = classnames(`${baseClass}__label`, {
|
||||
[`${baseClass}__label--error`]: !!error,
|
||||
[`${baseClass}__label--with-action`]:
|
||||
!!labelActionComponent || enableCopy,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={labelClassName}>
|
||||
{labelText}
|
||||
{labelActionComponent}
|
||||
<span className={`${baseClass}__label-text`}>{labelText}</span>
|
||||
<div className={`${baseClass}__label-actions`}>
|
||||
{labelActionComponent}
|
||||
{enableCopy && (
|
||||
<div className={`${baseClass}__copy-wrapper`}>
|
||||
{copied && (
|
||||
<span className={`${baseClass}__copied-confirmation`}>
|
||||
Copied!
|
||||
</span>
|
||||
)}
|
||||
<Button
|
||||
variant="text-icon"
|
||||
onClick={onClickCopy}
|
||||
size="small"
|
||||
iconStroke
|
||||
>
|
||||
Copy <Icon name="copy" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}, [error, label, labelActionComponent]);
|
||||
}, [error, label, labelActionComponent, enableCopy, copied]);
|
||||
|
||||
const renderHelpText = () => {
|
||||
if (helpText) {
|
||||
|
|
|
|||
|
|
@ -7,11 +7,34 @@
|
|||
&--error {
|
||||
color: $core-vibrant-red;
|
||||
}
|
||||
|
||||
&--with-action {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.sql-editor__label-text {
|
||||
flex: 1 1 auto;
|
||||
margin-right: $pad-small;
|
||||
}
|
||||
|
||||
.sql-editor__label-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $pad-xsmall;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.sql-editor__copy-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $pad-xxsmall;
|
||||
}
|
||||
|
||||
.sql-editor__copied-confirmation {
|
||||
@include copy-message;
|
||||
}
|
||||
|
||||
button {
|
||||
height: initial; // aligning space between label and textarea
|
||||
margin: -$pad-small 0;
|
||||
|
|
@ -33,6 +56,29 @@
|
|||
&--disabled {
|
||||
@include disabled;
|
||||
}
|
||||
|
||||
&--readonly-copy {
|
||||
cursor: default;
|
||||
|
||||
.ace-fleet:hover {
|
||||
border: 1px solid $ui-blue-gray;
|
||||
}
|
||||
|
||||
// remove interaction on both code + gutter
|
||||
.ace_scroller,
|
||||
.ace_gutter {
|
||||
pointer-events: auto;
|
||||
cursor: not-allowed;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
// keep label actions (copy, etc.) usable
|
||||
.sql-editor__label-actions,
|
||||
.sql-editor__label-actions * {
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ace_scroller {
|
||||
|
|
|
|||
|
|
@ -140,6 +140,7 @@ const DynamicLabelForm = ({
|
|||
onLoad={onLoad}
|
||||
wrapperClassName={`${baseClass}__text-editor-wrapper form-field`}
|
||||
wrapEnabled
|
||||
enableCopy={isEditing}
|
||||
/>
|
||||
<PlatformField
|
||||
platform={platform}
|
||||
|
|
|
|||
|
|
@ -207,6 +207,7 @@ $max-width: 2560px;
|
|||
border: solid 1px #e2e4ea;
|
||||
border-radius: 10px;
|
||||
padding: 2px 6px;
|
||||
margin: -4px 0;
|
||||
}
|
||||
|
||||
@mixin color-contrasted-sections {
|
||||
|
|
|
|||
Loading…
Reference in a new issue