Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions src/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,38 @@ async clbQueryOpdsStatus() : Promise<Result<OpdsServiceStatus, OpdsStatusError>>
else return { status: "error", error: e as any };
}
},
async clbQueryOpdsCredentialStatus() : Promise<Result<OpdsCredentialStatus, OpdsStatusError>> {
try {
return { status: "ok", data: await TAURI_INVOKE("clb_query_opds_credential_status") };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async clbCmdConfigureOpdsCredentials(username: string, password: string) : Promise<Result<OpdsCredentialStatus, OpdsStatusError>> {
try {
return { status: "ok", data: await TAURI_INVOKE("clb_cmd_configure_opds_credentials", { username, password }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async clbCmdGenerateOpdsCredentials(username: string) : Promise<Result<GeneratedOpdsCredentials, OpdsStatusError>> {
try {
return { status: "ok", data: await TAURI_INVOKE("clb_cmd_generate_opds_credentials", { username }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async clbCmdClearOpdsCredentials() : Promise<Result<OpdsCredentialStatus, OpdsStatusError>> {
try {
return { status: "ok", data: await TAURI_INVOKE("clb_cmd_clear_opds_credentials") };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async clbCmdOpenSettings() : Promise<void> {
await TAURI_INVOKE("clb_cmd_open_settings");
}
Expand Down Expand Up @@ -434,6 +466,7 @@ export type DetectionSource =
* Calibre's default `~/Calibre Library` folder.
*/
"default-folder"
export type GeneratedOpdsCredentials = { username: string; password: string }
/**
* Book identifiers, such as ISBN, DOI, Google Books ID, etc.
*/
Expand Down Expand Up @@ -523,13 +556,14 @@ export type LocalOrRemoteUrl = { kind: LocalOrRemote; url: string; local_path: s
export type MetadataProvider = "hardcover" | "loc" | "dnb" | "k10plus" | "openlibrary"
export type NewAuthor = { name: string; sortable_name: string | null }
export type OpdsBindTarget = { type: "allLocalNetworks" } | { type: "interface"; id: string }
export type OpdsErrorCode = "invalidPort" | "libraryNotReady" | "configurationConflict" | "interfaceUnavailable" | "interfaceEnumerationFailed" | "portUnavailable" | "listenerFailed" | "unexpected"
export type OpdsCredentialStatus = { configured: boolean; username: string | null }
export type OpdsErrorCode = "invalidPort" | "libraryNotReady" | "configurationConflict" | "interfaceUnavailable" | "interfaceEnumerationFailed" | "portUnavailable" | "listenerFailed" | "invalidCredentials" | "credentialsRequired" | "credentialStorageFailed" | "unexpected"
export type OpdsInterfaceKind = "lan" | "vpn" | "loopback" | "other"
export type OpdsInterfaceState = "up" | "down"
export type OpdsLifecycleState = "stopped" | "starting" | "running" | "waitingForInterface" | "error" | "stopping"
export type OpdsNetworkInterface = { id: string; label: string; kind: OpdsInterfaceKind; state: OpdsInterfaceState; addresses: string[]; shareable: boolean }
export type OpdsServiceStatus = { state: OpdsLifecycleState; target: OpdsBindTarget | null; port: number | null; activeLibraryId: string | null; urls: string[]; error: OpdsStatusError | null }
export type OpdsStartConfig = { target: OpdsBindTarget; port: number }
export type OpdsStartConfig = { target: OpdsBindTarget; port: number; authenticationEnabled: boolean }
export type OpdsStatusError = { code: OpdsErrorCode; message: string }
export type ProviderStatus = { provider: MetadataProvider; is_valid: boolean; message: string }
export type RemoteFile = { url: string }
Expand Down
13 changes: 13 additions & 0 deletions src/components/icons/F7AntennaRadiowavesLeftRight.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { SVGProps } from "react";

export function F7AntennaRadiowavesLeftRight(props: SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 56 56" width={56} height={56} {...props}>
<title>Sharing</title>
<path
fill="currentColor"
d="M28 33.2a5.2 5.2 0 1 0 0-10.4 5.2 5.2 0 0 0 0 10.4m-9.1 6.5a2 2 0 0 0 2.8-2.8 12.6 12.6 0 0 1 0-17.8 2 2 0 1 0-2.8-2.8 16.6 16.6 0 0 0 0 23.4m18.2 0a16.6 16.6 0 0 0 0-23.4 2 2 0 1 0-2.8 2.8 12.6 12.6 0 0 1 0 17.8 2 2 0 0 0 2.8 2.8M12.3 46.3a2 2 0 0 0 2.8-2.8 21.9 21.9 0 0 1 0-31 2 2 0 1 0-2.8-2.8 25.9 25.9 0 0 0 0 36.6m31.4 0a25.9 25.9 0 0 0 0 0-36.6 2 2 0 1 0-2.8 2.8 21.9 21.9 0 0 1 0 31 2 2 0 0 0 2.8 2.8"
/>
</svg>
);
}
14 changes: 13 additions & 1 deletion src/components/organisms/SettingsPanes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import type { KeyboardEvent, ReactNode } from "react";
import { useCallback, useId, useRef, useState } from "react";
import { commands } from "@/bindings";
import { F7BookFill } from "@/components/icons/F7BookFill";
import { F7AntennaRadiowavesLeftRight } from "@/components/icons/F7AntennaRadiowavesLeftRight";
import { F7Gear } from "@/components/icons/F7Gear";
import { FluentLibraryFilled } from "@/components/icons/FluentLibraryFilled";
import type { AddLibraryResult } from "@/components/molecules/SwitchLibraryForm";
import { SwitchLibraryForm } from "@/components/molecules/SwitchLibraryForm";
import classes from "@/components/organisms/SettingsPanes.module.css";
import { SharingSettings } from "@/components/organisms/SharingSettings";
import { Button, SegmentedControl, Switch, TextInput } from "@/components/ui";
import { ADOPT_INVALID_ERROR } from "@/lib/first-run/machine";
import { useAppUpdates } from "@/lib/hooks/use-app-updates";
Expand All @@ -19,7 +21,12 @@ import { createLibrary, setActiveLibrary } from "@/stores/settings/actions";
import { useAnySourceEnabled } from "@/stores/settings/metadata-providers";
import { useSettings } from "@/stores/settings/store";

const SETTINGS_TABS = ["general", "library", "integrations"] as const;
const SETTINGS_TABS = [
"general",
"library",
"sharing",
"integrations",
] as const;
type SettingsTab = (typeof SETTINGS_TABS)[number];

const TAB_META: Record<
Expand All @@ -34,6 +41,10 @@ const TAB_META: Record<
label: "Library",
icon: (className) => <FluentLibraryFilled className={className} />,
},
sharing: {
label: "Sharing",
icon: (className) => <F7AntennaRadiowavesLeftRight className={className} />,
},
integrations: {
label: "Metadata",
icon: (className) => <F7BookFill className={className} />,
Expand Down Expand Up @@ -145,6 +156,7 @@ export const SettingsPanes = ({ onRequestClose }: SettingsPanesProps) => {
<div className={classes.panelInner}>
{activeTab === "general" && <GeneralTab />}
{activeTab === "library" && <LibraryTab closeSettings={close} />}
{activeTab === "sharing" && <SharingSettings />}
{activeTab === "integrations" && <IntegrationsTab />}
</div>
</div>
Expand Down
Loading
Loading