License and updater Cargo features (#258)

This commit is contained in:
Gregory Schier
2025-09-29 22:08:05 -07:00
committed by GitHub
parent 6c79c1ef3f
commit 757d28c235
11 changed files with 124 additions and 52 deletions

View File

@@ -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"'

View File

@@ -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 }

View File

@@ -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<AppMetaData> {
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<YaakUpdater>> = 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<YaakUpdater>> = 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 {

View File

@@ -0,0 +1,20 @@
import type { ReactNode } from 'react';
import { appInfo } from '../lib/appInfo';
interface Props {
children: ReactNode;
feature: 'updater' | 'license';
}
const featureMap: Record<Props['feature'], boolean> = {
updater: appInfo.featureUpdater,
license: appInfo.featureLicense,
};
export function CargoFeature({ children, feature }: Props) {
if (featureMap[feature]) {
return <>{children}</>;
} else {
return null;
}
}

View File

@@ -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 (
<CargoFeature feature="license">
<LicenseBadgeCmp />
</CargoFeature>
);
}
function LicenseBadgeCmp() {
const { check } = useLicense();
const settings = useAtomValue(settingsAtom);

View File

@@ -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,
}),
)}
>
<TabContent value={TAB_GENERAL} className="overflow-y-auto h-full p-8">
<TabContent value={TAB_GENERAL} className="overflow-y-auto h-full px-8 !py-4">
<SettingsGeneral />
</TabContent>
<TabContent value={TAB_INTERFACE} className="overflow-y-auto h-full p-8">
<TabContent value={TAB_INTERFACE} className="overflow-y-auto h-full px-8 !py-4">
<SettingsInterface />
</TabContent>
<TabContent value={TAB_THEME} className="overflow-y-auto h-full p-8">
<TabContent value={TAB_THEME} className="overflow-y-auto h-full px-8 !py-4">
<SettingsTheme />
</TabContent>
<TabContent value={TAB_PLUGINS} className="h-full grid grid-rows-1 p-8">
<TabContent value={TAB_PLUGINS} className="h-full grid grid-rows-1 px-8 !py-4">
<SettingsPlugins />
</TabContent>
<TabContent value={TAB_PROXY} className="overflow-y-auto h-full p-8!">
<TabContent value={TAB_PROXY} className="overflow-y-auto h-full px-8 !py-4">
<SettingsProxy />
</TabContent>
<TabContent value={TAB_LICENSE} className="overflow-y-auto h-full px-8 !py-4">

View File

@@ -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 (
<VStack space={1.5} className="mb-4">
<div className="grid grid-cols-[minmax(0,1fr)_auto] gap-1">
<CargoFeature feature="updater">
<div className="grid grid-cols-[minmax(0,1fr)_auto] gap-1">
<Select
name="updateChannel"
label="Update Channel"
labelPosition="left"
labelClassName="w-[14rem]"
size="sm"
value={settings.updateChannel}
onChange={(updateChannel) => patchModel(settings, { updateChannel })}
options={[
{ label: 'Stable', value: 'stable' },
{ label: 'Beta (more frequent)', value: 'beta' },
]}
/>
<IconButton
variant="border"
size="sm"
title="Check for updates"
icon="refresh"
spin={checkForUpdates.isPending}
onClick={() => checkForUpdates.mutateAsync()}
/>
</div>
<Select
name="updateChannel"
label="Update Channel"
name="autoupdate"
value={settings.autoupdate ? 'auto' : 'manual'}
label="Update Behavior"
labelPosition="left"
labelClassName="w-[14rem]"
size="sm"
value={settings.updateChannel}
onChange={(updateChannel) => 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' },
]}
/>
<IconButton
variant="border"
size="sm"
title="Check for updates"
icon="refresh"
spin={checkForUpdates.isPending}
onClick={() => checkForUpdates.mutateAsync()}
/>
</div>
<Select
name="autoupdate"
value={settings.autoupdate ? 'auto' : 'manual'}
label="Update Behavior"
labelPosition="left"
size="sm"
labelClassName="w-[14rem]"
onChange={(v) => patchModel(settings, { autoupdate: v === 'auto' })}
options={[
{ label: 'Automatic', value: 'auto' },
{ label: 'Manual', value: 'manual' },
]}
/>
</CargoFeature>
<Select
name="switchWorkspaceBehavior"

View File

@@ -4,6 +4,7 @@ import { differenceInDays } from 'date-fns';
import React, { useState } from 'react';
import { useToggle } from '../../hooks/useToggle';
import { pluralizeCount } from '../../lib/pluralize';
import { CargoFeature } from '../CargoFeature';
import { Banner } from '../core/Banner';
import { Button } from '../core/Button';
import { Icon } from '../core/Icon';
@@ -13,6 +14,14 @@ import { HStack, VStack } from '../core/Stacks';
import { LocalImage } from '../LocalImage';
export function SettingsLicense() {
return (
<CargoFeature feature="license">
<SettingsLicenseCmp />
</CargoFeature>
);
}
function SettingsLicenseCmp() {
const { check, activate, deactivate } = useLicense();
const [key, setKey] = useState<string>('');
const [activateFormVisible, toggleActivateFormVisible] = useToggle(false);

View File

@@ -74,6 +74,7 @@ export function SettingsDropdown() {
{
label: 'Check for Updates',
leftSlot: <Icon icon="update" />,
hidden: !appInfo.featureUpdater,
onSelect: () => checkForUpdates.mutate(),
},
{

View File

@@ -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',

View File

@@ -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);