Skip to content

Commit 3571cb6

Browse files
authored
Merge pull request #930 from epicenter-md/command-to-allow-you-to-run-transformations-on-selections
feat: add transform-clipboard feature with two command modes and keyboard shortcuts
2 parents 9a8a584 + c8bb754 commit 3571cb6

File tree

86 files changed

+1518
-211
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+1518
-211
lines changed

apps/whispering/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@repo/whispering",
3-
"version": "7.5.5",
3+
"version": "7.6.0",
44
"description": "",
55
"exports": {
66
"./commands": "./src/lib/commands.ts"

apps/whispering/src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/whispering/src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "whispering"
3-
version = "7.5.5"
3+
version = "7.6.0"
44
description = "A Tauri App"
55
authors = ["you"]
66
repository = "https://github.com/epicenter-md/epicenter"

apps/whispering/src-tauri/capabilities/default.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,16 @@
1515
"os:default",
1616
"core:window:allow-hide",
1717
"core:window:allow-show",
18+
"core:window:allow-close",
1819
"core:window:allow-set-always-on-top",
1920
"core:window:allow-set-size",
21+
"core:webview:allow-create-webview-window",
22+
"core:webview:allow-webview-close",
23+
"core:event:allow-emit",
24+
"core:event:allow-listen",
2025
"process:allow-exit",
2126
"notification:default",
27+
"clipboard-manager:allow-read-text",
2228
"clipboard-manager:allow-write-text",
2329
"fs:allow-read",
2430
"fs:allow-write",
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"$schema": "../gen/schemas/desktop-schema.json",
3+
"identifier": "transformation-picker-capability",
4+
"description": "Minimal capability for transformation picker window",
5+
"windows": ["transformation-picker"],
6+
"permissions": [
7+
"core:default",
8+
"core:window:allow-close",
9+
"core:window:allow-hide",
10+
"core:event:allow-emit",
11+
"core:event:allow-listen",
12+
"clipboard-manager:allow-read-text",
13+
"clipboard-manager:allow-write-text",
14+
"notification:default",
15+
"notification:allow-is-permission-granted",
16+
"notification:allow-request-permission",
17+
"notification:allow-show"
18+
]
19+
}

apps/whispering/src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
}
3838
},
3939
"productName": "Whispering",
40-
"version": "7.5.5",
40+
"version": "7.6.0",
4141
"identifier": "com.bradenwong.whispering",
4242
"plugins": {
4343
"updater": {

apps/whispering/src/lib/commands.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,18 @@ export const commands = [
5757
on: 'Pressed',
5858
callback: () => rpc.commands.toggleVadRecording.execute(undefined),
5959
},
60+
{
61+
id: 'openTransformationPicker',
62+
title: 'Open transformation picker',
63+
on: 'Pressed',
64+
callback: () => rpc.commands.openTransformationPicker.execute(undefined),
65+
},
66+
{
67+
id: 'runTransformationOnClipboard',
68+
title: 'Run transformation on clipboard',
69+
on: 'Pressed',
70+
callback: () => rpc.commands.runTransformationOnClipboard.execute(undefined),
71+
},
6072
] as const satisfies SatisfiedCommand[];
6173

6274
export type Command = (typeof commands)[number];
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<script lang="ts">
2+
import { Badge } from '@repo/ui/badge';
3+
import * as Command from '@repo/ui/command';
4+
import { Kbd } from '@repo/ui/kbd';
5+
import { rpc } from '$lib/query';
6+
import type { Transformation } from '$lib/services/db';
7+
import { createQuery } from '@tanstack/svelte-query';
8+
import { LayersIcon } from '@lucide/svelte';
9+
import { PLATFORM_TYPE } from '$lib/constants/platform';
10+
import { onMount } from 'svelte';
11+
12+
const transformationsQuery = createQuery(
13+
rpc.db.transformations.getAll.options,
14+
);
15+
16+
const transformations = $derived(transformationsQuery.data ?? []);
17+
18+
const isMac = PLATFORM_TYPE === 'macos';
19+
const modifierKey = isMac ? '' : 'Ctrl';
20+
21+
let inputElement = $state<HTMLInputElement | null>(null);
22+
23+
let {
24+
onSelect,
25+
onSelectManageTransformations,
26+
placeholder,
27+
class: className,
28+
}: {
29+
/**
30+
* Called when a transformation is selected from the list.
31+
* Receives the selected transformation object.
32+
*/
33+
onSelect: (transformation: Transformation) => void;
34+
/**
35+
* Called when the "Manage transformations" option is selected.
36+
* Parent components typically close themselves and navigate to /transformations.
37+
*/
38+
onSelectManageTransformations: () => void;
39+
/**
40+
* Placeholder text shown in the search input field.
41+
*/
42+
placeholder: string;
43+
/**
44+
* Optional class name to apply to the command root
45+
*/
46+
class?: string;
47+
} = $props();
48+
49+
// Auto-focus search input on mount (component is destroyed/recreated each time popover opens)
50+
onMount(() => {
51+
inputElement?.focus();
52+
});
53+
54+
// Keyboard shortcut handler for Cmd/Ctrl + 0-9
55+
onMount(() => {
56+
function handleKeyDown(e: KeyboardEvent) {
57+
const isCmdOrCtrl = isMac ? e.metaKey : e.ctrlKey;
58+
59+
if (isCmdOrCtrl && e.key >= '0' && e.key <= '9') {
60+
e.preventDefault();
61+
const index = e.key === '0' ? 9 : parseInt(e.key) - 1; // 0 maps to 10th item
62+
63+
if (transformations[index]) {
64+
onSelect(transformations[index]);
65+
}
66+
}
67+
}
68+
69+
window.addEventListener('keydown', handleKeyDown);
70+
return () => window.removeEventListener('keydown', handleKeyDown);
71+
});
72+
</script>
73+
74+
{#snippet renderTransformationIdTitle(transformation: Transformation)}
75+
<div class="flex items-center gap-2">
76+
<Badge variant="id" class="shrink-0 max-w-16 truncate">
77+
{transformation.id}
78+
</Badge>
79+
<span class="font-medium truncate">
80+
{transformation.title}
81+
</span>
82+
</div>
83+
{/snippet}
84+
85+
<Command.Root loop class={className}>
86+
<Command.Input {placeholder} bind:ref={inputElement} />
87+
<Command.Empty>No transformation found.</Command.Empty>
88+
<Command.Group class="overflow-y-auto max-h-[400px]">
89+
{#each transformations as transformation, index (transformation.id)}
90+
<Command.Item
91+
value="${transformation.id} - ${transformation.title} - ${transformation.description}"
92+
onSelect={() => onSelect(transformation)}
93+
class="flex items-center justify-between gap-2 p-2"
94+
>
95+
<div class="flex flex-col min-w-0">
96+
{@render renderTransformationIdTitle(transformation)}
97+
{#if transformation.description}
98+
<span class="text-sm text-muted-foreground line-clamp-2">
99+
{transformation.description}
100+
</span>
101+
{/if}
102+
</div>
103+
{#if index < 10}
104+
<Kbd class="ml-auto shrink-0">
105+
{modifierKey}{index === 9 ? '0' : index + 1}
106+
</Kbd>
107+
{/if}
108+
</Command.Item>
109+
{/each}
110+
</Command.Group>
111+
<Command.Item
112+
value="Manage transformations"
113+
onSelect={onSelectManageTransformations}
114+
class="rounded-none p-2 bg-muted/50 text-muted-foreground"
115+
>
116+
<LayersIcon class="size-4 mx-2.5" />
117+
Manage transformations
118+
</Command.Item>
119+
</Command.Root>

apps/whispering/src/lib/components/settings/CompressionBody.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import { settings } from '$lib/stores/settings.svelte';
88
import { cn } from '@repo/ui/utils';
99
import { RotateCcw, AlertTriangle } from '@lucide/svelte';
10-
import { isCompressionRecommended } from '../../../routes/+layout/check-ffmpeg';
10+
import { isCompressionRecommended } from '../../../routes/(app)/_layout-utils/check-ffmpeg';
1111
import { createQuery } from '@tanstack/svelte-query';
1212
import { rpc } from '$lib/query';
1313
import * as Alert from '@repo/ui/alert';

apps/whispering/src/lib/components/settings/selectors/CompressionSelector.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import { useCombobox } from '@repo/ui/hooks';
99
import { settings } from '$lib/stores/settings.svelte';
1010
import { cn } from '@repo/ui/utils';
11-
import { isCompressionRecommended } from '../../../../routes/+layout/check-ffmpeg';
11+
import { isCompressionRecommended } from '../../../../routes/(app)/_layout-utils/check-ffmpeg';
1212
import { PackageIcon, SettingsIcon } from '@lucide/svelte';
1313
1414
let { class: className }: { class?: string } = $props();

0 commit comments

Comments
 (0)