This commit is contained in:
Quan HL
2023-09-25 17:11:25 +07:00
parent 82439cca83
commit 93397e0c70
9 changed files with 171 additions and 62 deletions

27
.gitignore vendored Normal file
View File

@@ -0,0 +1,27 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
/dist
/lib
/components
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

1
.prettierrc.json Normal file
View File

@@ -0,0 +1 @@
{}

7
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"ms-vscode.vscode-typescript-next"
]
}

9
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,9 @@
{
"editor.tabSize": 2,
"editor.detectIndentation": false,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"prettier.configPath": ".prettierrc.json",
"files.trimTrailingWhitespace": true,
"typescript.preferences.quoteStyle": "double"
}

View File

@@ -11,10 +11,8 @@ export interface Call {
export enum MessageEvent {
// Request
Login,
Call,
OpenPhoneWindow,
Ping,
}
export interface EmptyData {}

View File

@@ -1,66 +1,105 @@
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import {
ChakraProvider,
Text,
IconButton,
Flex,
CSSReset,
} from "@chakra-ui/react";
import { Phone } from "react-feather";
import { Call, CallAction, Message, MessageEvent } from "./../common/types";
import { openPhonePopup } from "./../utils";
export const ContentApp = () => {
const [isOpen, setIsOpen] = useState(false);
const [position, setPosition] = useState({ left: 0, top: 0 });
const [selectedText, setSelectedText] = useState("");
const [counting, setCounting] = useState(0);
const [position, setPosition] = useState({ x: 0, y: 0 });
// Add event listener for text selection
useEffect(() => {
function handleMouseUp() {
const selectedText = window.getSelection()?.toString();
if (selectedText) {
setSelectedText(selectedText);
const range = window.getSelection()?.getRangeAt(0);
const rect = range?.getBoundingClientRect();
setIsOpen(true);
setPosition({ left: rect?.left || 0, top: rect?.bottom || 0 });
function mouseUpListener(e: any) {
const text = window.getSelection()?.toString().trim();
if (text && text.length > 0) {
const number = validateAndFormatNumber(text);
setSelectedText(number || "");
setPosition({ x: e.pageX, y: e.pageY });
} else {
setIsOpen(false);
setSelectedText("");
}
}
document.addEventListener("mouseup", handleMouseUp);
document.addEventListener("mouseup", mouseUpListener);
return () => {
document.removeEventListener("mouseup", handleMouseUp);
document.removeEventListener("mouseup", mouseUpListener);
};
}, []);
const validateAndFormatNumber = (input: string): string | null => {
const strippedInput = input.replace(/\s+|\.|-|\+|\(|\)/g, ""); // remove all spaces
// check if the final string contains only numbers
if (/^\d+$/.test(strippedInput)) {
return strippedInput;
} else {
return null;
}
};
const onClick = () => {
setSelectedText("");
const msg: Message<Call> = {
event: MessageEvent.Call,
data: {
action: CallAction.OUTBOUND,
number: selectedText || "",
},
};
chrome.runtime.sendMessage(msg);
};
if (!selectedText) {
return null;
}
return (
<ChakraProvider>
<CSSReset />
{isOpen && (
<Flex
position="fixed"
left={position.left}
top={position.top}
bg="white"
p={4}
border="1px solid"
borderColor="gray.200"
boxShadow="md"
alignItems="center"
<div
style={{
position: "absolute",
left: `${position.x}px`,
top: `${position.y}px`,
backgroundColor: "white",
height: "80px",
width: "180px",
borderRadius: "10px",
boxShadow: "0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23)",
padding: "10px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
zIndex: "99999999",
fontFamily: "Arial, sans-serif",
}}
>
<div style={{ display: "flex", flexDirection: "column" }}>
<h1
style={{
fontSize: "18px",
color: "#333",
display: "inline",
fontWeight: "bold",
}}
>
<Text>{selectedText}</Text>
<IconButton
colorScheme="blue"
ml={2}
aria-label="text"
icon={<Phone />}
/>
</Flex>
)}
</ChakraProvider>
Number:{" "}
</h1>
<span style={{ fontSize: "16px", color: "#666" }}>{selectedText}</span>
</div>
<button
style={{
padding: "10px",
color: "white",
background: "#007BFF",
border: "none",
borderRadius: "5px",
cursor: "pointer",
}}
onClick={onClick}
>
Call
</button>
</div>
);
};

View File

@@ -1,4 +1,4 @@
import { SipConstants } from "src/lib";
import { SipConstants } from "./../lib";
import { deleteWindowIdKey, getWindowIdKey, saveWindowIdKey } from "./storage";
import { PhoneNumberFormat, PhoneNumberUtil } from "google-libphonenumber";
@@ -13,20 +13,23 @@ export const formatPhoneNumber = (number: string) => {
};
export const openPhonePopup = () => {
const runningPhoneWindowId = getWindowIdKey();
if (runningPhoneWindowId) {
chrome.windows.update(runningPhoneWindowId, { focused: true }, () => {
if (chrome.runtime.lastError) {
deleteWindowIdKey();
initiateNewPhonePopup();
}
});
} else {
initiateNewPhonePopup();
}
return new Promise((resolve) => {
const runningPhoneWindowId = getWindowIdKey();
if (runningPhoneWindowId) {
chrome.windows.update(runningPhoneWindowId, { focused: true }, () => {
if (chrome.runtime.lastError) {
deleteWindowIdKey();
initiateNewPhonePopup(resolve);
}
resolve(1);
});
} else {
initiateNewPhonePopup(resolve);
}
});
};
const initiateNewPhonePopup = () => {
const initiateNewPhonePopup = (callback: (v: unknown) => void) => {
const cfg: chrome.windows.CreateData = {
url: chrome.runtime.getURL("window/index.html"),
width: 300,
@@ -36,6 +39,7 @@ const initiateNewPhonePopup = () => {
state: "normal",
};
chrome.windows.create(cfg, (w) => {
callback(1);
if (w && w.id) saveWindowIdKey(w.id);
});
};

View File

@@ -15,7 +15,7 @@ import {
import { useEffect, useRef, useState } from "react";
import { Delete } from "react-feather";
import { DEFAULT_COLOR_SCHEME } from "src/common/constants";
import { SipClientStatus } from "src/common/types";
import { Call, Message, MessageEvent, SipClientStatus } from "src/common/types";
import { SipConstants, SipUA } from "src/lib";
import IncommingCall from "./incomming-call";
import OutgoingCall from "./outgoing-call";
@@ -24,6 +24,7 @@ import {
isSipClientAnswered,
isSipClientIdle,
isSipClientRinging,
openPhonePopup,
} from "src/utils";
type PhoneProbs = {
@@ -56,6 +57,28 @@ export const Phone = ({
}
}, [sipDomain, sipUsername, sipPassword, sipServerAddress, sipDisplayName]);
useEffect(() => {
chrome.runtime.onMessage.addListener(function (request) {
const msg = request as Message<any>;
switch (msg.event) {
case MessageEvent.Call:
handleCallEvent(msg.data as Call);
break;
default:
break;
}
});
}, []);
const handleCallEvent = (call: Call) => {
if (!call.number) return;
if (isSipClientIdle(callStatus)) {
setInputNumber(call.number);
sipUA.current?.call(call.number);
}
};
const createSipClient = (forceOfflineMode = false) => {
if (goOffline && !forceOfflineMode) {
return;

View File

@@ -6,6 +6,7 @@ module.exports = [
{
entry: "./src/content/index.tsx",
target: "web",
mode: "production",
output: {
path: path.join(__dirname, "dist"),
filename: "js/content.js",
@@ -27,7 +28,7 @@ module.exports = [
],
},
resolve: {
extensions: [".ts", ".js"],
extensions: [".ts", ".tsx",".js"],
},
},
{