Skip to content

Commit

Permalink
feat: extension settings (#132)
Browse files Browse the repository at this point in the history
* feat: extension settings

* link to settings

* aiprdescription setting

* lint fixes

* remove switch on/off button from ai config menu

* add settings for code refactor suggestions

* tests fix
  • Loading branch information
diivi committed Jun 5, 2023
1 parent b7664d3 commit 722cbbe
Show file tree
Hide file tree
Showing 17 changed files with 216 additions and 77 deletions.
6 changes: 3 additions & 3 deletions src/App.tsx
@@ -1,7 +1,7 @@
import { useEffect } from "react";
import Start from "./pages/start";
import Home from "./pages/home";
import Loading from "./pages/loading";
import Start from "./popup/pages/start";
import Home from "./popup/pages/home";
import Loading from "./popup/pages/loading";
import { useAuth } from "./hooks/useAuth";
import { goTo } from "react-chrome-extension-router";

Expand Down
Expand Up @@ -38,9 +38,6 @@ const handleSubmit = async (commentNode: HTMLElement) => {
if (!descriptionConfig) {
return;
}
if (!descriptionConfig.enabled) {
return alert("AI PR description is disabled!");
}

logo.classList.toggle("animate-spin");
button.classList.toggle("pointer-events-none");
Expand Down
Expand Up @@ -16,7 +16,6 @@ export const DescriptionGeneratorButton = () => {
</span>
<tool-tip for="ai-description-gen">Generate PR description</tool-tip>`,
onclick: handleSubmit,

});

return descriptionGeneratorButton;
Expand All @@ -34,6 +33,13 @@ const handleSubmit = async () => {
if (!logo || !button) {
return;
}

const descriptionConfig = await getAIDescriptionConfig();

if (!descriptionConfig) {
return;
}

logo.classList.toggle("animate-spin");
button.classList.toggle("pointer-events-none");

Expand Down
2 changes: 1 addition & 1 deletion src/content-scripts/github.ts
Expand Up @@ -26,7 +26,7 @@ const processGithubPage = async () => {
} else if (isPullRequestFilesChangedPage(window.location.href)) {
prReviewWatch(injectChangeSuggestorButton, 500);
} else if (isGithubPullRequestPage(window.location.href)) {
prEditWatch(injectDescriptionGeneratorButton);
prEditWatch(injectDescriptionGeneratorButton, 500);
void injectAddPRToHighlightsButton();
} else if (isGithubProfilePage(window.location.href)) {
const username = getGithubUsername(window.location.href);
Expand Down
55 changes: 55 additions & 0 deletions src/popup/components/ToggleSwitch.tsx
@@ -0,0 +1,55 @@
import { useState } from "react";

const Toggle = ({ settingName, settingLabel, enabledSetting }: { settingName: string; enabledSetting: boolean; settingLabel: string }) => {
const [enabled, setEnabled] = useState(enabledSetting);

const changeSetting = async (value: boolean) => {
const settingsConfig = await chrome.storage.sync.get("osSettingsConfig");

if (settingsConfig.osSettingsConfig === undefined) {
const defaultSettings = { aiPrDescription: false };

await chrome.storage.sync.set({ osSettingsConfig: defaultSettings });
}

const newSettingsConfig = { ...settingsConfig.osSettingsConfig, [settingName]: value };

await chrome.storage.sync.set({ osSettingsConfig: newSettingsConfig });

setEnabled(value);
};

return (
<div className="flex items-center justify-between mt-3">
<span className="text-sm font-medium text-gray-400">
{settingLabel}
</span>

<label className="inline-flex relative items-center mr-5 cursor-pointer">
<input
readOnly
checked={enabled}
className="sr-only peer"
type="checkbox"
/>

<div
aria-checked="false"
className="w-11 h-6 bg-gray-200 rounded-full peer peer-focus:ring-green-300 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-green-600"
role="checkbox"
tabIndex={0}
onClick={ async () => {
enabled ? await changeSetting(false) : await changeSetting(true);
}}
onKeyDown={ async e => {
if (e.key === "Enter") {
enabled ? await changeSetting(false) : await changeSetting(true);
}
}}
/>
</label>
</div>
);
};

export default Toggle;
@@ -1,6 +1,6 @@
import React, { useEffect, useReducer, useState } from "react";
import { FaChevronLeft } from "react-icons/fa";
import OpenSaucedLogo from "../assets/opensauced-logo.svg";
import OpenSaucedLogo from "../../assets/opensauced-logo.svg";
import { ImSwitch } from "react-icons/im";
import toast, { Toaster } from "react-hot-toast";

Expand All @@ -11,10 +11,9 @@ import {
DescriptionLanguage,
setAIDescriptionConfig,
getDefaultDescriptionConfig,
toggleAIPRDescriptionEnabled,
} from "../utils/aiprdescription/descriptionconfig";
import { useRefs } from "../hooks/useRefs";
import { configurationReducer } from "../utils/aiprdescription/configurationReducer";
} from "../../utils/aiprdescription/descriptionconfig";
import { useRefs } from "../../hooks/useRefs";
import { configurationReducer } from "../../utils/aiprdescription/configurationReducer";
import { goBack } from "react-chrome-extension-router";

const AIPRDescription = () => {
Expand All @@ -24,15 +23,13 @@ const AIPRDescription = () => {
const tones: DescriptionTone[] = ["exciting", "persuasive", "informative", "humorous", "formal"];
const sources: DescriptionSource[] = ["diff", "commitMessage", "both"];
const languages: DescriptionLanguage[] = ["english", "spanish", "french", "german", "italian", "portuguese", "dutch", "russian", "chinese", "korean"];
const [formDisabled, setFormDisabled] = useState<boolean>(true);


useEffect(() => {
const descriptionConfig = async () => {
const configData = await getAIDescriptionConfig();

dispatch({ type: "SET", value: configData });
setFormDisabled(!configData?.enabled);
};

void descriptionConfig();
Expand All @@ -47,10 +44,7 @@ const AIPRDescription = () => {
const source = (refs.source as HTMLSelectElement).value as DescriptionSource;
const tone = (refs.tone as HTMLSelectElement).value as DescriptionTone;

void setAIDescriptionConfig({
enabled: true,
config: { length, temperature, maxInputLength, language, source, tone },
});
void setAIDescriptionConfig({ config: { length, temperature, maxInputLength, language, source, tone } });
toast.success("Configuration updated!");
};

Expand Down Expand Up @@ -78,32 +72,14 @@ const AIPRDescription = () => {
src={OpenSaucedLogo}
/>
</div>

<button
title="Toggle AI PR Description"
className={`text-lg ${formDisabled ? "text-gray-400" : "text-orange"
}`}
onClick={() => {
setFormDisabled(!formDisabled);
dispatch({ type: "TOGGLE_ENABLED", value: config });
void toggleAIPRDescriptionEnabled();
if (formDisabled) {
toast.success("AI PR Description enabled!");
} else {
toast.error("AI PR Description disabled!");
}
}}
>
<ImSwitch />
</button>
</header>

<main className="text-white">
<form
ref={setRefFromKey("form")}
onSubmit={handleFormSubmit}
>
<fieldset disabled={formDisabled}>
<fieldset>
<h1 className="text-2xl text-white font-bold my-2">
OpenSauced AI
</h1>
Expand Down
6 changes: 3 additions & 3 deletions src/pages/help.tsx → src/popup/pages/help.tsx
@@ -1,13 +1,13 @@
import { FaChevronLeft } from "react-icons/fa";
import OpenSaucedLogo from "../assets/opensauced-logo.svg";
import { EXTERNAL_RESOURCES } from "../constants";
import OpenSaucedLogo from "../../assets/opensauced-logo.svg";
import { EXTERNAL_RESOURCES } from "../../constants";
import {
HiOutlineBookOpen,
HiOutlineChatBubbleLeftRight,
} from "react-icons/hi2";
import { goBack } from "react-chrome-extension-router";
import { VscIssues } from "react-icons/vsc";
import { version } from "../../package.json";
import { version } from "../../../package.json";

const Help = () => (
<div className="p-4 bg-slate-800">
Expand Down
28 changes: 20 additions & 8 deletions src/pages/home.tsx → src/popup/pages/home.tsx
Expand Up @@ -6,20 +6,22 @@ import {
HiPencil,
HiUserCircle,
} from "react-icons/hi2";
import { useEffect, useState } from "react";
import OpenSaucedLogo from "../assets/opensauced-logo.svg";
import { useAuth } from "../hooks/useAuth";
import { useOpensaucedUserCheck } from "../hooks/useOpensaucedUserCheck";
import { FiSettings } from "react-icons/fi";
import OpenSaucedLogo from "../../assets/opensauced-logo.svg";
import { useAuth } from "../../hooks/useAuth";
import { useOpensaucedUserCheck } from "../../hooks/useOpensaucedUserCheck";
import { Profile } from "./profile";
import { goTo } from "react-chrome-extension-router";
import AIPRDescription from "./aiprdescription";
import PostOnHighlight from "./posthighlight";
import { getHighlights } from "../utils/fetchOpenSaucedApiData";
import { getHighlights } from "../../utils/fetchOpenSaucedApiData";

import Help from "./help";
import { OPEN_SAUCED_INSIGHTS_DOMAIN } from "../constants";
import type { Highlight } from "../ts/types";
import { useIsGithubPRPageCheck } from "../hooks/useGithubPRPageCheck";
import { useEffect, useState } from "react";
import Settings from "./settings";
import { OPEN_SAUCED_INSIGHTS_DOMAIN } from "../../constants";
import type { Highlight } from "../../ts/types";
import { useIsGithubPRPageCheck } from "../../hooks/useGithubPRPageCheck";

const Home = () => {
const { user } = useAuth();
Expand Down Expand Up @@ -215,6 +217,16 @@ const Home = () => {
<HiOutlineQuestionMarkCircle />
Help
</button>

<button
className="flex items-center bg-slate-700 hover:bg-slate-700/70 hover:text-orange text-white gap-2 p-1.5 px-3 w-fit rounded-sm font-medium text-sm"
onClick={() => {
goTo(Settings);
}}
>
<FiSettings />
Settings
</button>
</footer>
</div>
</div>
Expand Down
File renamed without changes.
@@ -1,12 +1,12 @@
import { useEffect, useState } from "react";
import { FaChevronLeft } from "react-icons/fa";
import OpenSaucedLogo from "../assets/opensauced-logo.svg";
import { useAuth } from "../hooks/useAuth";
import OpenSaucedLogo from "../../assets/opensauced-logo.svg";
import { useAuth } from "../../hooks/useAuth";
import toast, { Toaster } from "react-hot-toast";
import { createHighlight } from "../../utils/fetchOpenSaucedApiData";
import { goBack } from "react-chrome-extension-router";
import { OPEN_SAUCED_INSIGHTS_DOMAIN } from "../constants";
import { getAiDescription } from "../content-scripts/components/GenerateAIDescription/DescriptionGeneratorButton";
import { createHighlight } from "../utils/fetchOpenSaucedApiData";
import { OPEN_SAUCED_INSIGHTS_DOMAIN } from "../../constants";
import { getAiDescription } from "../../content-scripts/components/GenerateAIDescription/DescriptionGeneratorButton";

const PostOnHighlight = ({ prUrl, prTitle }: { prUrl: string, prTitle: string }) => {
const { authToken, user } = useAuth();
Expand Down
12 changes: 6 additions & 6 deletions src/pages/profile.tsx → src/popup/pages/profile.tsx
Expand Up @@ -3,16 +3,16 @@ import { FaBrain, FaChevronLeft, FaRobot } from "react-icons/fa";
import { RiLinkedinFill, RiLinkM, RiTwitterFill } from "react-icons/ri";
import { SiC, SiCplusplus, SiCsharp, SiGoland, SiJavascript, SiPhp, SiPython, SiReact, SiRuby, SiRust, SiTypescript } from "react-icons/si";
import { DiJava } from "react-icons/di";
import OpenSaucedLogo from "../assets/opensauced-logo.svg";
import { getUserData, getUserPRData, getUserHighlightsData } from "../utils/fetchOpenSaucedApiData";
import OpenSaucedLogo from "../../assets/opensauced-logo.svg";
import { getUserData, getUserPRData, getUserHighlightsData } from "../../utils/fetchOpenSaucedApiData";
import { emojify } from "node-emoji";
import { goBack, goTo } from "react-chrome-extension-router";
import { getRelativeDays } from "../utils/dateUtils";
import { getUserPRVelocity } from "../utils/getUserPRVelocity";
import { getRelativeDays } from "../../utils/dateUtils";
import { getUserPRVelocity } from "../../utils/getUserPRVelocity";
import { BiExit } from "react-icons/bi";
import Start from "./start";
import { optLogOut } from "../utils/checkAuthentication";
import { OPEN_SAUCED_INSIGHTS_DOMAIN } from "../constants";
import { optLogOut } from "../../utils/checkAuthentication";
import { OPEN_SAUCED_INSIGHTS_DOMAIN } from "../../constants";

const interestIcon = {
python: <SiPython />,
Expand Down
78 changes: 78 additions & 0 deletions src/popup/pages/settings.tsx
@@ -0,0 +1,78 @@
import { FaChevronLeft } from "react-icons/fa";
import OpenSaucedLogo from "../../assets/opensauced-logo.svg";
import { goBack } from "react-chrome-extension-router";
import Toggle from "../components/ToggleSwitch";
import { useEffect, useState } from "react";

export type SettingsConfig = Record<string, boolean | undefined>;

const settingLabels: Record<string, string> = {
aiPrDescription: "AI PR Description",
codeRefactor: "Code Refactor",
};

const Settings = () => {
const [settingsConfig, setSettingsConfig] = useState<SettingsConfig>({});

useEffect(() => {
const getSettingsDataFromStorage = async () => {
const settingsConfig = await chrome.storage.sync.get("osSettingsConfig");

if (settingsConfig.osSettingsConfig === undefined) {
const defaultSettings = { aiPrDescription: true, codeRefactor: true };

await chrome.storage.sync.set({ osSettingsConfig: defaultSettings });
setSettingsConfig(defaultSettings);
} else {
setSettingsConfig(settingsConfig.osSettingsConfig);
}
};

void getSettingsDataFromStorage();
}, []);

return (
<div className="p-4 bg-slate-800">
<div className="grid grid-cols-1 divide-y divide-white/40 divider-y-center-2 min-w-[320px] text-white">
<header className="flex justify-between">
<div className="flex items-center gap-2">
<button
className="rounded-full p-2 bg-slate-700 hover:bg-slate-700/50"
onClick={() => {
goBack();
}}
>
<FaChevronLeft className="text-osOrange text-white" />
</button>

<img
alt="OpenSauced logo"
className="w-[100%]"
src={OpenSaucedLogo}
/>
</div>
</header>

<main className="main-content text-white">
<h3 className="text font-medium text-base leading-10">Settings:</h3>

{
Object.keys(settingsConfig).map(settingName => (
<Toggle
key={settingName}
enabledSetting={settingsConfig[settingName]!}
settingLabel={settingLabels[settingName]}
settingName={settingName}
/>
))

}

<div className="flex flex-col gap-2" />
</main>
</div>
</div>
);
};

export default Settings;
4 changes: 2 additions & 2 deletions src/pages/start.tsx → src/popup/pages/start.tsx
@@ -1,5 +1,5 @@
import OpenSaucedLogo from "../assets/opensauced-logo.svg";
import { optLogIn } from "../utils/checkAuthentication";
import OpenSaucedLogo from "../../assets/opensauced-logo.svg";
import { optLogIn } from "../../utils/checkAuthentication";

const Start = () => (
<div className="p-4 bg-slate-800">
Expand Down
3 changes: 0 additions & 3 deletions src/utils/aiprdescription/configurationReducer.ts
Expand Up @@ -25,9 +25,6 @@ export const configurationReducer = (state: DescriptionConfig, action: { type: s
case "SET_TONE":
newState.config.tone = action.value;
break;
case "TOGGLE_ENABLED":
newState.enabled = !newState.enabled;
break;
case "CLEAR":
newState = getDefaultDescriptionConfig();
break;
Expand Down

0 comments on commit 722cbbe

Please sign in to comment.