diff --git a/src/equicordplugins/loginWithQR/index.tsx b/src/equicordplugins/loginWithQR/index.tsx
index 198784872..71af39c5d 100644
--- a/src/equicordplugins/loginWithQR/index.tsx
+++ b/src/equicordplugins/loginWithQR/index.tsx
@@ -7,41 +7,43 @@
import { plugins } from "@api/PluginManager";
import { definePluginSettings } from "@api/Settings";
import { Paragraph } from "@components/Paragraph";
+import SettingsPlugin, { settingsSectionMap } from "@plugins/_core/settings";
import { EquicordDevs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";
-import { Button, Menu } from "@webpack/common";
-import { ReactElement } from "react";
+import { Button } from "@webpack/common";
import { preload, unload } from "./images";
-import { cl } from "./ui";
+import { QrCodeIcon } from "./ui";
import openQrModal from "./ui/modals/QrModal";
+const settings = definePluginSettings({
+ scanQr: {
+ type: OptionType.COMPONENT,
+ description: "Scan a QR code",
+ component() {
+ if (!plugins.LoginWithQR.started)
+ return (
+
+ Enable the plugin and restart your client to scan a login QR code
+
+ );
+
+ return (
+
+ {getIntlMessage("USER_SETTINGS_SCAN_QR_CODE")}
+
+ );
+ },
+ },
+});
+
export default definePlugin({
name: "LoginWithQR",
description: "Allows you to login to another device by scanning a login QR code, just like on mobile!",
authors: [EquicordDevs.nexpid],
- settings: definePluginSettings({
- scanQr: {
- type: OptionType.COMPONENT,
- description: "Scan a QR code",
- component() {
- if (!plugins.LoginWithQR.started)
- return (
-
- Enable the plugin and restart your client to scan a login QR code
-
- );
-
- return (
-
- {getIntlMessage("USER_SETTINGS_SCAN_QR_CODE")}
-
- );
- },
- },
- }),
+ settings,
patches: [
// Prevent paste event from firing when the QRModal is open
@@ -52,69 +54,30 @@ export default definePlugin({
replace: "handleGlobalPaste:(...args)=>!$self.qrModalOpen&&$1(...args)",
},
},
- // Insert a Scan QR Code button in the My Account tab
- /* broken
- {
- find: "UserSettingsAccountProfileCard",
- replacement: {
- // Find the Edit User Profile button and insert our custom button.
- // A bit jank, but whatever
- match: /,(\(.{1,90}#{intl::USER_SETTINGS_EDIT_USER_PROFILE}\),onClick:\i\}\))/,
- replace: ",$self.insertScanQrButton($1)",
- },
- },
- */
- // Insert a Scan QR Code MenuItem in the Swith Accounts popout
- {
- find: 'id:"manage-accounts"',
- replacement: {
- match: /(id:"manage-accounts",.*?)}\)\)(,\i)/,
- replace: "$1}),$self.ScanQrMenuItem)$2"
- }
- },
-
- // Insert a Scan QR Code button in the Settings sheet
- {
- find: ".isInputProfileCustom()",
- replacement: {
- match: /\.CONNECTIONS/,
- replace: "$&,\"SCAN_QR_CODE\""
- }
- },
- // Insert a Scan QR Code button in the Settings sheet (part 2)
- {
- find: ".PRIVACY_ENCRYPTION_VERIFIED_DEVICES_V2]",
- replacement: {
- match: /\.PRIVACY_SAFETY_ALERTS_V2]:{.*?},/,
- replace: "$&\"SCAN_QR_CODE\":$self.ScanQrSettingsSheet,"
- }
- }
],
qrModalOpen: false,
- insertScanQrButton: (button: ReactElement) => (
-
-
- {getIntlMessage("USER_SETTINGS_SCAN_QR_CODE")}
-
- {button}
-
- ),
- get ScanQrMenuItem() {
- return ;
- },
- get ScanQrSettingsSheet() {
- return {
- section: getIntlMessage("USER_SETTINGS_SCAN_QR_CODE"),
- onClick: openQrModal,
- searchableTitles: [getIntlMessage("USER_SETTINGS_SCAN_QR_CODE")],
- label: getIntlMessage("USER_SETTINGS_SCAN_QR_CODE"),
- ariaLabel: getIntlMessage("USER_SETTINGS_SCAN_QR_CODE")
- };
- },
-
start() {
+ const { customEntries, customSections } = SettingsPlugin;
+
+ customEntries.push({
+ key: "equicord_login_with_qr",
+ title: getIntlMessage("USER_SETTINGS_SCAN_QR_CODE"),
+ Component: openQrModal,
+ Icon: QrCodeIcon
+ });
+
+ customSections.push(() => ({
+ section: "EquicordLoginWithQR",
+ label: getIntlMessage("USER_SETTINGS_SCAN_QR_CODE"),
+ searchableTitles: [getIntlMessage("USER_SETTINGS_SCAN_QR_CODE")],
+ element: openQrModal,
+ id: "LoginWithQR",
+ }));
+
+ settingsSectionMap.push(["EquicordLoginWithQR", "equicord_login_with_qr"]);
+
preload();
},
diff --git a/src/equicordplugins/loginWithQR/ui/modals/QrModal.tsx b/src/equicordplugins/loginWithQR/ui/modals/QrModal.tsx
index a3bc0473c..2f7ce90fc 100644
--- a/src/equicordplugins/loginWithQR/ui/modals/QrModal.tsx
+++ b/src/equicordplugins/loginWithQR/ui/modals/QrModal.tsx
@@ -5,20 +5,14 @@
*/
import { BaseText } from "@components/BaseText";
+import { wrapTab } from "@components/settings";
import loginWithQR from "@equicordplugins/loginWithQR";
import { images } from "@equicordplugins/loginWithQR/images";
-import { getIntlMessage } from "@utils/discord";
import {
- ModalContent,
- ModalHeader,
ModalProps,
- ModalRoot,
- ModalSize,
- openModal,
} from "@utils/modal";
import { findByPropsLazy } from "@webpack";
import {
- Button,
RestAPI,
useEffect,
useRef,
@@ -33,7 +27,6 @@ import openVerifyModal from "./VerifyModal";
enum LoginStateType {
Idle,
Loading,
- Camera,
}
interface Preview {
@@ -51,7 +44,6 @@ interface QrModalProps {
media: HTMLImageElement | HTMLVideoElement | null,
location?: QRCode["location"]
) => Promise;
- closeMain: () => void;
}
type QrModalPropsRef = MutableRefObject;
@@ -92,7 +84,6 @@ const verifyUrl = async (
body: { handshake_token: handshake },
});
},
- modalProps.closeMain
);
};
@@ -245,7 +236,6 @@ function QrModal(props: ModalProps) {
setTimeout(res, 500 + 300 + 300);
}
}),
- closeMain: props.onClose,
});
useEffect(() => {
@@ -284,243 +274,123 @@ function QrModal(props: ModalProps) {
return () => document.removeEventListener("paste", callback);
}, [state]);
- useEffect(() => {
- if (state !== LoginStateType.Camera) return;
-
- const video = document.createElement("video");
- video.width = 1280;
- video.height = 720;
- const canvas = document.createElement("canvas");
- canvas.width = video.width;
- canvas.height = video.height;
- const ctx = canvas.getContext("2d", { willReadFrequently: true })!;
-
- let stream: MediaStream;
- let snapshotTimeout: number;
- let stopped = false;
-
- const stop = (stream: MediaStream) => (
- stream.getTracks().forEach(track => track.stop()),
- modalProps.current.exit(null)
- );
-
- const snapshot = () => {
- ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
- const { data, width, height } = ctx.getImageData(
- 0,
- 0,
- canvas.width,
- canvas.height
- );
- const code = jsQR(data, width, height);
-
- const token = code?.data.match(tokenRegex)?.[1];
- if (token) {
- const img = new Image();
- img.addEventListener("load", () =>
- modalProps.current
- .setPreview(img, code.location)
- .then(
- () => (
- img.remove(),
- verifyUrl(token, modalProps).catch(e =>
- modalProps.current.exit(e?.message)
- )
- )
- )
- );
- canvas.toBlob(blob => blob && (img.src = URL.createObjectURL(blob)));
- } else snapshotTimeout = setTimeout(snapshot, 1e3) as any;
- };
-
- navigator.mediaDevices
- .getUserMedia({
- audio: false,
- video: {
- deviceId: getVideoDeviceId(),
- width: canvas.width,
- height: canvas.height,
- frameRate: 30,
- },
- })
- .then(str => {
- if (stopped) return stop(str);
-
- stream = str;
- video.srcObject = str;
- video.addEventListener("loadedmetadata", () => {
- if (stopped) return stop(str);
-
- video.play();
- modalProps.current.setPreview(video);
- snapshot();
- });
- })
- .catch(() => setState(LoginStateType.Idle));
-
- return () => {
- stopped = true;
- clearTimeout(snapshotTimeout);
- if (stream) stop(stream);
-
- video.remove();
- canvas.remove();
- };
- }, [state]);
-
return (
-
-
-
- {getIntlMessage("USER_SETTINGS_SCAN_QR_CODE")}
-
-
-
-
- state === LoginStateType.Idle && inputRef.current?.click()
- }
- onDragEnter={e =>
- e.currentTarget.classList.add(cl("modal-filepaste-drop"))
- }
- onDragLeave={e =>
- e.currentTarget.classList.remove(cl("modal-filepaste-drop"))
- }
- onDrop={e => {
- e.preventDefault();
- e.currentTarget.classList.remove(cl("modal-filepaste-drop"));
+
+
+ state === LoginStateType.Idle && inputRef.current?.click()
+ }
+ onDragEnter={e =>
+ e.currentTarget.classList.add(cl("modal-filepaste-drop"))
+ }
+ onDragLeave={e =>
+ e.currentTarget.classList.remove(cl("modal-filepaste-drop"))
+ }
+ onDrop={e => {
+ e.preventDefault();
+ e.currentTarget.classList.remove(cl("modal-filepaste-drop"));
- if (state !== LoginStateType.Idle) return;
+ if (state !== LoginStateType.Idle) return;
- for (const item of e.dataTransfer.files) {
- if (item.type.startsWith("image/")) {
- setState(LoginStateType.Loading);
- handleProcessImage(item, modalProps);
- break;
- }
+ for (const item of e.dataTransfer.files) {
+ if (item.type.startsWith("image/")) {
+ setState(LoginStateType.Loading);
+ handleProcessImage(item, modalProps);
+ break;
}
- }}
- role="button"
- style={
- preview?.size
- ? {
- width: `${preview.size.width}rem`,
- height: `${preview.size.height}rem`,
- }
- : {}
}
- >
- {preview?.source ? (
-
i + x, 0) /
- preview.crosses.length -
- 50)}%`
- : undefined,
- ["--offset-y" as any]: preview.crosses
- ? `${-(preview.crosses.reduce((i, { y }) => i + y, 0) /
- preview.crosses.length -
- 50)}%`
- : undefined,
- }}
- className={cl(preview?.crosses && "preview-crosses")}
- >
- {preview.source}
- {preview.crosses?.map(({ x, y, rot, size }) => (
-
-
-
- ))}
-
- ) : state === LoginStateType.Loading ? (
-
- ) : error.current ? (
-
- {error.current}
-
- ) : state === LoginStateType.Camera ? (
+ }}
+ role="button"
+ style={
+ preview?.size
+ ? {
+ width: `${preview.size.width}rem`,
+ height: `${preview.size.height}rem`,
+ }
+ : {}
+ }
+ >
+ {preview?.source ? (
+
i + x, 0) /
+ preview.crosses.length -
+ 50)}%`
+ : undefined,
+ ["--offset-y" as any]: preview.crosses
+ ? `${-(preview.crosses.reduce((i, { y }) => i + y, 0) /
+ preview.crosses.length -
+ 50)}%`
+ : undefined,
+ }}
+ className={cl(preview?.crosses && "preview-crosses")}
+ >
+ {preview.source}
+ {preview.crosses?.map(({ x, y, rot, size }) => (
+
+
+
+ ))}
+
+ ) : state === LoginStateType.Loading ? (
+
+ ) : error.current ? (
+
+ {error.current}
+
+ ) : (
+ <>
- Scanning...
+ Drag and drop an image here, or click to select an image
- ) : (
- <>
-
- Drag and drop an image here, or click to select an image
-
-
- Or paste an image from your clipboard!
-
-
-
- >
- )}
-
-
{
- if (!e.target.files || state !== LoginStateType.Idle) return;
+
+ Or paste an image from your clipboard!
+
+
+
+ >
+ )}
+
+
{
+ if (!e.target.files || state !== LoginStateType.Idle) return;
- for (const item of e.target.files) {
- if (item.type.startsWith("image/")) {
- setState(LoginStateType.Loading);
- handleProcessImage(item, modalProps);
- break;
- }
+ for (const item of e.target.files) {
+ if (item.type.startsWith("image/")) {
+ setState(LoginStateType.Loading);
+ handleProcessImage(item, modalProps);
+ break;
}
- }}
- ref={inputRef}
- style={{ display: "none" }}
- />
-
{
- if (state === LoginStateType.Idle) setState(LoginStateType.Camera);
- else if (state === LoginStateType.Camera)
- modalProps.current.exit(null);
- }}
- >
- {state === LoginStateType.Camera
- ? "Stop scanning"
- : "Scan using webcamera"}
-
-
-
+ }
+ }}
+ ref={inputRef}
+ style={{ display: "none" }}
+ />
+
);
}
-export default function openQrModal() {
- return openModal(props => );
-}
+export default wrapTab(QrModal, "Scan QR Code");
diff --git a/src/equicordplugins/loginWithQR/ui/modals/VerifyModal.tsx b/src/equicordplugins/loginWithQR/ui/modals/VerifyModal.tsx
index 77acc84ef..f988367a5 100644
--- a/src/equicordplugins/loginWithQR/ui/modals/VerifyModal.tsx
+++ b/src/equicordplugins/loginWithQR/ui/modals/VerifyModal.tsx
@@ -37,12 +37,10 @@ enum VerifyState {
function VerifyModal({
token,
onAbort,
- closeMain,
...props
}: {
token: string | null;
onAbort: () => void;
- closeMain: () => void;
} & ModalProps) {
const [state, setState] = useState(
!token ? VerifyState.NotFound : VerifyState.Verifying
@@ -77,7 +75,6 @@ function VerifyModal({
},
})
.then(() => {
- closeMain();
setState(VerifyState.LoggedIn);
})
.catch(() => setState(VerifyState.NotFound))
@@ -229,14 +226,12 @@ function VerifyModal({
export default function openVerifyModal(
token: string | null,
onAbort: () => void,
- closeMain: () => void
) {
return openModal(props => (
));
}
diff --git a/src/equicordplugins/loginWithQR/ui/styles.css b/src/equicordplugins/loginWithQR/ui/styles.css
index c449eb057..50b6cee9c 100644
--- a/src/equicordplugins/loginWithQR/ui/styles.css
+++ b/src/equicordplugins/loginWithQR/ui/styles.css
@@ -1,7 +1,3 @@
-.qrlogin-modal-header {
- padding: 16px;
-}
-
.qrlogin-modal-filepaste {
width: 30rem;
height: 13rem;
@@ -67,11 +63,6 @@
opacity: 0;
}
-.qrlogin-modal-button {
- margin: 1rem 0;
- width: 100%;
-}
-
.qrlogin-device-content {
padding: 24px;
display: flex;
@@ -111,6 +102,10 @@
gap: 8px;
}
+.qrlogin-modal-container {
+ margin-left: 100px;
+}
+
[data-tab-id="Scan QR Code"]::before {
/* stylelint-disable-next-line property-no-vendor-prefix */
-webkit-mask: var(--si-scan-qr-code) center/contain no-repeat !important;
@@ -133,4 +128,4 @@
to {
transform: translate(var(--offset-x), var(--offset-y)) scale(var(--scale));
}
-}
\ No newline at end of file
+}