From 757d28c235c458b302d03ab19312bacf45fc5808 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Mon, 29 Sep 2025 22:08:05 -0700 Subject: [PATCH] License and updater Cargo features (#258) --- .github/workflows/release.yml | 2 +- src-tauri/Cargo.toml | 5 +- src-tauri/src/lib.rs | 36 ++++++---- src-web/components/CargoFeature.tsx | 20 ++++++ src-web/components/LicenseBadge.tsx | 9 +++ src-web/components/Settings/Settings.tsx | 20 ++++-- .../components/Settings/SettingsGeneral.tsx | 65 ++++++++++--------- .../components/Settings/SettingsLicense.tsx | 9 +++ src-web/components/SettingsDropdown.tsx | 1 + src-web/components/core/Tabs/Tabs.tsx | 5 ++ src-web/lib/appInfo.ts | 4 ++ 11 files changed, 124 insertions(+), 52 deletions(-) create mode 100644 src-web/components/CargoFeature.tsx diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 655cd7e0..99982807 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -114,4 +114,4 @@ jobs: releaseBody: '[Changelog __VERSION__](https://yaak.app/blog/__VERSION__)' releaseDraft: true prerelease: false - args: ${{ matrix.args }} + args: '${{ matrix.args }} --features "updater license"' diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 737bb50e..d806e0ab 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -32,6 +32,9 @@ strip = true # Automatically strip symbols from the binary. [features] cargo-clippy = [] +default = [] +updater = [] +license = ["yaak-license"] [build-dependencies] tauri-build = { version = "2.4.1", features = [] } @@ -74,7 +77,7 @@ yaak-fonts = { workspace = true } yaak-git = { path = "yaak-git" } yaak-grpc = { path = "yaak-grpc" } yaak-http = { workspace = true } -yaak-license = { path = "yaak-license" } +yaak-license = { path = "yaak-license", optional = true } yaak-mac-window = { path = "yaak-mac-window" } yaak-models = { workspace = true } yaak-plugins = { workspace = true } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 47a985d4..6f757f62 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -73,6 +73,8 @@ struct AppMetaData { name: String, app_data_dir: String, app_log_dir: String, + feature_updater: bool, + feature_license: bool, } #[tauri::command] @@ -85,6 +87,8 @@ async fn cmd_metadata(app_handle: AppHandle) -> YaakResult { name: app_handle.package_info().name.to_string(), app_data_dir: app_data_dir.to_string_lossy().to_string(), app_log_dir: app_log_dir.to_string_lossy().to_string(), + feature_license: cfg!(feature = "license"), + feature_updater: cfg!(feature = "updater"), }) } @@ -1254,7 +1258,6 @@ pub fn run() { .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_fs::init()) - .plugin(yaak_license::init()) .plugin(yaak_mac_window::init()) .plugin(yaak_models::init()) .plugin(yaak_plugins::init()) @@ -1264,6 +1267,11 @@ pub fn run() { .plugin(yaak_ws::init()) .plugin(yaak_sync::init()); + #[cfg(feature = "license")] + { + builder = builder.plugin(yaak_license::init()); + } + builder .setup(|app| { { @@ -1380,19 +1388,21 @@ pub fn run() { label, .. } => { - let w = app_handle.get_webview_window(&label).unwrap(); - let h = app_handle.clone(); - - // Run update check whenever the window is focused - tauri::async_runtime::spawn(async move { - if w.db().get_settings().autoupdate { - let val: State<'_, Mutex> = h.state(); - let update_mode = get_update_mode(&w).await.unwrap(); - if let Err(e) = val.lock().await.maybe_check(&w, update_mode).await { - warn!("Failed to check for updates {e:?}"); + if cfg!(feature = "updater") { + // Run update check whenever the window is focused + let w = app_handle.get_webview_window(&label).unwrap(); + let h = app_handle.clone(); + tauri::async_runtime::spawn(async move { + if w.db().get_settings().autoupdate { + let val: State<'_, Mutex> = h.state(); + let update_mode = get_update_mode(&w).await.unwrap(); + if let Err(e) = val.lock().await.maybe_check(&w, update_mode).await + { + warn!("Failed to check for updates {e:?}"); + } }; - }; - }); + }); + } let h = app_handle.clone(); tauri::async_runtime::spawn(async move { diff --git a/src-web/components/CargoFeature.tsx b/src-web/components/CargoFeature.tsx new file mode 100644 index 00000000..f53a8737 --- /dev/null +++ b/src-web/components/CargoFeature.tsx @@ -0,0 +1,20 @@ +import type { ReactNode } from 'react'; +import { appInfo } from '../lib/appInfo'; + +interface Props { + children: ReactNode; + feature: 'updater' | 'license'; +} + +const featureMap: Record = { + updater: appInfo.featureUpdater, + license: appInfo.featureLicense, +}; + +export function CargoFeature({ children, feature }: Props) { + if (featureMap[feature]) { + return <>{children}; + } else { + return null; + } +} diff --git a/src-web/components/LicenseBadge.tsx b/src-web/components/LicenseBadge.tsx index af275312..a8a2b838 100644 --- a/src-web/components/LicenseBadge.tsx +++ b/src-web/components/LicenseBadge.tsx @@ -5,6 +5,7 @@ import { useAtomValue } from 'jotai'; import type { ReactNode } from 'react'; import { openSettings } from '../commands/openSettings'; import { appInfo } from '../lib/appInfo'; +import { CargoFeature } from './CargoFeature'; import { BadgeButton } from './core/BadgeButton'; import type { ButtonProps } from './core/Button'; @@ -19,6 +20,14 @@ const details: Record< }; export function LicenseBadge() { + return ( + + + + ); +} + +function LicenseBadgeCmp() { const { check } = useLicense(); const settings = useAtomValue(settingsAtom); diff --git a/src-web/components/Settings/Settings.tsx b/src-web/components/Settings/Settings.tsx index 3af1ead7..1e33a5a4 100644 --- a/src-web/components/Settings/Settings.tsx +++ b/src-web/components/Settings/Settings.tsx @@ -4,8 +4,10 @@ import { type } from '@tauri-apps/plugin-os'; import classNames from 'classnames'; import React, { useState } from 'react'; import { useKeyPressEvent } from 'react-use'; +import { appInfo } from '../../lib/appInfo'; import { capitalize } from '../../lib/capitalize'; import { HStack } from '../core/Stacks'; +import type { TabItem } from '../core/Tabs/Tabs'; import { TabContent, Tabs } from '../core/Tabs/Tabs'; import { HeaderSize } from '../HeaderSize'; import { SettingsInterface } from './SettingsInterface'; @@ -72,21 +74,27 @@ export default function Settings({ hide }: Props) { tabListClassName="min-w-[10rem] bg-surface x-theme-sidebar border-r border-border pl-3" label="Settings" onChangeValue={setTab} - tabs={tabs.map((value) => ({ value, label: capitalize(value) }))} + tabs={tabs.map( + (value): TabItem => ({ + value, + label: capitalize(value), + hidden: !appInfo.featureLicense && value === TAB_LICENSE, + }), + )} > - + - + - + - + - + diff --git a/src-web/components/Settings/SettingsGeneral.tsx b/src-web/components/Settings/SettingsGeneral.tsx index 7a95f9a8..e88d4ab7 100644 --- a/src-web/components/Settings/SettingsGeneral.tsx +++ b/src-web/components/Settings/SettingsGeneral.tsx @@ -6,6 +6,7 @@ import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace'; import { appInfo } from '../../lib/appInfo'; import { useCheckForUpdates } from '../../hooks/useCheckForUpdates'; import { revealInFinderText } from '../../lib/reveal'; +import { CargoFeature } from '../CargoFeature'; import { Checkbox } from '../core/Checkbox'; import { Heading } from '../core/Heading'; import { IconButton } from '../core/IconButton'; @@ -26,43 +27,45 @@ export function SettingsGeneral() { return ( -
+ +
+ patchModel(settings, { updateChannel })} + labelClassName="w-[14rem]" + onChange={(v) => patchModel(settings, { autoupdate: v === 'auto' })} options={[ - { label: 'Stable', value: 'stable' }, - { label: 'Beta (more frequent)', value: 'beta' }, + { label: 'Automatic', value: 'auto' }, + { label: 'Manual', value: 'manual' }, ]} /> - checkForUpdates.mutateAsync()} - /> -
- - + +
+ ); +} + +function SettingsLicenseCmp() { const { check, activate, deactivate } = useLicense(); const [key, setKey] = useState(''); const [activateFormVisible, toggleActivateFormVisible] = useToggle(false); diff --git a/src-web/components/SettingsDropdown.tsx b/src-web/components/SettingsDropdown.tsx index f0b07cfe..5b615218 100644 --- a/src-web/components/SettingsDropdown.tsx +++ b/src-web/components/SettingsDropdown.tsx @@ -74,6 +74,7 @@ export function SettingsDropdown() { { label: 'Check for Updates', leftSlot: , + hidden: !appInfo.featureUpdater, onSelect: () => checkForUpdates.mutate(), }, { diff --git a/src-web/components/core/Tabs/Tabs.tsx b/src-web/components/core/Tabs/Tabs.tsx index 9f853320..30383f7c 100644 --- a/src-web/components/core/Tabs/Tabs.tsx +++ b/src-web/components/core/Tabs/Tabs.tsx @@ -10,6 +10,7 @@ export type TabItem = | { value: string; label: string; + hidden?: boolean; rightSlot?: ReactNode; } | { @@ -97,6 +98,10 @@ export function Tabs({ )} > {tabs.map((t) => { + if ('hidden' in t && t.hidden) { + return null; + } + const isActive = t.value === value; const btnClassName = classNames( 'h-sm flex items-center rounded whitespace-nowrap', diff --git a/src-web/lib/appInfo.ts b/src-web/lib/appInfo.ts index 4f4e5264..a44276de 100644 --- a/src-web/lib/appInfo.ts +++ b/src-web/lib/appInfo.ts @@ -8,9 +8,13 @@ export interface AppInfo { appDataDir: string; appLogDir: string; identifier: string; + featureLicense: boolean; + featureUpdater: boolean; } export const appInfo = { ...(await invokeCmd('cmd_metadata')), identifier: await getIdentifier(), } as AppInfo; + +console.log('App info', appInfo);