# Conflicts: # frontend/package-lock.json # frontend/package.jsonpull/2182/head
commit
080710e7e1
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,34 @@
|
|||||||
|
import { useLanguages } from "@/apis/hooks";
|
||||||
|
import { Selector, SelectorProps } from "@/components/inputs";
|
||||||
|
import { useSelectorOptions } from "@/utilities";
|
||||||
|
import { FunctionComponent, useMemo } from "react";
|
||||||
|
|
||||||
|
interface LanguageSelectorProps
|
||||||
|
extends Omit<SelectorProps<Language.Server>, "options" | "getkey"> {
|
||||||
|
enabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LanguageSelector: FunctionComponent<LanguageSelectorProps> = ({
|
||||||
|
enabled = false,
|
||||||
|
...selector
|
||||||
|
}) => {
|
||||||
|
const { data } = useLanguages();
|
||||||
|
|
||||||
|
const filteredData = useMemo(() => {
|
||||||
|
if (enabled) {
|
||||||
|
return data?.filter((value) => value.enabled);
|
||||||
|
} else {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}, [data, enabled]);
|
||||||
|
|
||||||
|
const options = useSelectorOptions(
|
||||||
|
filteredData ?? [],
|
||||||
|
(value) => value.name,
|
||||||
|
(value) => value.code3
|
||||||
|
);
|
||||||
|
|
||||||
|
return <Selector {...options} searchable {...selector}></Selector>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LanguageSelector;
|
@ -1,4 +1,4 @@
|
|||||||
export * from "./inputs";
|
|
||||||
export { default as Search } from "./Search";
|
export { default as Search } from "./Search";
|
||||||
|
export * from "./inputs";
|
||||||
export * from "./tables";
|
export * from "./tables";
|
||||||
export { default as Toolbox } from "./toolbox";
|
export { default as Toolbox } from "./toolbox";
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
import ReactDOM from "react-dom";
|
import { createRoot } from "react-dom/client";
|
||||||
import { useRoutes } from "react-router-dom";
|
import { Router } from "./Router";
|
||||||
import { AllProviders } from "./providers";
|
import { AllProviders } from "./providers";
|
||||||
import { useRouteItems } from "./Router";
|
|
||||||
|
|
||||||
const RouteApp = () => {
|
const container = document.getElementById("root");
|
||||||
const items = useRouteItems();
|
|
||||||
|
|
||||||
return useRoutes(items);
|
if (container === null) {
|
||||||
};
|
Error("Cannot initialize app, root not found");
|
||||||
|
} else {
|
||||||
ReactDOM.render(
|
const root = createRoot(container);
|
||||||
|
root.render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<AllProviders>
|
<AllProviders>
|
||||||
<RouteApp />
|
<Router />
|
||||||
</AllProviders>
|
</AllProviders>
|
||||||
</StrictMode>,
|
</StrictMode>
|
||||||
document.getElementById("root")
|
);
|
||||||
);
|
}
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
export * from "./hooks";
|
|
||||||
export { default as ModalsProvider } from "./ModalsProvider";
|
export { default as ModalsProvider } from "./ModalsProvider";
|
||||||
export { default as withModal } from "./WithModal";
|
export { default as withModal } from "./WithModal";
|
||||||
|
export * from "./hooks";
|
||||||
|
@ -0,0 +1,196 @@
|
|||||||
|
import {
|
||||||
|
decodeEqualData,
|
||||||
|
encodeEqualData,
|
||||||
|
LanguageEqualData,
|
||||||
|
LanguageEqualImmediateData,
|
||||||
|
} from "@/pages/Settings/Languages/equals";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Equals Parser", () => {
|
||||||
|
it("should parse from string correctly", () => {
|
||||||
|
interface TestData {
|
||||||
|
text: string;
|
||||||
|
expected: LanguageEqualImmediateData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function testParsedResult(
|
||||||
|
text: string,
|
||||||
|
expected: LanguageEqualImmediateData
|
||||||
|
) {
|
||||||
|
const result = decodeEqualData(text);
|
||||||
|
|
||||||
|
if (result === undefined) {
|
||||||
|
expect(false, `Cannot parse '${text}' as language equal data`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(
|
||||||
|
result,
|
||||||
|
`${text} does not match with the expected equal data`
|
||||||
|
).toStrictEqual(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
const testValues: TestData[] = [
|
||||||
|
{
|
||||||
|
text: "spa-MX:spa",
|
||||||
|
expected: {
|
||||||
|
source: {
|
||||||
|
content: "spa-MX",
|
||||||
|
hi: false,
|
||||||
|
forced: false,
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
content: "spa",
|
||||||
|
hi: false,
|
||||||
|
forced: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "zho@hi:zht",
|
||||||
|
expected: {
|
||||||
|
source: {
|
||||||
|
content: "zho",
|
||||||
|
hi: true,
|
||||||
|
forced: false,
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
content: "zht",
|
||||||
|
hi: false,
|
||||||
|
forced: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "es-MX@forced:es-MX",
|
||||||
|
expected: {
|
||||||
|
source: {
|
||||||
|
content: "es-MX",
|
||||||
|
hi: false,
|
||||||
|
forced: true,
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
content: "es-MX",
|
||||||
|
hi: false,
|
||||||
|
forced: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "en:en@hi",
|
||||||
|
expected: {
|
||||||
|
source: {
|
||||||
|
content: "en",
|
||||||
|
hi: false,
|
||||||
|
forced: false,
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
content: "en",
|
||||||
|
hi: true,
|
||||||
|
forced: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
testValues.forEach((data) => {
|
||||||
|
testParsedResult(data.text, data.expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should encode to string correctly", () => {
|
||||||
|
interface TestData {
|
||||||
|
source: LanguageEqualData;
|
||||||
|
expected: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const testValues: TestData[] = [
|
||||||
|
{
|
||||||
|
source: {
|
||||||
|
source: {
|
||||||
|
content: {
|
||||||
|
name: "Abkhazian",
|
||||||
|
code2: "ab",
|
||||||
|
code3: "abk",
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
hi: false,
|
||||||
|
forced: false,
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
content: {
|
||||||
|
name: "Aragonese",
|
||||||
|
code2: "an",
|
||||||
|
code3: "arg",
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
hi: false,
|
||||||
|
forced: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "abk:arg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: {
|
||||||
|
source: {
|
||||||
|
content: {
|
||||||
|
name: "Abkhazian",
|
||||||
|
code2: "ab",
|
||||||
|
code3: "abk",
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
hi: true,
|
||||||
|
forced: false,
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
content: {
|
||||||
|
name: "Aragonese",
|
||||||
|
code2: "an",
|
||||||
|
code3: "arg",
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
hi: false,
|
||||||
|
forced: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "abk@hi:arg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: {
|
||||||
|
source: {
|
||||||
|
content: {
|
||||||
|
name: "Abkhazian",
|
||||||
|
code2: "ab",
|
||||||
|
code3: "abk",
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
hi: false,
|
||||||
|
forced: true,
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
content: {
|
||||||
|
name: "Aragonese",
|
||||||
|
code2: "an",
|
||||||
|
code3: "arg",
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
hi: false,
|
||||||
|
forced: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "abk@forced:arg",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function testEncodeResult({ source, expected }: TestData) {
|
||||||
|
const encoded = encodeEqualData(source);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
encoded,
|
||||||
|
`Encoded result '${encoded}' is not matched to '${expected}'`
|
||||||
|
).toEqual(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
testValues.forEach(testEncodeResult);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,365 @@
|
|||||||
|
import { useLanguages } from "@/apis/hooks";
|
||||||
|
import { Action, SimpleTable } from "@/components";
|
||||||
|
import LanguageSelector from "@/components/bazarr/LanguageSelector";
|
||||||
|
import { languageEqualsKey } from "@/pages/Settings/keys";
|
||||||
|
import { useFormActions } from "@/pages/Settings/utilities/FormValues";
|
||||||
|
import { useSettingValue } from "@/pages/Settings/utilities/hooks";
|
||||||
|
import { LOG } from "@/utilities/console";
|
||||||
|
import { faEquals, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { Button, Checkbox } from "@mantine/core";
|
||||||
|
import { FunctionComponent, useCallback, useMemo } from "react";
|
||||||
|
import { Column } from "react-table";
|
||||||
|
|
||||||
|
interface GenericEqualTarget<T> {
|
||||||
|
content: T;
|
||||||
|
hi: boolean;
|
||||||
|
forced: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LanguageEqualGenericData<T> {
|
||||||
|
source: GenericEqualTarget<T>;
|
||||||
|
target: GenericEqualTarget<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LanguageEqualImmediateData =
|
||||||
|
LanguageEqualGenericData<Language.CodeType>;
|
||||||
|
|
||||||
|
export type LanguageEqualData = LanguageEqualGenericData<Language.Server>;
|
||||||
|
|
||||||
|
function decodeEqualTarget(
|
||||||
|
text: string
|
||||||
|
): GenericEqualTarget<Language.CodeType> | undefined {
|
||||||
|
const [code, decoration] = text.split("@");
|
||||||
|
|
||||||
|
if (code.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const forced = decoration === "forced";
|
||||||
|
const hi = decoration === "hi";
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: code,
|
||||||
|
forced,
|
||||||
|
hi,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeEqualData(
|
||||||
|
text: string
|
||||||
|
): LanguageEqualImmediateData | undefined {
|
||||||
|
const [first, second] = text.split(":");
|
||||||
|
|
||||||
|
const source = decodeEqualTarget(first);
|
||||||
|
const target = decodeEqualTarget(second);
|
||||||
|
|
||||||
|
if (source === undefined || target === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeEqualTarget(data: GenericEqualTarget<Language.Server>): string {
|
||||||
|
let text = data.content.code3;
|
||||||
|
if (data.hi) {
|
||||||
|
text += "@hi";
|
||||||
|
} else if (data.forced) {
|
||||||
|
text += "@forced";
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encodeEqualData(data: LanguageEqualData): string {
|
||||||
|
const source = encodeEqualTarget(data.source);
|
||||||
|
const target = encodeEqualTarget(data.target);
|
||||||
|
|
||||||
|
return `${source}:${target}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useLatestLanguageEquals(): LanguageEqualData[] {
|
||||||
|
const { data } = useLanguages();
|
||||||
|
|
||||||
|
const latest = useSettingValue<string[]>(languageEqualsKey);
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() =>
|
||||||
|
latest
|
||||||
|
?.map(decodeEqualData)
|
||||||
|
.map((parsed) => {
|
||||||
|
if (parsed === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = data?.find(
|
||||||
|
(value) => value.code3 === parsed.source.content
|
||||||
|
);
|
||||||
|
const target = data?.find(
|
||||||
|
(value) => value.code3 === parsed.target.content
|
||||||
|
);
|
||||||
|
|
||||||
|
if (source === undefined || target === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
source: { ...parsed.source, content: source },
|
||||||
|
target: { ...parsed.target, content: target },
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((v): v is LanguageEqualData => v !== undefined) ?? [],
|
||||||
|
[data, latest]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EqualsTableProps {}
|
||||||
|
|
||||||
|
const EqualsTable: FunctionComponent<EqualsTableProps> = () => {
|
||||||
|
const { data: languages } = useLanguages();
|
||||||
|
const canAdd = languages !== undefined;
|
||||||
|
|
||||||
|
const equals = useLatestLanguageEquals();
|
||||||
|
|
||||||
|
const { setValue } = useFormActions();
|
||||||
|
|
||||||
|
const setEquals = useCallback(
|
||||||
|
(values: LanguageEqualData[]) => {
|
||||||
|
const encodedValues = values.map(encodeEqualData);
|
||||||
|
|
||||||
|
LOG("info", "updating language equals data", values);
|
||||||
|
setValue(encodedValues, languageEqualsKey);
|
||||||
|
},
|
||||||
|
[setValue]
|
||||||
|
);
|
||||||
|
|
||||||
|
const add = useCallback(() => {
|
||||||
|
if (languages === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const enabled = languages.find((value) => value.enabled);
|
||||||
|
|
||||||
|
if (enabled === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newValue: LanguageEqualData[] = [
|
||||||
|
...equals,
|
||||||
|
{
|
||||||
|
source: {
|
||||||
|
content: enabled,
|
||||||
|
hi: false,
|
||||||
|
forced: false,
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
content: enabled,
|
||||||
|
hi: false,
|
||||||
|
forced: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
setEquals(newValue);
|
||||||
|
}, [equals, languages, setEquals]);
|
||||||
|
|
||||||
|
const update = useCallback(
|
||||||
|
(index: number, value: LanguageEqualData) => {
|
||||||
|
if (index < 0 || index >= equals.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newValue: LanguageEqualData[] = [...equals];
|
||||||
|
|
||||||
|
newValue[index] = { ...value };
|
||||||
|
setEquals(newValue);
|
||||||
|
},
|
||||||
|
[equals, setEquals]
|
||||||
|
);
|
||||||
|
|
||||||
|
const remove = useCallback(
|
||||||
|
(index: number) => {
|
||||||
|
if (index < 0 || index >= equals.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newValue: LanguageEqualData[] = [...equals];
|
||||||
|
|
||||||
|
newValue.splice(index, 1);
|
||||||
|
|
||||||
|
setEquals(newValue);
|
||||||
|
},
|
||||||
|
[equals, setEquals]
|
||||||
|
);
|
||||||
|
|
||||||
|
const columns = useMemo<Column<LanguageEqualData>[]>(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
Header: "Source",
|
||||||
|
id: "source-lang",
|
||||||
|
accessor: "source",
|
||||||
|
Cell: ({ value: { content }, row }) => {
|
||||||
|
return (
|
||||||
|
<LanguageSelector
|
||||||
|
enabled
|
||||||
|
value={content}
|
||||||
|
onChange={(result) => {
|
||||||
|
if (result !== null) {
|
||||||
|
update(row.index, {
|
||||||
|
...row.original,
|
||||||
|
source: { ...row.original.source, content: result },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></LanguageSelector>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "source-hi",
|
||||||
|
accessor: "source",
|
||||||
|
Cell: ({ value: { hi }, row }) => {
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
label="HI"
|
||||||
|
checked={hi}
|
||||||
|
onChange={({ currentTarget: { checked } }) => {
|
||||||
|
update(row.index, {
|
||||||
|
...row.original,
|
||||||
|
source: {
|
||||||
|
...row.original.source,
|
||||||
|
hi: checked,
|
||||||
|
forced: checked ? false : row.original.source.forced,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
></Checkbox>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "source-forced",
|
||||||
|
accessor: "source",
|
||||||
|
Cell: ({ value: { forced }, row }) => {
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
label="Forced"
|
||||||
|
checked={forced}
|
||||||
|
onChange={({ currentTarget: { checked } }) => {
|
||||||
|
update(row.index, {
|
||||||
|
...row.original,
|
||||||
|
source: {
|
||||||
|
...row.original.source,
|
||||||
|
forced: checked,
|
||||||
|
hi: checked ? false : row.original.source.hi,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
></Checkbox>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "equal-icon",
|
||||||
|
Cell: () => {
|
||||||
|
return <FontAwesomeIcon icon={faEquals} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: "Target",
|
||||||
|
id: "target-lang",
|
||||||
|
accessor: "target",
|
||||||
|
Cell: ({ value: { content }, row }) => {
|
||||||
|
return (
|
||||||
|
<LanguageSelector
|
||||||
|
enabled
|
||||||
|
value={content}
|
||||||
|
onChange={(result) => {
|
||||||
|
if (result !== null) {
|
||||||
|
update(row.index, {
|
||||||
|
...row.original,
|
||||||
|
target: { ...row.original.target, content: result },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></LanguageSelector>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "target-hi",
|
||||||
|
accessor: "target",
|
||||||
|
Cell: ({ value: { hi }, row }) => {
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
label="HI"
|
||||||
|
checked={hi}
|
||||||
|
onChange={({ currentTarget: { checked } }) => {
|
||||||
|
update(row.index, {
|
||||||
|
...row.original,
|
||||||
|
target: {
|
||||||
|
...row.original.target,
|
||||||
|
hi: checked,
|
||||||
|
forced: checked ? false : row.original.target.forced,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
></Checkbox>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "target-forced",
|
||||||
|
accessor: "target",
|
||||||
|
Cell: ({ value: { forced }, row }) => {
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
label="Forced"
|
||||||
|
checked={forced}
|
||||||
|
onChange={({ currentTarget: { checked } }) => {
|
||||||
|
update(row.index, {
|
||||||
|
...row.original,
|
||||||
|
target: {
|
||||||
|
...row.original.target,
|
||||||
|
forced: checked,
|
||||||
|
hi: checked ? false : row.original.target.hi,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
></Checkbox>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "action",
|
||||||
|
accessor: "target",
|
||||||
|
Cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<Action
|
||||||
|
label="Remove"
|
||||||
|
icon={faTrash}
|
||||||
|
color="red"
|
||||||
|
onClick={() => remove(row.index)}
|
||||||
|
></Action>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[remove, update]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SimpleTable data={equals} columns={columns}></SimpleTable>
|
||||||
|
<Button fullWidth disabled={!canAdd} color="light" onClick={add}>
|
||||||
|
{canAdd ? "Add Equal" : "No Enabled Languages"}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EqualsTable;
|
@ -1,41 +1,9 @@
|
|||||||
// A workaround of built-in hooks in React-Router v6
|
// A workaround of built-in hooks in React-Router v6
|
||||||
// https://gist.github.com/rmorse/426ffcc579922a82749934826fa9f743
|
// https://gist.github.com/rmorse/426ffcc579922a82749934826fa9f743
|
||||||
|
|
||||||
import type { Blocker, History, Transition } from "history";
|
import { unstable_usePrompt as useUnstablePrompt } from "react-router-dom";
|
||||||
import { useContext, useEffect } from "react";
|
|
||||||
// eslint-disable-next-line camelcase
|
|
||||||
import { UNSAFE_NavigationContext } from "react-router-dom";
|
|
||||||
|
|
||||||
export function useBlocker(blocker: Blocker, when = true) {
|
|
||||||
const navigator = useContext(UNSAFE_NavigationContext).navigator as History;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!when) return;
|
|
||||||
|
|
||||||
const unblock = navigator.block((tx: Transition) => {
|
|
||||||
const autoUnblockingTx = {
|
|
||||||
...tx,
|
|
||||||
retry() {
|
|
||||||
// Automatically unblock the transition so it can play all the way
|
|
||||||
// through before retrying it. TODO: Figure out how to re-enable
|
|
||||||
// this block if the transition is cancelled for some reason.
|
|
||||||
unblock();
|
|
||||||
tx.retry();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
blocker(autoUnblockingTx);
|
|
||||||
});
|
|
||||||
|
|
||||||
return unblock;
|
|
||||||
}, [navigator, blocker, when]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Replace with Mantine's confirmation modal
|
// TODO: Replace with Mantine's confirmation modal
|
||||||
export function usePrompt(when: boolean, message: string) {
|
export function usePrompt(when: boolean, message: string) {
|
||||||
useBlocker((tx) => {
|
useUnstablePrompt({ when, message });
|
||||||
if (window.confirm(message)) {
|
|
||||||
tx.retry();
|
|
||||||
}
|
|
||||||
}, when);
|
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue