mirror of
https://github.com/XFox111/TabsAsideExtension.git
synced 2026-04-22 07:58:01 +03:00
!feat: major 3.0 release candidate
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
import { BuyMeACoffee20Regular } from "@/assets/BuyMeACoffee20";
|
||||
import { bskyLink, buyMeACoffeeLink, githubLinks, storeLink, websiteLink } from "@/data/links";
|
||||
import { useBmcStyles } from "@/hooks/useBmcStyles";
|
||||
import extLink from "@/utils/extLink";
|
||||
import { Body1, Button, Caption1, Link, Subtitle1, Text } from "@fluentui/react-components";
|
||||
import { PersonFeedback20Regular } from "@fluentui/react-icons";
|
||||
import { useOptionsStyles } from "../hooks/useOptionsStyles";
|
||||
import Package from "@/package.json";
|
||||
|
||||
export default function AboutSection(): React.ReactElement
|
||||
{
|
||||
const cls = useOptionsStyles();
|
||||
const bmcCls = useBmcStyles();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text as="p">
|
||||
<Subtitle1>{ i18n.t("manifest.name") }</Subtitle1>
|
||||
<sup><Caption1> v{ Package.version }</Caption1></sup>
|
||||
</Text>
|
||||
|
||||
<Body1 as="p">
|
||||
{ i18n.t("options_page.about.developed_by") } (<Link { ...extLink(bskyLink) }>@xfox111.net</Link>)<br />
|
||||
{ i18n.t("options_page.about.licensed_under") } <Link { ...extLink(githubLinks.license) }>{ i18n.t("options_page.about.mit_license") }</Link>
|
||||
</Body1>
|
||||
|
||||
<Body1 as="p">
|
||||
{ i18n.t("options_page.about.translation_cta.text") }<br />
|
||||
<Link { ...extLink(githubLinks.translationGuide) }>
|
||||
{ i18n.t("options_page.about.translation_cta.button") }
|
||||
</Link>
|
||||
</Body1>
|
||||
|
||||
<Body1 as="p">
|
||||
<Link { ...extLink(websiteLink) }>{ i18n.t("options_page.about.links.website") }</Link><br />
|
||||
<Link { ...extLink(githubLinks.repo) }>{ i18n.t("options_page.about.links.source") }</Link><br />
|
||||
<Link { ...extLink(githubLinks.release) }>{ i18n.t("options_page.about.links.changelog") }</Link>
|
||||
</Body1>
|
||||
|
||||
<div className={ cls.horizontalButtons }>
|
||||
<Button
|
||||
as="a" { ...extLink(storeLink) }
|
||||
appearance="primary"
|
||||
icon={ <PersonFeedback20Regular /> }
|
||||
>
|
||||
{ i18n.t("common.cta.feedback") }
|
||||
</Button>
|
||||
<Button
|
||||
as="a" { ...extLink(buyMeACoffeeLink) }
|
||||
appearance="primary" className={ bmcCls.button }
|
||||
icon={ <BuyMeACoffee20Regular /> }
|
||||
>
|
||||
{ i18n.t("common.cta.sponsor") }
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import useSettings, { SettingsValue } from "@/hooks/useSettings";
|
||||
import { Dropdown, Field, Option } from "@fluentui/react-components";
|
||||
|
||||
export default function ActionsSection(): React.ReactElement
|
||||
{
|
||||
const [saveAction, setSaveAction] = useSettings("defaultSaveAction");
|
||||
const [restoreAction, setRestoreAction] = useSettings("defaultRestoreAction");
|
||||
|
||||
return (
|
||||
<>
|
||||
<Field label={ i18n.t("options_page.actions.options.save_actions.title") }>
|
||||
<Dropdown
|
||||
value={ saveAction ? saveActionOptions[saveAction] : "" }
|
||||
selectedOptions={ [saveAction ?? ""] }
|
||||
onOptionSelect={ (_, e) => setSaveAction(e.optionValue as SaveActionType) }
|
||||
>
|
||||
{ Object.entries(saveActionOptions).map(([value, label]) =>
|
||||
<Option key={ value } value={ value }>
|
||||
{ label }
|
||||
</Option>
|
||||
) }
|
||||
</Dropdown>
|
||||
</Field>
|
||||
|
||||
<Field label={ i18n.t("options_page.actions.options.restore_actions.title") }>
|
||||
<Dropdown
|
||||
value={ restoreAction ? restoreActionOptions[restoreAction] : "" }
|
||||
selectedOptions={ [restoreAction ?? ""] }
|
||||
onOptionSelect={ (_, e) => setRestoreAction(e.optionValue as RestoreActionType) }
|
||||
>
|
||||
{ Object.entries(restoreActionOptions).map(([value, label]) =>
|
||||
<Option key={ value } value={ value }>
|
||||
{ label }
|
||||
</Option>
|
||||
) }
|
||||
</Dropdown>
|
||||
</Field>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
type SaveActionType = SettingsValue<"defaultSaveAction">;
|
||||
type RestoreActionType = SettingsValue<"defaultRestoreAction">;
|
||||
|
||||
const restoreActionOptions: Record<RestoreActionType, string> =
|
||||
{
|
||||
"open": i18n.t("options_page.actions.options.restore_actions.options.open"),
|
||||
"restore": i18n.t("options_page.actions.options.restore_actions.options.restore")
|
||||
};
|
||||
|
||||
const saveActionOptions: Record<SaveActionType, string> =
|
||||
{
|
||||
"set_aside": i18n.t("options_page.actions.options.save_actions.options.set_aside"),
|
||||
"save": i18n.t("options_page.actions.options.save_actions.options.save")
|
||||
};
|
||||
@@ -0,0 +1,118 @@
|
||||
import useSettings, { SettingsValue } from "@/hooks/useSettings";
|
||||
import { Button, Checkbox, Dropdown, Field, Option, OptionOnSelectData } from "@fluentui/react-components";
|
||||
import { KeyCommand20Regular } from "@fluentui/react-icons";
|
||||
import { useOptionsStyles } from "../hooks/useOptionsStyles";
|
||||
|
||||
export default function GeneralSection(): React.ReactElement
|
||||
{
|
||||
const [alwaysShowToolbars, setAlwaysShowToolbars] = useSettings("alwaysShowToolbars");
|
||||
const [ignorePinned, setIgnorePinned] = useSettings("ignorePinned");
|
||||
const [deletePrompt, setDeletePrompt] = useSettings("deletePrompt");
|
||||
const [showBadge, setShowBadge] = useSettings("showBadge");
|
||||
const [notifyOnSave, setNotifyOnSave] = useSettings("notifyOnSave");
|
||||
const [dismissOnLoad, setDismissOnLoad] = useSettings("dismissOnLoad");
|
||||
const [listLocation, setListLocation] = useSettings("listLocation");
|
||||
const [contextAction, setContextAction] = useSettings("contextAction");
|
||||
|
||||
const cls = useOptionsStyles();
|
||||
|
||||
const openShortcutsPage = (): Promise<any> =>
|
||||
browser.tabs.create({
|
||||
url: "chrome://extensions/shortcuts",
|
||||
active: true
|
||||
});
|
||||
|
||||
const handleListLocationChange = (_: any, e: OptionOnSelectData): void =>
|
||||
{
|
||||
if (e.optionValue === "popup" && contextAction !== "open")
|
||||
setContextAction("open");
|
||||
|
||||
setListLocation(e.optionValue as ListLocationType);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className={ cls.section }>
|
||||
<Checkbox
|
||||
label={ i18n.t("options_page.general.options.always_show_toolbars") }
|
||||
checked={ alwaysShowToolbars ?? false }
|
||||
onChange={ (_, e) => setAlwaysShowToolbars(e.checked as boolean) } />
|
||||
<Checkbox
|
||||
label={ i18n.t("options_page.general.options.include_pinned") }
|
||||
checked={ !ignorePinned }
|
||||
onChange={ (_, e) => setIgnorePinned(!e.checked) } />
|
||||
<Checkbox
|
||||
label={ i18n.t("options_page.general.options.show_delete_prompt") }
|
||||
checked={ deletePrompt ?? false }
|
||||
onChange={ (_, e) => setDeletePrompt(e.checked as boolean) } />
|
||||
<Checkbox
|
||||
label={ i18n.t("options_page.general.options.show_badge") }
|
||||
checked={ showBadge ?? false }
|
||||
onChange={ (_, e) => setShowBadge(e.checked as boolean) } />
|
||||
<Checkbox
|
||||
label={ i18n.t("options_page.general.options.show_notification") }
|
||||
checked={ notifyOnSave ?? false }
|
||||
onChange={ (_, e) => setNotifyOnSave(e.checked as boolean) } />
|
||||
<Checkbox
|
||||
label={ i18n.t("options_page.general.options.unload_tabs") }
|
||||
checked={ dismissOnLoad ?? false }
|
||||
onChange={ (_, e) => setDismissOnLoad(e.checked as boolean) } />
|
||||
</section>
|
||||
|
||||
<Field label={ i18n.t("options_page.general.options.list_locations.title") }>
|
||||
<Dropdown
|
||||
value={ listLocation ? listLocationOptions[listLocation] : "" }
|
||||
selectedOptions={ [listLocation ?? ""] }
|
||||
onOptionSelect={ handleListLocationChange }
|
||||
>
|
||||
{ Object.entries(listLocationOptions).map(([key, value]) =>
|
||||
<Option key={ key } value={ key }>
|
||||
{ value }
|
||||
</Option>
|
||||
) }
|
||||
</Dropdown>
|
||||
</Field>
|
||||
|
||||
<Field label={ i18n.t("options_page.general.options.icon_action.title") }>
|
||||
<Dropdown
|
||||
value={ contextAction ? contextActionOptions[contextAction] : "" }
|
||||
selectedOptions={ [contextAction ?? ""] }
|
||||
onOptionSelect={ (_, e) => setContextAction(e.optionValue as ContextActionType) }
|
||||
disabled={ listLocation === "popup" }
|
||||
>
|
||||
{ Object.entries(contextActionOptions).map(([key, value]) =>
|
||||
key === "context" && import.meta.env.FIREFOX
|
||||
? <></> :
|
||||
<Option key={ key } value={ key }>
|
||||
{ value }
|
||||
</Option>
|
||||
) }
|
||||
</Dropdown>
|
||||
</Field>
|
||||
|
||||
{ !import.meta.env.FIREFOX &&
|
||||
<Button icon={ <KeyCommand20Regular /> } onClick={ openShortcutsPage } className={ cls.buttonFix }>
|
||||
{ i18n.t("options_page.general.options.change_shortcuts") }
|
||||
</Button>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
type ListLocationType = SettingsValue<"listLocation">;
|
||||
type ContextActionType = SettingsValue<"contextAction">;
|
||||
|
||||
const listLocationOptions: Record<ListLocationType, string> =
|
||||
{
|
||||
"sidebar": i18n.t("options_page.general.options.list_locations.options.sidebar"),
|
||||
"popup": i18n.t("options_page.general.options.list_locations.options.popup"),
|
||||
"tab": i18n.t("options_page.general.options.list_locations.options.tab"),
|
||||
"pinned": i18n.t("options_page.general.options.list_locations.options.pinned")
|
||||
};
|
||||
|
||||
const contextActionOptions: Record<ContextActionType, string> =
|
||||
{
|
||||
"action": i18n.t("options_page.general.options.icon_action.options.action"),
|
||||
"context": i18n.t("options_page.general.options.icon_action.options.context"),
|
||||
"open": i18n.t("options_page.general.options.icon_action.options.open")
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
import { useDialog } from "@/contexts/DialogProvider";
|
||||
import useStorageInfo from "@/hooks/useStorageInfo";
|
||||
import { Button, Field, MessageBar, MessageBarBody, MessageBarTitle, ProgressBar } from "@fluentui/react-components";
|
||||
import { ArrowDownload20Regular, ArrowUpload20Regular } from "@fluentui/react-icons";
|
||||
import { useOptionsStyles } from "../hooks/useOptionsStyles";
|
||||
import exportData from "../utils/exportData";
|
||||
import importData from "../utils/importData";
|
||||
|
||||
export default function StorageSection(): React.ReactElement
|
||||
{
|
||||
const { bytesInUse, storageQuota, usedStorageRatio } = useStorageInfo();
|
||||
const [importResult, setImportResult] = useState<boolean | null>(null);
|
||||
|
||||
const dialog = useDialog();
|
||||
const cls = useOptionsStyles();
|
||||
|
||||
const handleImport = (): void =>
|
||||
dialog.pushPrompt({
|
||||
title: i18n.t("options_page.storage.import_prompt.title"),
|
||||
confirmText: i18n.t("options_page.storage.import_prompt.proceed"),
|
||||
onConfirm: () => importData().then(setImportResult),
|
||||
content: (
|
||||
<MessageBar intent="warning">
|
||||
<MessageBarBody>
|
||||
<MessageBarTitle>{ i18n.t("options_page.storage.import_prompt.warning_title") }</MessageBarTitle>
|
||||
|
||||
{ i18n.t("options_page.storage.import_prompt.warning_text") }
|
||||
</MessageBarBody>
|
||||
</MessageBar>
|
||||
)
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Field
|
||||
label={ i18n.t("options_page.storage.capacity.title") }
|
||||
hint={ i18n.t("options_page.storage.capacity.description", [(bytesInUse / 1024).toFixed(1), storageQuota / 1024]) }
|
||||
validationState={ usedStorageRatio >= 0.8 ? "error" : undefined }
|
||||
>
|
||||
<ProgressBar value={ usedStorageRatio } thickness="large" />
|
||||
</Field>
|
||||
|
||||
<div className={ cls.horizontalButtons }>
|
||||
<Button icon={ <ArrowDownload20Regular /> } onClick={ exportData }>
|
||||
{ i18n.t("options_page.storage.export") }
|
||||
</Button>
|
||||
<Button icon={ <ArrowUpload20Regular /> } onClick={ handleImport }>
|
||||
{ i18n.t("options_page.storage.import") }
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{ importResult !== null &&
|
||||
<MessageBar intent={ importResult ? "success" : "error" }>
|
||||
<MessageBarBody>
|
||||
{ importResult === true ?
|
||||
i18n.t("options_page.storage.import_results.success") :
|
||||
i18n.t("options_page.storage.import_results.error")
|
||||
}
|
||||
</MessageBarBody>
|
||||
</MessageBar>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user