feat: Add desktop Open In button (#350)
* feat: add OpenInAppButton and macOS path opener * feat: filter installed apps on macOS and use in OpenInAppButton * feat: fetch and display macOS app icons in OpenInAppButton * feat(desktop): cache and fetch installed macOS apps * feat: add force refresh and retry for installed apps * feat(OpenInAppButton): add Copy Path action in dropdown header * feat: enhance caching mechanism for apps discovery --------- Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
Bohdan Triapitsyn
parent
9534e3d016
commit
b432437b02
@@ -451,6 +451,7 @@ export interface SettingsPayload {
|
||||
diffViewMode?: 'single' | 'stacked';
|
||||
directoryShowHidden?: boolean;
|
||||
filesViewShowGitignored?: boolean;
|
||||
openInAppId?: string;
|
||||
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ export type DesktopSettings = {
|
||||
defaultVariant?: string;
|
||||
defaultAgent?: string;
|
||||
defaultGitIdentityId?: string; // ''/undefined = unset, 'global' or profile id
|
||||
openInAppId?: string;
|
||||
autoCreateWorktree?: boolean;
|
||||
queueModeEnabled?: boolean;
|
||||
gitmojiEnabled?: boolean;
|
||||
@@ -322,3 +323,140 @@ export const restartToApplyUpdate = async (): Promise<boolean> => {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const openDesktopPath = async (path: string, app?: string | null): Promise<boolean> => {
|
||||
if (!isTauriShell() || !isDesktopLocalOriginActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const trimmed = path?.trim();
|
||||
if (!trimmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const tauri = (window as unknown as { __TAURI__?: TauriGlobal }).__TAURI__;
|
||||
await tauri?.core?.invoke?.('desktop_open_path', {
|
||||
path: trimmed,
|
||||
app: typeof app === 'string' && app.trim().length > 0 ? app.trim() : undefined,
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.warn('Failed to open path (tauri)', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const filterInstalledDesktopApps = async (apps: string[]): Promise<string[]> => {
|
||||
if (!isTauriShell() || !isDesktopLocalOriginActive()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const candidate = Array.isArray(apps) ? apps.filter((value) => typeof value === 'string') : [];
|
||||
if (candidate.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const tauri = (window as unknown as { __TAURI__?: TauriGlobal }).__TAURI__;
|
||||
const result = await tauri?.core?.invoke?.('desktop_filter_installed_apps', {
|
||||
apps: candidate,
|
||||
});
|
||||
return Array.isArray(result) ? result.filter((value) => typeof value === 'string') : [];
|
||||
} catch (error) {
|
||||
console.warn('Failed to check installed apps (tauri)', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchDesktopAppIcons = async (apps: string[]): Promise<Record<string, string>> => {
|
||||
if (!isTauriShell() || !isDesktopLocalOriginActive()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const candidate = Array.isArray(apps) ? apps.filter((value) => typeof value === 'string') : [];
|
||||
if (candidate.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
const tauri = (window as unknown as { __TAURI__?: TauriGlobal }).__TAURI__;
|
||||
const result = await tauri?.core?.invoke?.('desktop_fetch_app_icons', {
|
||||
apps: candidate,
|
||||
});
|
||||
if (!Array.isArray(result)) {
|
||||
return {};
|
||||
}
|
||||
const map: Record<string, string> = {};
|
||||
for (const entry of result) {
|
||||
if (!entry || typeof entry !== 'object') continue;
|
||||
const candidateEntry = entry as { app?: unknown; data_url?: unknown };
|
||||
if (typeof candidateEntry.app !== 'string' || typeof candidateEntry.data_url !== 'string') continue;
|
||||
map[candidateEntry.app] = candidateEntry.data_url;
|
||||
}
|
||||
return map;
|
||||
} catch (error) {
|
||||
console.warn('Failed to fetch installed app icons (tauri)', error);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
export type InstalledDesktopAppInfo = {
|
||||
name: string;
|
||||
iconDataUrl?: string | null;
|
||||
};
|
||||
|
||||
export type FetchDesktopInstalledAppsResult = {
|
||||
apps: InstalledDesktopAppInfo[];
|
||||
success: boolean;
|
||||
hasCache: boolean;
|
||||
isCacheStale: boolean;
|
||||
};
|
||||
|
||||
export const fetchDesktopInstalledApps = async (
|
||||
apps: string[],
|
||||
force?: boolean
|
||||
): Promise<FetchDesktopInstalledAppsResult> => {
|
||||
if (!isTauriShell() || !isDesktopLocalOriginActive()) {
|
||||
return { apps: [], success: false, hasCache: false, isCacheStale: false };
|
||||
}
|
||||
|
||||
const candidate = Array.isArray(apps) ? apps.filter((value) => typeof value === 'string') : [];
|
||||
if (candidate.length === 0) {
|
||||
return { apps: [], success: true, hasCache: false, isCacheStale: false };
|
||||
}
|
||||
|
||||
try {
|
||||
const tauri = (window as unknown as { __TAURI__?: TauriGlobal }).__TAURI__;
|
||||
const result = await tauri?.core?.invoke?.('desktop_get_installed_apps', {
|
||||
apps: candidate,
|
||||
force: force === true ? true : undefined,
|
||||
});
|
||||
if (!result || typeof result !== 'object') {
|
||||
return { apps: [], success: false, hasCache: false, isCacheStale: false };
|
||||
}
|
||||
const payload = result as { apps?: unknown; hasCache?: unknown; isCacheStale?: unknown };
|
||||
if (!Array.isArray(payload.apps)) {
|
||||
return { apps: [], success: false, hasCache: false, isCacheStale: false };
|
||||
}
|
||||
const installedApps = payload.apps
|
||||
.filter((entry) => entry && typeof entry === 'object')
|
||||
.map((entry) => {
|
||||
const record = entry as { name?: unknown; iconDataUrl?: unknown };
|
||||
return {
|
||||
name: typeof record.name === 'string' ? record.name : '',
|
||||
iconDataUrl: typeof record.iconDataUrl === 'string' ? record.iconDataUrl : null,
|
||||
};
|
||||
})
|
||||
.filter((entry) => entry.name.length > 0);
|
||||
return {
|
||||
apps: installedApps,
|
||||
success: true,
|
||||
hasCache: payload.hasCache === true,
|
||||
isCacheStale: payload.isCacheStale === true,
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn('Failed to fetch installed apps (tauri)', error);
|
||||
return { apps: [], success: false, hasCache: false, isCacheStale: false };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -71,6 +71,11 @@ const persistToLocalStorage = (settings: DesktopSettings) => {
|
||||
if (typeof settings.filesViewShowGitignored === 'boolean') {
|
||||
localStorage.setItem('filesViewShowGitignored', settings.filesViewShowGitignored ? 'true' : 'false');
|
||||
}
|
||||
if (typeof settings.openInAppId === 'string' && settings.openInAppId.length > 0) {
|
||||
localStorage.setItem('openInAppId', settings.openInAppId);
|
||||
} else {
|
||||
localStorage.removeItem('openInAppId');
|
||||
}
|
||||
};
|
||||
|
||||
type PersistApi = {
|
||||
@@ -471,6 +476,9 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => {
|
||||
if (typeof candidate.filesViewShowGitignored === 'boolean') {
|
||||
result.filesViewShowGitignored = candidate.filesViewShowGitignored;
|
||||
}
|
||||
if (typeof candidate.openInAppId === 'string' && candidate.openInAppId.length > 0) {
|
||||
result.openInAppId = candidate.openInAppId;
|
||||
}
|
||||
|
||||
if (typeof candidate.memoryLimitHistorical === 'number' && Number.isFinite(candidate.memoryLimitHistorical)) {
|
||||
result.memoryLimitHistorical = candidate.memoryLimitHistorical;
|
||||
|
||||
Reference in New Issue
Block a user