Skip to content

Commit

Permalink
Show match context (#436)
Browse files Browse the repository at this point in the history
  • Loading branch information
mickol34 authored Jan 23, 2025
1 parent 945dce5 commit 24dd72e
Show file tree
Hide file tree
Showing 5 changed files with 310 additions and 2 deletions.
2 changes: 2 additions & 0 deletions src/mqueryfront/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
"react": "^18.3.1",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18.3.1",
"react-draggable": "^4.4.6",
"react-html-parser": "^2.0.2",
"react-router-dom": "^6.26.2",
"react-select": "^5.8.1",
"replace-js-pagination": "^1.0.5",
Expand Down
26 changes: 26 additions & 0 deletions src/mqueryfront/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,29 @@
.cursor-pointer {
cursor: pointer;
}

.modal-container {
position: absolute;
offset-distance: 10px;
z-index: auto;
right: 5vw;
}

.modal-block {
position: relative;
block-size: "fit-content";
right: 5vw;
}

.modal-dialog {
margin: 0;
}

.modal-header:hover {
cursor: grab;
}

.modal-table {
overflow-y: scroll;
max-height: 50vh;
}
198 changes: 198 additions & 0 deletions src/mqueryfront/src/components/ActionShowMatchContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import React, { useState, useRef, useEffect } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faLightbulb } from "@fortawesome/free-solid-svg-icons";
import Draggable from "react-draggable";
import ReactHtmlParser from "react-html-parser";

const useClickOutsideModal = (ref, callback) => {
const handleClick = (event) => {
if (ref.current && !ref.current.contains(event.target)) {
// lose focus (higher z-index) only if other modal was clicked
const modals = document.querySelectorAll(".modal");
const wasClicked = (modal) => modal.contains(event.target);
if (Array.from(modals).some(wasClicked)) {
callback();
}
}
};

useEffect(() => {
document.addEventListener("click", handleClick);

return () => {
document.removeEventListener("click", handleClick);
};
});
};

function base64ToHex(str64) {
return (
atob(str64)
.split("")
.map(function (aChar) {
return ("0" + aChar.charCodeAt(0).toString(16)).slice(-2);
})
.join(" ")
.toUpperCase() + " "
);
}

function base64ToSanitizedString(str64) {
return atob(str64)
.split("")
.map(function (aChar) {
if (32 <= aChar.charCodeAt(0) && aChar.charCodeAt(0) < 127) {
return aChar;
}
return ".";
})
.join("");
}

function insertEveryNChars(str, insert, n) {
return str
.split(new RegExp(`(.{${n}})`))
.filter((x) => x)
.join(insert);
}

function cellHTML(foundSample, lineLength, transformFunc) {
const hexBefore = transformFunc(foundSample["before"]);
const hexMatching = transformFunc(foundSample["matching"]);
const hexAfter = transformFunc(foundSample["after"]);
const basicStr = hexBefore + hexMatching + hexAfter;
const strWithBreakLines = insertEveryNChars(basicStr, "<br>", lineLength);
const breaksInBefore = Math.floor(hexBefore.length / lineLength);
const breaksInBeforeAndMatching = Math.floor(
(hexBefore.length + hexMatching.length) / lineLength
);
const BoldOpeningTagIndex = hexBefore.length + 4 * breaksInBefore;
const BoldClosingTagIndex =
hexBefore.length + hexMatching.length + 4 * breaksInBeforeAndMatching;
let boldedStr =
strWithBreakLines.slice(0, BoldClosingTagIndex) +
"</b>" +
strWithBreakLines.slice(BoldClosingTagIndex);
boldedStr =
boldedStr.slice(0, BoldOpeningTagIndex) +
"<b>" +
boldedStr.slice(BoldOpeningTagIndex);
return boldedStr;
}

const ActionShowMatchContext = (props) => {
const ref = useRef(null);
const [showModal, setShowModal] = useState(false);
const [focus, setFocus] = useState(true);
useClickOutsideModal(ref, () => setFocus(false));

const modalHeader = (
<div className="modal-header d-flex justify-content-between">
<h6 className="modal-title">{`Match context for ${props.filename}`}</h6>
<button
type="button"
className="btn-close"
onClick={() => setShowModal(false)}
/>
</div>
);

const tableRows = Object.keys(props.context).map((rulename, index) => {
const rulenameRows = Object.keys(props.context[rulename]).map(
(identifier) => {
const foundSample = props.context[rulename][identifier];
return (
<>
<td scope="row">
<span className="badge rounded-pill bg-info ms-1 mt-1">
{identifier}
</span>
</td>
<td scope="row" className="text-monospace">
{ReactHtmlParser(
cellHTML(foundSample, 24, base64ToHex)
)}
</td>
<td scope="row" className="text-monospace">
{ReactHtmlParser(
cellHTML(
foundSample,
8,
base64ToSanitizedString
)
)}
</td>
</>
);
}
);

return (
<>
<tr key={index}>
<td
scope="row fit-content"
rowSpan={Object.keys(props.context[rulename]).length}
>
<span className="badge rounded-pill bg-primary ms-1 mt-1">
{rulename}
</span>
</td>
{rulenameRows[0]}
</tr>
{rulenameRows.slice(1).map((row) => (
<tr>{row}</tr>
))}
</>
);
});

const modalBody = (
<div className="modal-body modal-table">
{!Object.keys(props.context).length ? (
"No context available"
) : (
<table className="table table-bordered">
<tbody>{tableRows}</tbody>
</table>
)}
</div>
);

return (
<div className="d-flex flex-row">
<button
title="Show match context"
className="text-secondary"
style={{ border: 0, background: 0 }}
onClick={() => setShowModal(!showModal)}
>
<FontAwesomeIcon icon={faLightbulb} size="sm" />
</button>
{showModal && (
<Draggable handle=".modal-header">
<div
className="modal-container"
style={{ zIndex: focus ? 100 : 10 }}
ref={ref}
onClick={() => setFocus(true)}
>
<div
className="modal modal-block"
style={{ display: showModal ? "block" : "none" }}
>
<div className="modal-dialog modal-xl">
<div className="modal-content">
{modalHeader}
{modalBody}
</div>
</div>
</div>
</div>
</Draggable>
)}
</div>
);
};

export default ActionShowMatchContext;
9 changes: 8 additions & 1 deletion src/mqueryfront/src/query/QueryMatchesItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import React from "react";
import path from "path-browserify";
import ActionDownload from "../components/ActionDownload";
import ActionCopyToClipboard from "../components/ActionCopyToClipboard";
import ActionShowMatchContext from "../components/ActionShowMatchContext";

const QueryMatchesItem = (props) => {
const { match, download_url } = props;
const { matches, meta, file } = match;
const { matches, meta, file, context } = match;

const fileBasename = path.basename(file);

Expand Down Expand Up @@ -68,6 +69,12 @@ const QueryMatchesItem = (props) => {
tooltipMessage="Copy file name to clipboard"
/>
</small>
<small className="text-secondary ms-2 me-1 mt-1">
<ActionShowMatchContext
filename={fileBasename}
context={context}
/>
</small>
{matchBadges}
{metadataBadges}
</div>
Expand Down
77 changes: 76 additions & 1 deletion src/mqueryfront/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,11 @@ classnames@^2.2.5:
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==

clsx@^1.1.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==

color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
Expand Down Expand Up @@ -1233,6 +1238,39 @@ dom-helpers@^5.0.1:
"@babel/runtime" "^7.8.7"
csstype "^3.0.2"

dom-serializer@0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==
dependencies:
domelementtype "^2.0.1"
entities "^2.0.0"

domelementtype@1, domelementtype@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==

domelementtype@^2.0.1:
version "2.3.0"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==

domhandler@^2.3.0:
version "2.4.2"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==
dependencies:
domelementtype "1"

domutils@^1.5.1:
version "1.7.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==
dependencies:
dom-serializer "0"
domelementtype "1"

dot-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
Expand Down Expand Up @@ -1261,6 +1299,16 @@ encodeurl@~2.0.0:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==

entities@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==

entities@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==

entities@^4.4.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
Expand Down Expand Up @@ -1567,6 +1615,18 @@ html-entities@^2.4.0:
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.5.2.tgz#201a3cf95d3a15be7099521620d19dfb4f65359f"
integrity sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==

htmlparser2@^3.9.0:
version "3.10.1"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"
integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==
dependencies:
domelementtype "^1.3.1"
domhandler "^2.3.0"
domutils "^1.5.1"
entities "^1.1.1"
inherits "^2.0.1"
readable-stream "^3.1.1"

http-deceiver@^1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
Expand Down Expand Up @@ -2119,6 +2179,21 @@ react-dom@^18.3.1:
loose-envify "^1.1.0"
scheduler "^0.23.2"

react-draggable@^4.4.6:
version "4.4.6"
resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.4.6.tgz#63343ee945770881ca1256a5b6fa5c9f5983fe1e"
integrity sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==
dependencies:
clsx "^1.1.1"
prop-types "^15.8.1"

react-html-parser@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/react-html-parser/-/react-html-parser-2.0.2.tgz#6dbe1ddd2cebc1b34ca15215158021db5fc5685e"
integrity sha512-XeerLwCVjTs3njZcgCOeDUqLgNIt/t+6Jgi5/qPsO/krUWl76kWKXMeVs2LhY2gwM6X378DkhLjur0zUQdpz0g==
dependencies:
htmlparser2 "^3.9.0"

react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
Expand Down Expand Up @@ -2189,7 +2264,7 @@ readable-stream@^2.0.1:
string_decoder "~1.1.1"
util-deprecate "~1.0.1"

readable-stream@^3.0.6:
readable-stream@^3.0.6, readable-stream@^3.1.1:
version "3.6.2"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
Expand Down

0 comments on commit 24dd72e

Please sign in to comment.