update to use fontawsome

This commit is contained in:
Quan HL
2024-05-07 12:48:56 +07:00
parent fdc5869390
commit f5703e1422
18 changed files with 2513 additions and 403 deletions

2575
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,9 +6,9 @@
"@chakra-ui/react": "^2.8.0",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@fortawesome/fontawesome-svg-core": "^6.5.2",
"@fortawesome/free-solid-svg-icons": "^6.5.2",
"@fortawesome/react-fontawesome": "^0.2.0",
"buffer": "^6.0.3",
"dayjs": "^1.11.10",
"framer-motion": "^10.16.4",
@@ -17,13 +17,13 @@
"jssip": "^3.10.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-feather": "^2.0.10",
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"uuid": "^9.0.1",
"web-vitals": "^2.1.4"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/chrome": "^0.0.242",
"@types/google-libphonenumber": "^7.4.27",
"@types/jest": "^27.5.2",
@@ -34,8 +34,10 @@
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.8.1",
"file-loader": "^6.2.0",
"react-scripts": "5.0.1",
"sass": "^1.64.1",
"sass-loader": "^13.3.2",
"serve": "^14.2.1",
"style-loader": "^3.3.3",
"ts-loader": "^9.4.4",
"webpack": "^5.88.2",
@@ -43,7 +45,8 @@
},
"scripts": {
"build": "webpack --config webpack.config.js",
"watch": "webpack -w --config webpack.config.js"
"watch": "webpack -w --config webpack.config.js",
"start": "react-scripts start"
},
"eslintConfig": {
"extends": [

View File

@@ -1,23 +1,8 @@
{
"version": "1.0.5",
"manifest_version": 3,
"name": "jambonz webrtc phone",
"description": "jambonz webrtc phone",
"action": {
"default_title": "jambonz webrtc phone"
},
"background": {
"service_worker": "background/index.js"
},
"permissions": ["storage"],
"host_permissions": ["https://*/*"],
"icons": {
"16": "icons/icon.png",
"48": "icons/icon.png",
"128": "icons/icon.png",
"192": "icons/icon192.png",
"384": "icons/icon384.png",
"512": "icons/icon512.png",
"1024": "icons/icon1024.png"
}
"short_name": "Jambonz WebPhone",
"name": "Jambonz WebPhone",
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -1,33 +0,0 @@
import React from "react";
import "./styles.scss";
import { Button, Flex, Text } from "@chakra-ui/react";
import { openPhonePopup } from "./utils";
export const App = () => {
const handleClick = () => {
openPhonePopup();
};
return (
<Flex
justifyContent="center"
flexFlow="column"
height="100%"
padding="20px"
alignItems="center"
>
<Text fontSize="24px" mb={5}>
Click 'Start' to activate the service.
</Text>
<Button
size="lg"
width="full"
onClick={handleClick}
colorScheme="jambonz"
>
Start
</Button>
</Flex>
);
};
export default App;

View File

@@ -1,12 +1,7 @@
import { useState } from "react";
import {
Input,
InputGroup,
InputRightElement,
Button,
Box,
} from "@chakra-ui/react";
import { Eye, EyeOff } from "react-feather";
import { Input, InputGroup, InputRightElement, Button } from "@chakra-ui/react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons";
type PasswordInputProbs = {
password: [string, React.Dispatch<React.SetStateAction<string>>];
@@ -34,7 +29,7 @@ function PasswordInput({
/>
<InputRightElement width="4.5rem">
<Button h="1.75rem" size="sm" onClick={handleClick} variant="unstyled">
{showPassword ? <EyeOff /> : <Eye />}
<FontAwesomeIcon icon={showPassword ? faEyeSlash : faEye} />
</Button>
</InputRightElement>
</InputGroup>

View File

@@ -1,9 +1,10 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { ChakraProvider } from "@chakra-ui/react";
import mainTheme from "./theme";
import WindowApp from "./window/app";
// This file is being used only for dev.
const root = document.createElement("div");
root.className = "container";
document.body.appendChild(root);
@@ -11,7 +12,7 @@ const rootDiv = ReactDOM.createRoot(root);
rootDiv.render(
<React.StrictMode>
<ChakraProvider theme={mainTheme}>
<App />
<WindowApp />
</ChakraProvider>
</React.StrictMode>
);

View File

@@ -8,27 +8,41 @@ export default class SipAudioElements {
#localHungup: HTMLAudioElement;
constructor() {
this.#ringing = new Audio(chrome.runtime.getURL("audios/ringing.mp3"));
this.#ringing = this.getAudio("audios/ringing.mp3");
this.#ringing.loop = true;
this.#ringing.volume = 0.8;
this.#ringBack = new Audio(chrome.runtime.getURL("audios/us-ringback.mp3"));
this.#ringBack = this.getAudio("audios/us-ringback.mp3");
this.#ringBack.loop = true;
this.#ringBack.volume = 0.8;
this.#failed = new Audio(chrome.runtime.getURL("audios/call-failed.mp3"));
this.#failed = this.getAudio("audios/call-failed.mp3");
this.#failed.volume = 0.3;
this.#busy = new Audio(chrome.runtime.getURL("audios/us-busy-signal.mp3"));
this.#busy = this.getAudio("audios/us-busy-signal.mp3");
this.#busy.volume = 0.3;
this.#hungup = new Audio(
chrome.runtime.getURL("audios/remote-party-hungup-tone.mp3")
);
this.#hungup = this.getAudio("audios/remote-party-hungup-tone.mp3");
this.#hungup.volume = 0.3;
this.#localHungup = new Audio(
chrome.runtime.getURL("audios/local-party-hungup-tone.mp3")
);
this.#localHungup = this.getAudio("audios/local-party-hungup-tone.mp3");
this.#localHungup.volume = 0.3;
this.#remote = new Audio();
}
private getAudio(path: string) {
let audioURL;
// Check if we're in a Chrome extension
if (
typeof chrome !== "undefined" &&
chrome.runtime &&
chrome.runtime.getURL
) {
audioURL = chrome.runtime.getURL(path);
} else {
// We're in a web context, adjust this path as necessary
audioURL = `/${path}`;
}
return new Audio(audioURL);
}
playLocalHungup(volume: number | undefined) {
this.pauseRingback();
this.pauseRinging();

View File

@@ -34,6 +34,7 @@ export default class SipUA extends events.EventEmitter {
display_name: client.name,
sockets: [new WebSocketInterface(settings.wsUri)],
register: settings.register,
register_expires: 600,
});
this.#ua.on("connecting", (data: UAConnectingEvent) =>
this.emit(SipConstants.UA_CONNECTING, { ...data, client })

View File

@@ -104,10 +104,11 @@ export const WindowApp = () => {
index={tabIndex}
>
<TabList mb="1em" gap={1}>
{tabsSettings.map((s) => (
{tabsSettings.map((s, i) => (
<Tab
_selected={{ color: "white", bg: "jambonz.500" }}
bg="grey.500"
key={i}
>
{s.title}
</Tab>
@@ -115,8 +116,8 @@ export const WindowApp = () => {
</TabList>
<TabPanels>
{tabsSettings.map((s) => (
<TabPanel>{s.content}</TabPanel>
{tabsSettings.map((s, i) => (
<TabPanel key={i}>{s.content}</TabPanel>
))}
</TabPanels>
</Tabs>

View File

@@ -1,21 +1,21 @@
import {
HStack,
Icon,
IconButton,
Spacer,
Text,
Tooltip,
VStack,
} from "@chakra-ui/react";
import {
faArrowRightFromBracket,
faArrowRightToBracket,
faPhone,
faSave,
faTrash,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import dayjs from "dayjs";
import { useState } from "react";
import {
Phone,
PhoneIncoming,
PhoneOutgoing,
Save,
Trash2,
} from "react-feather";
import { CallHistory, SipCallDirection } from "src/common/types";
import { getSettings, isSaveCallHistory } from "src/storage";
import { formatPhoneNumber } from "src/utils";
@@ -36,11 +36,11 @@ export const CallHistoryItem = ({
const [callEnable, setCallEnable] = useState(false);
const getDirectionIcon = (direction: SipCallDirection) => {
if (direction === "outgoing") {
return PhoneOutgoing;
return faArrowRightFromBracket;
} else if (direction === "incoming") {
return PhoneIncoming;
return faArrowRightToBracket;
} else {
return Phone;
return faPhone;
}
};
@@ -57,7 +57,7 @@ export const CallHistoryItem = ({
<Tooltip label="Call">
<IconButton
aria-label="call recents"
icon={<Phone />}
icon={<FontAwesomeIcon icon={faPhone} />}
onClick={() => {
if (onCallNumber) {
onCallNumber(call.number, call.name);
@@ -69,7 +69,11 @@ export const CallHistoryItem = ({
/>
</Tooltip>
) : (
<Icon as={getDirectionIcon(call.direction)} w="20px" h="20px" />
<FontAwesomeIcon
icon={getDirectionIcon(call.direction)}
width="20px"
height="20px"
/>
)}
<VStack align="start">
@@ -88,7 +92,11 @@ export const CallHistoryItem = ({
<Tooltip label={isSaved && call.isSaved ? "Delete" : "Save"}>
<IconButton
aria-label="save recents"
icon={isSaved && call.isSaved ? <Trash2 /> : <Save />}
icon={
<FontAwesomeIcon
icon={isSaved && call.isSaved ? faTrash : faSave}
/>
}
onClick={() => {
if (!isSaved && call.isSaved) {
return;

View File

@@ -13,11 +13,12 @@ import {
TabPanel,
} from "@chakra-ui/react";
import { useState } from "react";
import { Search, Sliders } from "react-feather";
import { CallHistory } from "src/common/types";
import { DEFAULT_COLOR_SCHEME } from "src/common/constants";
import Recents from "./recent";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSearch, faSliders } from "@fortawesome/free-solid-svg-icons";
type CallHistoriesProbs = {
calls: CallHistory[];
@@ -51,11 +52,11 @@ export const CallHistories = ({
fontWeight="normal"
/>
<InputLeftElement mr={2}>
<Icon as={Search} w="20px" h="20px" />
<FontAwesomeIcon icon={faSearch} width="20px" height="20px" />
</InputLeftElement>
</InputGroup>
<HStack spacing={2} bg="grey.100" p={2} borderRadius={7}>
<Icon as={Sliders} w="20px" h="20px" />
<FontAwesomeIcon icon={faSliders} width="20px" height="20px" />
<Text fontSize="12px" fontWeight="500">
Filter
</Text>

View File

@@ -51,8 +51,9 @@ export const Recents = ({
spacing={2}
mt={2}
>
{callHistories.map((c) => (
{callHistories.map((c, i) => (
<CallHistoryItem
key={i}
isSaved={isSaved}
call={c}
onCallNumber={onCallNumber}

View File

@@ -4,9 +4,23 @@ export default class DialPadAudioElements {
constructor() {
const arr = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "#"];
for (const i of arr) {
this.keySounds[i] = new Audio(
chrome.runtime.getURL(`audios/dtmf-${encodeURIComponent(i)}.mp3`)
);
let audioURL;
// Check if we're in a Chrome extension
if (
typeof chrome !== "undefined" &&
chrome.runtime &&
chrome.runtime.getURL
) {
audioURL = chrome.runtime.getURL(
`audios/dtmf-${encodeURIComponent(i)}.mp3`
);
} else {
// We're in a web context, adjust this path as necessary
audioURL = `/audios/dtmf-${encodeURIComponent(i)}.mp3`;
}
this.keySounds[i] = new Audio(audioURL);
const audio = this.keySounds[i];
if (audio) {
audio.volume = 0.5;

View File

@@ -53,10 +53,16 @@ export const DialPad = ({ handleDigitPress }: DialPadProbs) => {
}, []);
return (
<Box p={2} w="full" ref={selfRef}>
<VStack w="full" bg="grey.500" spacing={0.5}>
<Box p={2} w="full" h="280px" ref={selfRef}>
<VStack w="full" h="full" bg="grey.500" spacing={0.5}>
{buttons.map((row, rowIndex) => (
<HStack key={rowIndex} justifyContent="space-between" spacing={0.5}>
<HStack
key={rowIndex}
justifyContent="space-between"
spacing={0.5}
w="full"
h="full"
>
{row.map((num) => (
<Button
key={num}
@@ -66,8 +72,8 @@ export const DialPad = ({ handleDigitPress }: DialPadProbs) => {
}}
size="lg"
p={0}
width="124px"
height="70px"
width="calc(100% / 3)"
height="100%"
variant="unstyled"
bg="white"
_hover={{

View File

@@ -1,5 +1,6 @@
import { Button, VStack, Text, Icon, HStack, Spacer } from "@chakra-ui/react";
import { PhoneCall } from "react-feather";
import { faPhone } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { formatPhoneNumber } from "src/utils";
type IncommingCallProbs = {
@@ -15,7 +16,12 @@ export const IncommingCall = ({
}: IncommingCallProbs) => {
return (
<VStack alignItems="center" spacing={4} mt="130px" w="full">
<Icon as={PhoneCall} color="jambonz.500" w="60px" h="60px" />
<FontAwesomeIcon
icon={faPhone}
color="jambonz.500"
width="60px"
height="60px"
/>
<Text fontSize="15px">Incoming call from</Text>
<Text fontSize="24px" fontWeight="bold">
{formatPhoneNumber(number)}

View File

@@ -7,7 +7,6 @@ import {
IconButton,
Image,
Input,
Select,
Spacer,
Text,
Tooltip,
@@ -15,16 +14,6 @@ import {
useToast,
} from "@chakra-ui/react";
import { useEffect, useRef, useState } from "react";
import {
GitMerge,
List,
Mic,
MicOff,
Pause,
PhoneOff,
Play,
Users,
} from "react-feather";
import {
AdvancedAppSettings,
SipCallDirection,
@@ -60,6 +49,19 @@ import {
import JambonzSwitch from "src/components/switch";
import { DEFAULT_TOAST_DURATION } from "src/common/constants";
import { RegisteredUser } from "src/api/types";
import {
faCodeMerge,
faList,
faMicrophone,
faMicrophoneSlash,
faPause,
faPeopleGroup,
faPhoneSlash,
faPlay,
faUserGroup,
faUsers,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
type PhoneProbs = {
sipDomain: string;
@@ -539,7 +541,7 @@ export const Phone = ({
<HStack spacing={2} align="start" w="full">
{registeredUser.allow_direct_user_calling && (
<IconButtonMenu
icon={<Users />}
icon={<FontAwesomeIcon icon={faUserGroup} />}
tooltip="Call an online user"
noResultLabel="No one else is online"
onClick={(_, value) => {
@@ -551,7 +553,9 @@ export const Phone = ({
(resolve, reject) => {
getRegisteredUser()
.then(({ json }) => {
const sortedUsers = json.sort((a, b) => a.localeCompare(b));
const sortedUsers = json.sort((a, b) =>
a.localeCompare(b)
);
resolve(
sortedUsers
.filter((u) => !u.includes(sipUsername))
@@ -573,7 +577,7 @@ export const Phone = ({
{registeredUser.allow_direct_queue_calling && (
<IconButtonMenu
icon={<List />}
icon={<FontAwesomeIcon icon={faList} />}
tooltip="Take a call from queue"
noResultLabel="No calls in queue"
onClick={(name, value) => {
@@ -587,7 +591,9 @@ export const Phone = ({
(resolve, reject) => {
getQueues()
.then(({ json }) => {
const sortedQueues = json.sort((a, b) => a.name.localeCompare(b.name));
const sortedQueues = json.sort((a, b) =>
a.name.localeCompare(b.name)
);
resolve(
sortedQueues.map((q) => ({
name: `${q.name} (${q.length})`,
@@ -604,7 +610,7 @@ export const Phone = ({
{registeredUser.allow_direct_app_calling && (
<IconButtonMenu
icon={<GitMerge />}
icon={<FontAwesomeIcon icon={faCodeMerge} />}
tooltip="Call an application"
noResultLabel="No applications"
onClick={(name, value) => {
@@ -618,7 +624,9 @@ export const Phone = ({
(resolve, reject) => {
getApplications()
.then(({ json }) => {
const sortedApps = json.sort((a, b) => a.name.localeCompare(b.name));
const sortedApps = json.sort((a, b) =>
a.name.localeCompare(b.name)
);
resolve(
sortedApps.map((a) => ({
name: a.name,
@@ -632,6 +640,37 @@ export const Phone = ({
}}
/>
)}
<IconButtonMenu
icon={<FontAwesomeIcon icon={faPeopleGroup} />}
tooltip="Call an application"
noResultLabel="No applications"
onClick={(name, value) => {
setAppName(`App ${name}`);
const calledAppId = `app-${value}`;
setInputNumber("");
makeOutboundCall(calledAppId, `App ${name}`);
}}
onOpen={() => {
return new Promise<IconButtonMenuItems[]>(
(resolve, reject) => {
getApplications()
.then(({ json }) => {
const sortedApps = json.sort((a, b) =>
a.name.localeCompare(b.name)
);
resolve(
sortedApps.map((a) => ({
name: a.name,
value: a.application_sid,
}))
);
})
.catch((err) => reject(err));
}
);
}}
/>
</HStack>
)}
@@ -680,7 +719,11 @@ export const Phone = ({
<IconButton
aria-label="Place call onhold"
icon={
sipUA.current?.isHolded(undefined) ? <Play /> : <Pause />
<FontAwesomeIcon
icon={
sipUA.current?.isHolded(undefined) ? faPlay : faPause
}
/>
}
w="33%"
variant="unstyled"
@@ -694,7 +737,7 @@ export const Phone = ({
<Spacer />
<IconButton
aria-label="Hangup"
icon={<PhoneOff />}
icon={<FontAwesomeIcon icon={faPhoneSlash} />}
w="70px"
h="70px"
borderRadius="100%"
@@ -708,7 +751,13 @@ export const Phone = ({
<IconButton
aria-label="Mute"
icon={
sipUA.current?.isMuted(undefined) ? <Mic /> : <MicOff />
<FontAwesomeIcon
icon={
sipUA.current?.isMuted(undefined)
? faMicrophone
: faMicrophoneSlash
}
/>
}
w="33%"
variant="unstyled"

View File

@@ -1,5 +1,6 @@
import { Button, Icon, Text, VStack } from "@chakra-ui/react";
import { PhoneCall } from "react-feather";
import { faPhone } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { formatPhoneNumber } from "src/utils";
type OutGoingCallProbs = {
@@ -10,7 +11,12 @@ type OutGoingCallProbs = {
export const OutGoingCall = ({ number, cancelCall }: OutGoingCallProbs) => {
return (
<VStack alignItems="center" spacing={4} mt="130px" w="full">
<Icon as={PhoneCall} color="jambonz.500" w="60px" h="60px" />
<FontAwesomeIcon
icon={faPhone}
color="jambonz.500"
width="60px"
height="60px"
/>
<Text fontSize="15px">Dialing</Text>
<Text fontSize="24px" fontWeight="bold">
{formatPhoneNumber(number)}

View File

@@ -6,16 +6,18 @@ import {
Icon,
Image,
Input,
Spacer,
Text,
VStack,
} from "@chakra-ui/react";
import {
faCheckCircle,
faCircleXmark,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useEffect, useState } from "react";
import { CheckCircle, XCircle } from "react-feather";
import { getApplications } from "src/api";
import { AdvancedAppSettings } from "src/common/types";
import PasswordInput from "src/components/password-input";
import InfoIcon from "src/imgs/icons/Info.svg";
import ResetIcon from "src/imgs/icons/Reset.svg";
import { getAdvancedSettings, saveAddvancedSettings } from "src/storage";
import { normalizeUrl } from "src/utils";
@@ -109,10 +111,9 @@ export const AdvancedSettings = () => {
</VStack>
{isAdvancedMode && (
<HStack w="full" mt={2} mb={2}>
<Icon
as={isCredentialOk ? CheckCircle : XCircle}
<FontAwesomeIcon
icon={isCredentialOk ? faCheckCircle : faCircleXmark}
color={isCredentialOk ? "green.500" : "red.500"}
boxSize={6}
/>
<Text
fontSize="14px"