Skip to content
This repository has been archived by the owner on Sep 26, 2024. It is now read-only.

Commit

Permalink
Hookup reactor (#150)
Browse files Browse the repository at this point in the history
* hookup-reactor

* add reactor alias for vite

* inject @repo/reactor to mocksi-lite-next

* Update reactor to ESM

* remove alias

* cleanup

* formatting

---------

Co-authored-by: Kayla Fitzsimmons <[email protected]>
Co-authored-by: Jonathan Kaplan <[email protected]>
  • Loading branch information
3 people authored Aug 21, 2024
1 parent 993e588 commit 89061ce
Show file tree
Hide file tree
Showing 16 changed files with 469 additions and 254 deletions.
9 changes: 8 additions & 1 deletion apps/mocksi-lite-next/package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
{
"dependencies": {
"@repo/reactor": "workspace:*",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"uuid": "^9.0.1",
"webextension-polyfill": "^0.11.0"
},
"dependenciesMeta": {
"@repo/reactor": {
"injected": true
}
},
"description": "A simple chrome extension template with Vite, React, TypeScript and Tailwind CSS.",
"devDependencies": {
"@crxjs/vite-plugin": "^2.0.0-beta.23",
Expand Down Expand Up @@ -45,4 +52,4 @@
},
"type": "module",
"version": "1.2.0"
}
}
22 changes: 19 additions & 3 deletions apps/mocksi-lite-next/src/pages/background/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
console.log("background script loaded");
console.debug("background script loaded");

chrome.runtime.onMessage.addListener(
(request, _sender, sendResponse): boolean => {
console.log("Received message:", request);
console.debug("Received message:", request);

sendResponse({ message: request.message, status: "ok" });
return true;
Expand All @@ -11,7 +11,23 @@ chrome.runtime.onMessage.addListener(

chrome.runtime.onMessageExternal.addListener(
(request, _sender, sendResponse): boolean => {
console.log("Received message from external:", request);
console.debug("Received message from external:", request);
if (request.message === "EDITING") {
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
if (tabs[0].id) {
chrome.tabs.sendMessage(tabs[0].id, { message: "EDITING" });
}
});
}

if (request.message === "STOP_EDITING") {
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
if (tabs[0].id) {
chrome.tabs.sendMessage(tabs[0].id, { message: "STOP_EDITING" });
}
});
}

if (
request.message === "sm-top" ||
request.message === "lg-bottom" ||
Expand Down
132 changes: 132 additions & 0 deletions apps/mocksi-lite-next/src/pages/content/highlighter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { v4 as uuidv4 } from "uuid";

const MOCKSI_HIGHLIGHTER_ID = "mocksi-highlighter";

class Highlighter {
private contentRanger = document.createRange();
private highlightedNodes: { highlightedElem: Node; highlightId: string }[] =
[];

highlightNode = (elementToHighlight: Node) => {
this.contentRanger.selectNodeContents(elementToHighlight);
const { height, width, x, y } =
this.contentRanger.getBoundingClientRect() || {};
const textHighlight = highlight({
height,
highlightedElement: elementToHighlight,
width,
x,
y,
});
textHighlight.id = uuidv4();
document.body.appendChild(textHighlight);
//@ts-ignore just don't know what is meaning here
this.highlightedNodes.push({
highlightId: textHighlight.id,
highlightedElem: elementToHighlight,
});
};

removeHighlightNode = (elementToUnhighlight: Node) => {
const { highlightId } =
this.highlightedNodes.find(
({ highlightedElem }) => highlightedElem === elementToUnhighlight,
) || {};
if (highlightId) {
const highlightDOMElem = document.getElementById(highlightId);
highlightDOMElem?.remove();
}
};

showHideHighlight = (show: boolean, elementInvolved: Node) => {
const { highlightId } =
this.highlightedNodes.find(
({ highlightedElem }) => highlightedElem === elementInvolved,
) || {};
if (highlightId) {
const highlightDOMElem = document.getElementById(highlightId);
(highlightDOMElem as HTMLElement).style.display = show ? "block" : "none";
}
};

showHideHighlights = (show: boolean) => {
for (const node of document.querySelectorAll(
`div.${MOCKSI_HIGHLIGHTER_ID}`,
)) {
(node as HTMLElement).style.display = show ? "block" : "none";
}
};

removeHighlightNodes = () => {
for (const node of document.querySelectorAll(
`div.${MOCKSI_HIGHLIGHTER_ID}`,
)) {
(node as HTMLElement).remove();
}
};
}

let ContentHighlighter: Highlighter;

export const getHighlighter = () => {
if (!ContentHighlighter) {
ContentHighlighter = new Highlighter();
}
return ContentHighlighter;
};

const createHighlighterStyles = (
width: number,
height: number,
x: number,
y: number,
scrollY: number,
scrollX: number,
) => ({
background: "rgba(229, 111, 12, 0.05)",
border: "2px solid #FFB68B",
cursor: "text",
height: `${height}px`,
left: `${window.scrollX + x + -2}px`,
pointerEvents: "none",
position: "absolute",
top: `${window.scrollY + y + -2}px`,
width: `${width}px`,
zIndex: "999",
});

const highlight = ({
height,
highlightedElement,
width,
x,
y,
}: {
height: number;
highlightedElement: Node;
width: number;
x: number;
y: number;
}) => {
const highlighterStyles = createHighlighterStyles(
width,
height,
x,
y,
window.scrollY,
window.scrollX,
);
const highlightDiv = document.createElement("div");
highlightDiv.className = MOCKSI_HIGHLIGHTER_ID;

highlightDiv.ondblclick = (event: MouseEvent) => {
if (!highlightedElement?.parentElement) {
return;
}
(event.target as HTMLElement).style.display = "none";
// TODO: Come back to handle double clicking on a highlight
document.getElementById("mocksiTextArea")?.focus();
event.stopPropagation();
};
return highlightDiv;
};
85 changes: 55 additions & 30 deletions apps/mocksi-lite-next/src/pages/content/mocksi-extension.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import React, { useEffect } from "react";
import type { ModificationRequest } from "@repo/reactor";
import { Reactor } from "@repo/reactor";
import React from "react";
import ReactDOM from "react-dom";
import { createRoot } from "react-dom/client";
import { getHighlighter } from "./highlighter";

const div = document.createElement("div");
div.id = "__root";
Expand All @@ -10,52 +13,74 @@ document.body.appendChild(div);
const rootContainer = document.querySelector("#__root");
if (!rootContainer) throw new Error("Can't find Content root element");
const root = createRoot(rootContainer);
const reactor = new Reactor();
const highlighter = getHighlighter();

const Iframe = () => {
const iframeRef = React.useRef<HTMLIFrameElement>(null);
const [iframeSize, setIframeSize] = React.useState("lg-bottom");

// Switch out differently styled Iframes based on message from nest/extension,
// swapping out the iframes themselves makes the size change
// immediate instead of keeping the iframe and updating the styles
// which only happens on the next render
React.useEffect(() => {
window.document.body.addEventListener("click", async (event) => {
const oldValue = "Engineering";
const newValue = "Cats";
const modification: ModificationRequest = {
description: `Change ${oldValue} to ${newValue}`,
modifications: [
{
action: "replaceAll",
content: `/${oldValue}/${newValue}/`,
selector: "body",
},
],
};
console.log(modification);
const modifications = await reactor.pushModification(modification);
for (const modification of modifications) {
modification.setHighlight(true);
}
});

chrome.runtime.onMessage.addListener(
(request, _sender, sendResponse): boolean => {
console.log("Received message in content script:", request);

setIframeSize(request.message);

if (iframeRef.current) {
if (request.message === "xs-bottom") {
iframeRef.current.style.height = "100px";
iframeRef.current.style.width = "100px";
iframeRef.current.style.bottom = "10px";
iframeRef.current.style.top = "auto";
} else if (request.message === "sm-top") {
iframeRef.current.style.height = "150px";
iframeRef.current.style.width = "400px";
iframeRef.current.style.top = "0px";
iframeRef.current.style.bottom = "auto";
} else if (request.message === "lg-bottom") {
iframeRef.current.style.height = "600px";
iframeRef.current.style.width = "500px";
iframeRef.current.style.bottom = "10px";
iframeRef.current.style.top = "auto";
if (request.message === "EDITING") {
console.log("Time to attach reactor");
reactor.attach(document, highlighter);
} else if (request.message === "STOP_EDITING") {
console.log("Time to detach reactor");
reactor.detach();
} else {
// UI SIZING
if (iframeRef.current) {
if (request.message === "xs-bottom") {
iframeRef.current.style.height = "100px";
iframeRef.current.style.width = "100px";
iframeRef.current.style.bottom = "10px";
iframeRef.current.style.top = "auto";
} else if (request.message === "sm-top") {
iframeRef.current.style.height = "150px";
iframeRef.current.style.width = "400px";
iframeRef.current.style.top = "0px";
iframeRef.current.style.bottom = "auto";
} else if (request.message === "lg-bottom") {
iframeRef.current.style.height = "600px";
iframeRef.current.style.width = "500px";
iframeRef.current.style.bottom = "10px";
iframeRef.current.style.top = "auto";
}
}
}

sendResponse({ message: request.message, status: "ok" });
return true;
},
);
}, []);

return (
<div>
<>
{ReactDOM.createPortal(
<>
<iframe
allowTransparency={true}
ref={iframeRef}
seamless={true}
src="http://localhost:3030/extension"
Expand All @@ -68,13 +93,13 @@ const Iframe = () => {
boxShadow: "none",
zIndex: 99998,
border: "none",
backgroundColor: "transparent",
backgroundColor: "transparent!important",
}}
/>
</>,
document.body,
)}
</div>
</>
);
};

Expand Down
51 changes: 34 additions & 17 deletions apps/mocksi-lite-next/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,27 +1,44 @@
{
"compilerOptions": {
"target": "esnext",
"types": ["vite/client", "node", "chrome"],
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"baseUrl": ".",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"jsx": "react-jsx",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"noEmit": true,
"jsx": "react-jsx",
"baseUrl": ".",
"noFallthroughCasesInSwitch": true,
"paths": {
"@src/*": ["src/*"],
"@assets/*": ["src/assets/*"],
"@pages/*": ["src/pages/*"]
}
"@assets/*": [
"src/assets/*"
],
"@pages/*": [
"src/pages/*"
],
"@src/*": [
"src/*"
]
},
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "esnext",
"types": [
"vite/client",
"node",
"chrome"
]
},
"include": ["src",
"utils", "vite.config.ts"],
}
"include": [
"src",
"utils",
"vite.config.ts"
],
}
Loading

0 comments on commit 89061ce

Please sign in to comment.