mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-25 02:08:28 +02:00
License and updater Cargo features (#258)
This commit is contained in:
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -114,4 +114,4 @@ jobs:
|
|||||||
releaseBody: '[Changelog __VERSION__](https://yaak.app/blog/__VERSION__)'
|
releaseBody: '[Changelog __VERSION__](https://yaak.app/blog/__VERSION__)'
|
||||||
releaseDraft: true
|
releaseDraft: true
|
||||||
prerelease: false
|
prerelease: false
|
||||||
args: ${{ matrix.args }}
|
args: '${{ matrix.args }} --features "updater license"'
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ strip = true # Automatically strip symbols from the binary.
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
cargo-clippy = []
|
cargo-clippy = []
|
||||||
|
default = []
|
||||||
|
updater = []
|
||||||
|
license = ["yaak-license"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "2.4.1", features = [] }
|
tauri-build = { version = "2.4.1", features = [] }
|
||||||
@@ -74,7 +77,7 @@ yaak-fonts = { workspace = true }
|
|||||||
yaak-git = { path = "yaak-git" }
|
yaak-git = { path = "yaak-git" }
|
||||||
yaak-grpc = { path = "yaak-grpc" }
|
yaak-grpc = { path = "yaak-grpc" }
|
||||||
yaak-http = { workspace = true }
|
yaak-http = { workspace = true }
|
||||||
yaak-license = { path = "yaak-license" }
|
yaak-license = { path = "yaak-license", optional = true }
|
||||||
yaak-mac-window = { path = "yaak-mac-window" }
|
yaak-mac-window = { path = "yaak-mac-window" }
|
||||||
yaak-models = { workspace = true }
|
yaak-models = { workspace = true }
|
||||||
yaak-plugins = { workspace = true }
|
yaak-plugins = { workspace = true }
|
||||||
|
|||||||
@@ -73,6 +73,8 @@ struct AppMetaData {
|
|||||||
name: String,
|
name: String,
|
||||||
app_data_dir: String,
|
app_data_dir: String,
|
||||||
app_log_dir: String,
|
app_log_dir: String,
|
||||||
|
feature_updater: bool,
|
||||||
|
feature_license: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -85,6 +87,8 @@ async fn cmd_metadata(app_handle: AppHandle) -> YaakResult<AppMetaData> {
|
|||||||
name: app_handle.package_info().name.to_string(),
|
name: app_handle.package_info().name.to_string(),
|
||||||
app_data_dir: app_data_dir.to_string_lossy().to_string(),
|
app_data_dir: app_data_dir.to_string_lossy().to_string(),
|
||||||
app_log_dir: app_log_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_dialog::init())
|
||||||
.plugin(tauri_plugin_os::init())
|
.plugin(tauri_plugin_os::init())
|
||||||
.plugin(tauri_plugin_fs::init())
|
.plugin(tauri_plugin_fs::init())
|
||||||
.plugin(yaak_license::init())
|
|
||||||
.plugin(yaak_mac_window::init())
|
.plugin(yaak_mac_window::init())
|
||||||
.plugin(yaak_models::init())
|
.plugin(yaak_models::init())
|
||||||
.plugin(yaak_plugins::init())
|
.plugin(yaak_plugins::init())
|
||||||
@@ -1264,6 +1267,11 @@ pub fn run() {
|
|||||||
.plugin(yaak_ws::init())
|
.plugin(yaak_ws::init())
|
||||||
.plugin(yaak_sync::init());
|
.plugin(yaak_sync::init());
|
||||||
|
|
||||||
|
#[cfg(feature = "license")]
|
||||||
|
{
|
||||||
|
builder = builder.plugin(yaak_license::init());
|
||||||
|
}
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
{
|
{
|
||||||
@@ -1380,19 +1388,21 @@ pub fn run() {
|
|||||||
label,
|
label,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let w = app_handle.get_webview_window(&label).unwrap();
|
if cfg!(feature = "updater") {
|
||||||
let h = app_handle.clone();
|
// Run update check whenever the window is focused
|
||||||
|
let w = app_handle.get_webview_window(&label).unwrap();
|
||||||
// Run update check whenever the window is focused
|
let h = app_handle.clone();
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
if w.db().get_settings().autoupdate {
|
if w.db().get_settings().autoupdate {
|
||||||
let val: State<'_, Mutex<YaakUpdater>> = h.state();
|
let val: State<'_, Mutex<YaakUpdater>> = h.state();
|
||||||
let update_mode = get_update_mode(&w).await.unwrap();
|
let update_mode = get_update_mode(&w).await.unwrap();
|
||||||
if let Err(e) = val.lock().await.maybe_check(&w, update_mode).await {
|
if let Err(e) = val.lock().await.maybe_check(&w, update_mode).await
|
||||||
warn!("Failed to check for updates {e:?}");
|
{
|
||||||
|
warn!("Failed to check for updates {e:?}");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
let h = app_handle.clone();
|
let h = app_handle.clone();
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
|
|||||||
20
src-web/components/CargoFeature.tsx
Normal file
20
src-web/components/CargoFeature.tsx
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import { useAtomValue } from 'jotai';
|
|||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { openSettings } from '../commands/openSettings';
|
import { openSettings } from '../commands/openSettings';
|
||||||
import { appInfo } from '../lib/appInfo';
|
import { appInfo } from '../lib/appInfo';
|
||||||
|
import { CargoFeature } from './CargoFeature';
|
||||||
import { BadgeButton } from './core/BadgeButton';
|
import { BadgeButton } from './core/BadgeButton';
|
||||||
import type { ButtonProps } from './core/Button';
|
import type { ButtonProps } from './core/Button';
|
||||||
|
|
||||||
@@ -19,6 +20,14 @@ const details: Record<
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function LicenseBadge() {
|
export function LicenseBadge() {
|
||||||
|
return (
|
||||||
|
<CargoFeature feature="license">
|
||||||
|
<LicenseBadgeCmp />
|
||||||
|
</CargoFeature>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function LicenseBadgeCmp() {
|
||||||
const { check } = useLicense();
|
const { check } = useLicense();
|
||||||
const settings = useAtomValue(settingsAtom);
|
const settings = useAtomValue(settingsAtom);
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import { type } from '@tauri-apps/plugin-os';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useKeyPressEvent } from 'react-use';
|
import { useKeyPressEvent } from 'react-use';
|
||||||
|
import { appInfo } from '../../lib/appInfo';
|
||||||
import { capitalize } from '../../lib/capitalize';
|
import { capitalize } from '../../lib/capitalize';
|
||||||
import { HStack } from '../core/Stacks';
|
import { HStack } from '../core/Stacks';
|
||||||
|
import type { TabItem } from '../core/Tabs/Tabs';
|
||||||
import { TabContent, Tabs } from '../core/Tabs/Tabs';
|
import { TabContent, Tabs } from '../core/Tabs/Tabs';
|
||||||
import { HeaderSize } from '../HeaderSize';
|
import { HeaderSize } from '../HeaderSize';
|
||||||
import { SettingsInterface } from './SettingsInterface';
|
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"
|
tabListClassName="min-w-[10rem] bg-surface x-theme-sidebar border-r border-border pl-3"
|
||||||
label="Settings"
|
label="Settings"
|
||||||
onChangeValue={setTab}
|
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 />
|
<SettingsGeneral />
|
||||||
</TabContent>
|
</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 />
|
<SettingsInterface />
|
||||||
</TabContent>
|
</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 />
|
<SettingsTheme />
|
||||||
</TabContent>
|
</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 />
|
<SettingsPlugins />
|
||||||
</TabContent>
|
</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 />
|
<SettingsProxy />
|
||||||
</TabContent>
|
</TabContent>
|
||||||
<TabContent value={TAB_LICENSE} className="overflow-y-auto h-full px-8 !py-4">
|
<TabContent value={TAB_LICENSE} className="overflow-y-auto h-full px-8 !py-4">
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
|||||||
import { appInfo } from '../../lib/appInfo';
|
import { appInfo } from '../../lib/appInfo';
|
||||||
import { useCheckForUpdates } from '../../hooks/useCheckForUpdates';
|
import { useCheckForUpdates } from '../../hooks/useCheckForUpdates';
|
||||||
import { revealInFinderText } from '../../lib/reveal';
|
import { revealInFinderText } from '../../lib/reveal';
|
||||||
|
import { CargoFeature } from '../CargoFeature';
|
||||||
import { Checkbox } from '../core/Checkbox';
|
import { Checkbox } from '../core/Checkbox';
|
||||||
import { Heading } from '../core/Heading';
|
import { Heading } from '../core/Heading';
|
||||||
import { IconButton } from '../core/IconButton';
|
import { IconButton } from '../core/IconButton';
|
||||||
@@ -26,43 +27,45 @@ export function SettingsGeneral() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack space={1.5} className="mb-4">
|
<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
|
<Select
|
||||||
name="updateChannel"
|
name="autoupdate"
|
||||||
label="Update Channel"
|
value={settings.autoupdate ? 'auto' : 'manual'}
|
||||||
|
label="Update Behavior"
|
||||||
labelPosition="left"
|
labelPosition="left"
|
||||||
labelClassName="w-[14rem]"
|
|
||||||
size="sm"
|
size="sm"
|
||||||
value={settings.updateChannel}
|
labelClassName="w-[14rem]"
|
||||||
onChange={(updateChannel) => patchModel(settings, { updateChannel })}
|
onChange={(v) => patchModel(settings, { autoupdate: v === 'auto' })}
|
||||||
options={[
|
options={[
|
||||||
{ label: 'Stable', value: 'stable' },
|
{ label: 'Automatic', value: 'auto' },
|
||||||
{ label: 'Beta (more frequent)', value: 'beta' },
|
{ label: 'Manual', value: 'manual' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
</CargoFeature>
|
||||||
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' },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
name="switchWorkspaceBehavior"
|
name="switchWorkspaceBehavior"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { differenceInDays } from 'date-fns';
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useToggle } from '../../hooks/useToggle';
|
import { useToggle } from '../../hooks/useToggle';
|
||||||
import { pluralizeCount } from '../../lib/pluralize';
|
import { pluralizeCount } from '../../lib/pluralize';
|
||||||
|
import { CargoFeature } from '../CargoFeature';
|
||||||
import { Banner } from '../core/Banner';
|
import { Banner } from '../core/Banner';
|
||||||
import { Button } from '../core/Button';
|
import { Button } from '../core/Button';
|
||||||
import { Icon } from '../core/Icon';
|
import { Icon } from '../core/Icon';
|
||||||
@@ -13,6 +14,14 @@ import { HStack, VStack } from '../core/Stacks';
|
|||||||
import { LocalImage } from '../LocalImage';
|
import { LocalImage } from '../LocalImage';
|
||||||
|
|
||||||
export function SettingsLicense() {
|
export function SettingsLicense() {
|
||||||
|
return (
|
||||||
|
<CargoFeature feature="license">
|
||||||
|
<SettingsLicenseCmp />
|
||||||
|
</CargoFeature>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SettingsLicenseCmp() {
|
||||||
const { check, activate, deactivate } = useLicense();
|
const { check, activate, deactivate } = useLicense();
|
||||||
const [key, setKey] = useState<string>('');
|
const [key, setKey] = useState<string>('');
|
||||||
const [activateFormVisible, toggleActivateFormVisible] = useToggle(false);
|
const [activateFormVisible, toggleActivateFormVisible] = useToggle(false);
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ export function SettingsDropdown() {
|
|||||||
{
|
{
|
||||||
label: 'Check for Updates',
|
label: 'Check for Updates',
|
||||||
leftSlot: <Icon icon="update" />,
|
leftSlot: <Icon icon="update" />,
|
||||||
|
hidden: !appInfo.featureUpdater,
|
||||||
onSelect: () => checkForUpdates.mutate(),
|
onSelect: () => checkForUpdates.mutate(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export type TabItem =
|
|||||||
| {
|
| {
|
||||||
value: string;
|
value: string;
|
||||||
label: string;
|
label: string;
|
||||||
|
hidden?: boolean;
|
||||||
rightSlot?: ReactNode;
|
rightSlot?: ReactNode;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
@@ -97,6 +98,10 @@ export function Tabs({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{tabs.map((t) => {
|
{tabs.map((t) => {
|
||||||
|
if ('hidden' in t && t.hidden) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const isActive = t.value === value;
|
const isActive = t.value === value;
|
||||||
const btnClassName = classNames(
|
const btnClassName = classNames(
|
||||||
'h-sm flex items-center rounded whitespace-nowrap',
|
'h-sm flex items-center rounded whitespace-nowrap',
|
||||||
|
|||||||
@@ -8,9 +8,13 @@ export interface AppInfo {
|
|||||||
appDataDir: string;
|
appDataDir: string;
|
||||||
appLogDir: string;
|
appLogDir: string;
|
||||||
identifier: string;
|
identifier: string;
|
||||||
|
featureLicense: boolean;
|
||||||
|
featureUpdater: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const appInfo = {
|
export const appInfo = {
|
||||||
...(await invokeCmd('cmd_metadata')),
|
...(await invokeCmd('cmd_metadata')),
|
||||||
identifier: await getIdentifier(),
|
identifier: await getIdentifier(),
|
||||||
} as AppInfo;
|
} as AppInfo;
|
||||||
|
|
||||||
|
console.log('App info', appInfo);
|
||||||
|
|||||||
Reference in New Issue
Block a user