Skip to content

Commit

Permalink
Merge pull request #489 from buttercup/feat/input_btn_control
Browse files Browse the repository at this point in the history
Input button customisation
  • Loading branch information
perry-mitchell committed Apr 2, 2024
2 parents 7b7722e + 30c33ce commit f5203e5
Show file tree
Hide file tree
Showing 12 changed files with 149 additions and 16 deletions.
39 changes: 37 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "buttercup-browser-extension",
"version": "3.0.0",
"version": "3.1.0",
"description": "Buttercup browser extension",
"exports": "./dist/background/index.js",
"type": "module",
Expand Down Expand Up @@ -61,6 +61,7 @@
"@blueprintjs/core": "^4.20.2",
"@blueprintjs/icons": "^4.16.0",
"@blueprintjs/popover2": "^1.14.11",
"@blueprintjs/select": "^4.9.24",
"@buttercup/channel-queue": "^1.4.0",
"@buttercup/locust": "^2.2.1",
"@buttercup/ui": "^6.2.2",
Expand Down
3 changes: 2 additions & 1 deletion source/background/services/config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { getSyncValue, setSyncValue } from "./storage.js";
import { Configuration, SyncStorageItem } from "../types.js";
import { Configuration, InputButtonType, SyncStorageItem } from "../types.js";

const DEFAULTS: Configuration = {
entryIcons: true,
inputButtonDefault: InputButtonType.LargeButton,
saveNewLogins: true,
theme: "light",
useSystemTheme: true
Expand Down
50 changes: 49 additions & 1 deletion source/popup/components/pages/SettingsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { Fragment, useCallback, useMemo, useState } from "react";
import styled from "styled-components";
import { Alert, Button, Callout, Classes, Intent, Switch } from "@blueprintjs/core";
import { Alert, Button, Callout, Classes, Intent, MenuItem, Switch } from "@blueprintjs/core";
import { ItemRendererProps, Select } from "@blueprintjs/select";
import { t } from "../../../shared/i18n/trans.js";
import { useConfig } from "../../../shared/hooks/config.js";
import { ErrorMessage } from "../../../shared/components/ErrorMessage.js";
Expand All @@ -9,6 +10,11 @@ import { getToaster } from "../../../shared/services/notifications.js";
import { localisedErrorMessage } from "../../../shared/library/error.js";
import { useAllLoginCredentials } from "../../hooks/credentials.js";
import { createNewTab, getExtensionURL } from "../../../shared/library/extension.js";
import { InputButtonType } from "../../types.js";

interface InputButtonTypeItem {
name: string, type: InputButtonType;
}

const Container = styled.div`
display: flex;
Expand All @@ -26,11 +32,37 @@ const SettingSection = styled(Callout)`
padding: 9px;
`;

function renderInputButtonTypeItem(item: InputButtonTypeItem, props: ItemRendererProps) {
const { handleClick, handleFocus, modifiers } = props;
return (
<MenuItem
active={modifiers.active}
disabled={modifiers.disabled}
key={item.type}
label={item.type === InputButtonType.LargeButton ? t("config.default-hint") : ""}
onClick={handleClick}
onFocus={handleFocus}
roleStructure="listoption"
text={item.name}
/>
);
}

export function SettingsPage() {
const [config, configError, setValue] = useConfig();
const [showConfirmReset, setShowConfirmReset] = useState<boolean>(false);
const { value: allCredentials } = useAllLoginCredentials();
const hasSavedCredentials = useMemo(() => Array.isArray(allCredentials) && allCredentials.length > 0, [allCredentials]);
const inputButtonItems: Array<InputButtonTypeItem> = useMemo(() => Object.values(InputButtonType).map(type => ({
name: t(`config.input-button-type.${type}`),
type
})), []);
const activeInputButtonItem = useMemo(
() => inputButtonItems.find(item => item.type === config?.inputButtonDefault),
[config, inputButtonItems]);
const handleInputButtonItemSelect = useCallback((item: InputButtonTypeItem) => {
setValue("inputButtonDefault", item.type);
}, [setValue]);
const handleOpenDisabledDomains = useCallback(async () => {
try {
await createNewTab(getExtensionURL("full.html#/disabled-domains"));
Expand Down Expand Up @@ -110,6 +142,22 @@ export function SettingsPage() {
</Fragment>
)}
</SettingSection>
<SettingSection title={t("config.section.forms")}>
<Select
activeItem={activeInputButtonItem}
fill
filterable={false}
items={inputButtonItems}
onItemSelect={handleInputButtonItemSelect}
itemRenderer={renderInputButtonTypeItem}
>
<Button
text={t(`config.input-button-type.${config.inputButtonDefault}`)}
rightIcon="double-caret-vertical"
// placeholder="Select a film"
/>
</Select>
</SettingSection>
<SettingSection title={t("config.section.privacy")}>
<Switch
checked={config.entryIcons}
Expand Down
6 changes: 6 additions & 0 deletions source/shared/i18n/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@
"title": "Attributions"
},
"config": {
"default-hint": "(default)",
"input-button-type": {
"innericon": "Small interior icon",
"largebutton": "Large external button"
},
"reset-dialog": {
"cancel-button": "Cancel",
"confirm-button": "Reset",
"message": "Are you sure you wish to reset the application? This clears all cached data, settings and keys."
},
"section": {
"advanced": "Advanced Settings",
"forms": "Forms",
"logins": "Logins",
"privacy": "Privacy",
"theme": "Theme"
Expand Down
4 changes: 2 additions & 2 deletions source/shared/library/version.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Do not edit this file - it is generated automatically at build time

export const BUILD_DATE = "2024-03-27";
export const VERSION = "3.0.0";
export const BUILD_DATE = "2024-04-02";
export const VERSION = "3.1.0";
1 change: 1 addition & 0 deletions source/shared/styles/base.sass
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@import "~@blueprintjs/core/lib/css/blueprint.css"
@import "~@blueprintjs/icons/lib/css/blueprint-icons.css"
@import "~@blueprintjs/popover2/lib/css/blueprint-popover2.css"
@import "~@blueprintjs/select/lib/css/blueprint-select.css"

@import "fonts"

Expand Down
6 changes: 6 additions & 0 deletions source/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export type ChildElements = ChildElement | Array<ChildElement>;

export interface Configuration {
entryIcons: boolean;
inputButtonDefault: InputButtonType;
saveNewLogins: boolean;
theme: "light" | "dark";
useSystemTheme: boolean;
Expand All @@ -107,6 +108,11 @@ export interface ElementRect {
height: number;
}

export enum InputButtonType {
InnerIcon = "innericon",
LargeButton = "largebutton"
}

export enum InputType {
OTP = "otp",
UserPassword = "user-password"
Expand Down
13 changes: 13 additions & 0 deletions source/tab/services/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Layerr } from "layerr";
import { sendBackgroundMessage } from "../../shared/services/messaging.js";
import { BackgroundMessageType, Configuration } from "../types.js";

export async function getConfig(): Promise<Configuration> {
const resp = await sendBackgroundMessage({
type: BackgroundMessageType.GetConfiguration
});
if (resp.error) {
throw new Layerr(resp.error, "Failed fetching configuration");
}
return resp.config;
}
2 changes: 1 addition & 1 deletion source/tab/services/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function fillFormDetails(frameEvent: FrameEvent) {

export async function initialise() {
// Watch for forms
waitAndAttachLaunchButtons((input, loginTarget, inputType) => {
await waitAndAttachLaunchButtons((input, loginTarget, inputType) => {
FORM.currentFormID = ulid();
FORM.currentLoginTarget = loginTarget;
if (FRAME.isTop) {
Expand Down
16 changes: 12 additions & 4 deletions source/tab/services/formDetection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { attachLaunchButton } from "../ui/launch.js";
import { watchCredentialsOnTarget } from "./logins/watcher.js";
import { processTargetAutoLogin } from "./autoLogin.js";
import { InputType } from "../types.js";
import { getConfig } from "./config.js";

const TARGET_SEARCH_INTERVAL = 1000;

Expand All @@ -28,19 +29,26 @@ function onIdentifiedTarget(callback: (target: LoginTarget) => void) {
};
}

export function waitAndAttachLaunchButtons(
export async function waitAndAttachLaunchButtons(
onInputActivate: (input: HTMLInputElement, loginTarget: LoginTarget, inputType: InputType) => void
) {
const config = await getConfig();
onIdentifiedTarget((loginTarget: LoginTarget) => {
const { otpField, usernameField, passwordField } = loginTarget;
if (otpField) {
attachLaunchButton(otpField, (el) => onInputActivate(el, loginTarget, InputType.OTP));
attachLaunchButton(otpField, config.inputButtonDefault, (el) =>
onInputActivate(el, loginTarget, InputType.OTP)
);
}
if (passwordField) {
attachLaunchButton(passwordField, (el) => onInputActivate(el, loginTarget, InputType.UserPassword));
attachLaunchButton(passwordField, config.inputButtonDefault, (el) =>
onInputActivate(el, loginTarget, InputType.UserPassword)
);
}
if (usernameField) {
attachLaunchButton(usernameField, (el) => onInputActivate(el, loginTarget, InputType.UserPassword));
attachLaunchButton(usernameField, config.inputButtonDefault, (el) =>
onInputActivate(el, loginTarget, InputType.UserPassword)
);
}
watchCredentialsOnTarget(loginTarget);
processTargetAutoLogin(loginTarget).catch(console.error);
Expand Down
22 changes: 18 additions & 4 deletions source/tab/ui/launch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@ import { onBodyWidthResize } from "../library/resize.js";
import { getExtensionURL } from "../../shared/library/extension.js";
import BUTTON_BACKGROUND_IMAGE_RES from "../../../resources/content-button-background.png";
import INPUT_BACKGROUND_IMAGE_RES from "../../../resources/buttercup-simple-150.png";
import { InputButtonType } from "../types.js";

const BUTTON_BACKGROUND_IMAGE = getExtensionURL(BUTTON_BACKGROUND_IMAGE_RES);
const INPUT_BACKGROUND_IMAGE = getExtensionURL(INPUT_BACKGROUND_IMAGE_RES);

export function attachLaunchButton(input: HTMLInputElement, onClick: (input: HTMLInputElement) => void) {
export function attachLaunchButton(
input: HTMLInputElement,
buttonType: InputButtonType,
onClick: (input: HTMLInputElement) => void
): void {
if (input.dataset.bcup === "attached" || itemIsIgnored(input)) {
return;
}
Expand All @@ -24,8 +29,11 @@ export function attachLaunchButton(input: HTMLInputElement, onClick: (input: HTM
setTimeout(tryToAttach, 250);
return;
}
renderButtonStyle(input, () => onClick(input), tryToAttach, bounds);
// renderInternalStyle(input, () => onClick(input), tryToAttach, bounds);
if (buttonType === InputButtonType.LargeButton) {
renderButtonStyle(input, () => onClick(input), tryToAttach, bounds);
} else if (buttonType === InputButtonType.InnerIcon) {
renderInternalStyle(input, () => onClick(input), tryToAttach, bounds);
}
};
tryToAttach();
}
Expand All @@ -41,6 +49,7 @@ function renderInternalStyle(
const imageSize = height * 0.6;
const rightOffset = 8;
const buttonArea = imageSize + rightOffset + 4;
const originalAutocomplete = input.getAttribute("autocomplete") ?? null;
setStyle(input, {
backgroundImage: `url(${INPUT_BACKGROUND_IMAGE})`,
backgroundSize: `${imageSize}px`,
Expand All @@ -52,16 +61,21 @@ function renderInternalStyle(
if (event.offsetX >= input.offsetWidth - buttonArea) {
event.preventDefault();
event.stopPropagation();
// toggleInputDialog(input, DIALOG_TYPE_ENTRY_PICKER);
onClick();
}
};
input.onmousemove = (event) => {
if (event.offsetX >= input.offsetWidth - buttonArea) {
input.setAttribute("autocomplete", "off");
setStyle(input, {
cursor: "pointer"
});
} else {
if (originalAutocomplete) {
input.setAttribute("autocomplete", originalAutocomplete);
} else {
input.removeAttribute("autocomplete");
}
setStyle(input, {
cursor: "unset"
});
Expand Down

0 comments on commit f5203e5

Please sign in to comment.