Skip to content

Commit 828b874

Browse files
Merge pull request #1458 from CapSoftware/fix-selection-overlay
2 parents bb5c789 + 39fab9a commit 828b874

26 files changed

Lines changed: 985 additions & 655 deletions

File tree

apps/desktop/src-tauri/src/captions.rs

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@ use ffmpeg::{
55
format::{self as avformat},
66
software::resampling,
77
};
8+
use futures::StreamExt;
89
use serde::{Deserialize, Serialize};
910
use specta::Type;
1011
use std::fs::File;
1112
use std::io::Read;
1213
use std::path::PathBuf;
1314
use std::process::Command;
1415
use std::sync::Arc;
15-
use tauri::{AppHandle, Emitter, Manager, Window};
16+
use tauri::{AppHandle, Manager};
17+
use tauri_specta::Event;
1618
use tempfile::tempdir;
1719
use tokio::io::AsyncWriteExt;
1820
use tokio::sync::Mutex;
@@ -1775,11 +1777,14 @@ pub async fn save_captions(
17751777
"position".to_string(),
17761778
serde_json::Value::String(settings.position.clone()),
17771779
);
1778-
settings_obj.insert("bold".to_string(), serde_json::Value::Bool(settings.bold));
17791780
settings_obj.insert(
17801781
"italic".to_string(),
17811782
serde_json::Value::Bool(settings.italic),
17821783
);
1784+
settings_obj.insert(
1785+
"fontWeight".to_string(),
1786+
serde_json::Value::Number(serde_json::Number::from(settings.font_weight)),
1787+
);
17831788
settings_obj.insert(
17841789
"outline".to_string(),
17851790
serde_json::Value::Bool(settings.outline),
@@ -1912,18 +1917,19 @@ pub fn parse_captions_json(json: &str) -> Result<cap_project::CaptionsData, Stri
19121917
.and_then(|v| v.as_str())
19131918
.unwrap_or("bottom")
19141919
.to_string();
1915-
let bold = settings_obj
1916-
.get("bold")
1917-
.and_then(|v| v.as_bool())
1918-
.unwrap_or(false);
19191920
let italic = settings_obj
19201921
.get("italic")
19211922
.and_then(|v| v.as_bool())
19221923
.unwrap_or(false);
1924+
let font_weight = settings_obj
1925+
.get("fontWeight")
1926+
.or_else(|| settings_obj.get("font_weight"))
1927+
.and_then(|v| v.as_u64())
1928+
.unwrap_or(700) as u32;
19231929
let outline = settings_obj
19241930
.get("outline")
19251931
.and_then(|v| v.as_bool())
1926-
.unwrap_or(true);
1932+
.unwrap_or(false);
19271933

19281934
let outline_color = settings_obj
19291935
.get("outlineColor")
@@ -1971,8 +1977,8 @@ pub fn parse_captions_json(json: &str) -> Result<cap_project::CaptionsData, Stri
19711977
background_color,
19721978
background_opacity,
19731979
position,
1974-
bold,
19751980
italic,
1981+
font_weight,
19761982
outline,
19771983
outline_color,
19781984
export_with_subtitles,
@@ -2081,16 +2087,11 @@ pub struct DownloadProgress {
20812087
pub message: String,
20822088
}
20832089

2084-
impl DownloadProgress {
2085-
const EVENT_NAME: &'static str = "download-progress";
2086-
}
2087-
20882090
#[tauri::command]
20892091
#[specta::specta]
2090-
#[instrument(skip(window))]
2092+
#[instrument(skip(app))]
20912093
pub async fn download_whisper_model(
20922094
app: AppHandle,
2093-
window: Window,
20942095
model_name: String,
20952096
output_path: String,
20962097
) -> Result<(), String> {
@@ -2128,38 +2129,30 @@ pub async fn download_whisper_model(
21282129
.await
21292130
.map_err(|e| format!("Failed to create file: {e}"))?;
21302131

2131-
let mut downloaded = 0;
2132-
let mut bytes = response
2133-
.bytes()
2134-
.await
2135-
.map_err(|e| format!("Failed to get response bytes: {e}"))?;
2132+
let mut downloaded: u64 = 0;
2133+
let mut stream = response.bytes_stream();
21362134

2137-
const CHUNK_SIZE: usize = 1024 * 1024;
2138-
while !bytes.is_empty() {
2139-
let chunk_size = std::cmp::min(CHUNK_SIZE, bytes.len());
2140-
let chunk = bytes.split_to(chunk_size);
2135+
while let Some(chunk_result) = stream.next().await {
2136+
let chunk = chunk_result.map_err(|e| format!("Error while downloading: {e}"))?;
21412137

21422138
file.write_all(&chunk)
21432139
.await
21442140
.map_err(|e| format!("Error while writing to file: {e}"))?;
21452141

2146-
downloaded += chunk_size as u64;
2142+
downloaded += chunk.len() as u64;
21472143

21482144
let progress = if total_size > 0 {
21492145
(downloaded as f64 / total_size as f64) * 100.0
21502146
} else {
21512147
0.0
21522148
};
21532149

2154-
window
2155-
.emit(
2156-
DownloadProgress::EVENT_NAME,
2157-
DownloadProgress {
2158-
message: format!("Downloading model: {progress:.1}%"),
2159-
progress,
2160-
},
2161-
)
2162-
.map_err(|e| format!("Failed to emit progress: {e}"))?;
2150+
DownloadProgress {
2151+
progress,
2152+
message: format!("Downloading model: {progress:.1}%"),
2153+
}
2154+
.emit(&app)
2155+
.ok();
21632156
}
21642157

21652158
file.flush()

apps/desktop/src-tauri/src/windows.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,10 @@ impl CapWindowId {
182182
pub fn min_size(&self) -> Option<(f64, f64)> {
183183
Some(match self {
184184
Self::Setup => (600.0, 600.0),
185-
Self::Main => (300.0, 360.0),
185+
Self::Main => (310.0, 320.0),
186186
Self::Editor { .. } => (1275.0, 800.0),
187187
Self::ScreenshotEditor { .. } => (800.0, 600.0),
188-
Self::Settings => (600.0, 450.0),
188+
Self::Settings => (600.0, 465.0),
189189
Self::Camera => (200.0, 200.0),
190190
Self::Upgrade => (950.0, 850.0),
191191
Self::ModeSelect => (580.0, 340.0),

apps/desktop/src/routes/(window-chrome)/new-main/CameraSelect.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export default function CameraSelect(props: {
2020
<CameraSelectBase
2121
{...props}
2222
PillComponent={InfoPill}
23-
class="flex flex-row gap-2 items-center px-2 w-full h-9 rounded-lg transition-colors cursor-default disabled:opacity-70 bg-gray-3 disabled:text-gray-11 KSelect"
23+
class="flex flex-row gap-2 items-center px-2 w-full h-10 rounded-lg transition-colors cursor-default disabled:opacity-70 bg-gray-3 disabled:text-gray-11 KSelect"
2424
iconClass="text-gray-10 size-4"
2525
/>
2626
);

apps/desktop/src/routes/(window-chrome)/new-main/MicrophoneSelect.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export default function MicrophoneSelect(props: {
2727
return (
2828
<MicrophoneSelectBase
2929
{...props}
30-
class="flex overflow-hidden relative z-10 flex-row gap-2 items-center px-2 w-full h-9 rounded-lg transition-colors cursor-default disabled:opacity-70 bg-gray-3 disabled:text-gray-11 KSelect"
30+
class="flex overflow-hidden relative z-10 flex-row gap-2 items-center px-2 w-full h-10 rounded-lg transition-colors cursor-default disabled:opacity-70 bg-gray-3 disabled:text-gray-11 KSelect"
3131
levelIndicatorClass="bg-blue-7"
3232
iconClass="text-gray-10 size-4"
3333
PillComponent={InfoPill}

apps/desktop/src/routes/(window-chrome)/new-main/SystemAudio.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import InfoPill from "./InfoPill";
1212
export default function SystemAudio() {
1313
return (
1414
<SystemAudioToggleRoot
15-
class="flex flex-row gap-2 items-center px-2 w-full h-9 rounded-lg transition-colors cursor-default disabled:opacity-70 bg-gray-3 disabled:text-gray-11 KSelect"
15+
class="flex flex-row gap-2 items-center px-2 w-full h-10 rounded-lg transition-colors cursor-default disabled:opacity-70 bg-gray-3 disabled:text-gray-11 KSelect"
1616
PillComponent={InfoPill}
1717
icon={<IconPhMonitorBold class="text-gray-10 size-4" />}
1818
/>

apps/desktop/src/routes/(window-chrome)/new-main/TargetDropdownButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export default function TargetDropdownButton<
2727
aria-expanded={local.expanded ? "true" : "false"}
2828
data-expanded={local.expanded ? "true" : "false"}
2929
class={cx(
30-
"flex h-[3.75rem] w-5 shrink-0 items-center justify-center rounded-lg bg-gray-4 text-gray-12 transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-9 focus-visible:ring-offset-2 focus-visible:ring-offset-gray-1 hover:bg-gray-5",
30+
"flex h-[4rem] w-5 shrink-0 items-center justify-center rounded-lg bg-gray-4 text-gray-12 transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-9 focus-visible:ring-offset-2 focus-visible:ring-offset-gray-1 hover:bg-gray-5",
3131
local.expanded && "bg-gray-5",
3232
local.disabled && "pointer-events-none opacity-60",
3333
local.class,

apps/desktop/src/routes/(window-chrome)/new-main/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ import TargetDropdownButton from "./TargetDropdownButton";
8383
import TargetMenuGrid from "./TargetMenuGrid";
8484
import TargetTypeButton from "./TargetTypeButton";
8585

86-
const WINDOW_SIZE = { width: 290, height: 310 } as const;
86+
const WINDOW_SIZE = { width: 310, height: 320 } as const;
8787

8888
const findCamera = (cameras: CameraInfo[], id: DeviceOrModelID) => {
8989
return cameras.find((c) => {

apps/desktop/src/routes/(window-chrome)/settings/general.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,11 +152,8 @@ function AppearanceSection(props: {
152152

153153
return (
154154
<div class="flex flex-col gap-4">
155-
<div class="flex flex-col pb-4 border-b border-gray-2">
156-
<h2 class="text-lg font-medium text-gray-12">General</h2>
157-
<p class="text-sm text-gray-10">
158-
General settings of your Cap application.
159-
</p>
155+
<div class="flex flex-col border-b border-gray-2">
156+
<h2 class="text-lg font-medium text-gray-12">General Settings</h2>
160157
</div>
161158
<div
162159
class="flex justify-start items-center text-gray-12"

apps/desktop/src/routes/editor/CaptionsTab.tsx

Lines changed: 82 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import { Toggle } from "~/components/Toggle";
1818
import { defaultCaptionSettings } from "~/store/captions";
1919
import type { CaptionSettings } from "~/utils/tauri";
2020
import { commands, events } from "~/utils/tauri";
21+
import IconCapChevronDown from "~icons/cap/chevron-down";
22+
import IconCapCircleCheck from "~icons/cap/circle-check";
2123
import IconLucideCheck from "~icons/lucide/check";
2224
import IconLucideDownload from "~icons/lucide/download";
2325
import { useEditorContext } from "./context";
@@ -31,6 +33,7 @@ import {
3133
Slider,
3234
Subfield,
3335
topLeftAnimateClasses,
36+
topSlideAnimateClasses,
3437
} from "./ui";
3538

3639
interface ModelOption {
@@ -273,16 +276,24 @@ export function CaptionsTab() {
273276
);
274277

275278
createEffect(
276-
on(selectedModel, (model) => {
277-
if (model) localStorage.setItem("selectedTranscriptionModel", model);
278-
}),
279+
on(
280+
selectedModel,
281+
(model) => {
282+
if (model) localStorage.setItem("selectedTranscriptionModel", model);
283+
},
284+
{ defer: true },
285+
),
279286
);
280287

281288
createEffect(
282-
on(selectedLanguage, (language) => {
283-
if (language)
284-
localStorage.setItem("selectedTranscriptionLanguage", language);
285-
}),
289+
on(
290+
selectedLanguage,
291+
(language) => {
292+
if (language)
293+
localStorage.setItem("selectedTranscriptionLanguage", language);
294+
},
295+
{ defer: true },
296+
),
286297
);
287298

288299
const checkModelExists = async (modelName: string) => {
@@ -778,32 +789,71 @@ export function CaptionsTab() {
778789
</div>
779790
</Field>
780791

781-
<Field name="Style Options" icon={<IconCapMessageBubble />}>
782-
<div class="space-y-3">
783-
<div class="flex flex-col gap-4">
784-
<Subfield name="Outline">
785-
<Toggle
786-
checked={getSetting("outline")}
787-
onChange={(checked) =>
788-
updateCaptionSetting("outline", checked)
789-
}
790-
disabled={!hasCaptions()}
791-
/>
792-
</Subfield>
793-
</div>
794-
795-
<Show when={getSetting("outline")}>
796-
<div class="flex flex-col gap-2">
797-
<span class="text-gray-11 text-sm">Outline Color</span>
798-
<RgbInput
799-
value={getSetting("outlineColor")}
800-
onChange={(value) =>
801-
updateCaptionSetting("outlineColor", value)
802-
}
792+
<Field name="Font Weight" icon={<IconCapMessageBubble />}>
793+
<KSelect
794+
options={[
795+
{ label: "Normal", value: 400 },
796+
{ label: "Medium", value: 500 },
797+
{ label: "Bold", value: 700 },
798+
]}
799+
optionValue="value"
800+
optionTextValue="label"
801+
value={{
802+
label: "Custom",
803+
value: getSetting("fontWeight"),
804+
}}
805+
onChange={(value) => {
806+
if (!value) return;
807+
updateCaptionSetting("fontWeight", value.value);
808+
}}
809+
disabled={!hasCaptions()}
810+
itemComponent={(selectItemProps) => (
811+
<MenuItem<typeof KSelect.Item>
812+
as={KSelect.Item}
813+
item={selectItemProps.item}
814+
>
815+
<KSelect.ItemLabel class="flex-1">
816+
{selectItemProps.item.rawValue.label}
817+
</KSelect.ItemLabel>
818+
<KSelect.ItemIndicator class="ml-auto text-blue-9">
819+
<IconCapCircleCheck />
820+
</KSelect.ItemIndicator>
821+
</MenuItem>
822+
)}
823+
>
824+
<KSelect.Trigger class="flex w-full items-center justify-between rounded-md border border-gray-3 bg-gray-2 px-3 py-2 text-sm text-gray-12 transition-colors hover:border-gray-4 hover:bg-gray-3 focus:border-blue-9 focus:outline-none focus:ring-1 focus:ring-blue-9">
825+
<KSelect.Value<{
826+
label: string;
827+
value: number;
828+
}> class="truncate">
829+
{(state) => {
830+
const selected = state.selectedOption();
831+
if (selected) return selected.label;
832+
const weight = getSetting("fontWeight");
833+
const option = [
834+
{ label: "Normal", value: 400 },
835+
{ label: "Medium", value: 500 },
836+
{ label: "Bold", value: 700 },
837+
].find((o) => o.value === weight);
838+
return option ? option.label : "Bold";
839+
}}
840+
</KSelect.Value>
841+
<KSelect.Icon>
842+
<IconCapChevronDown class="size-4 shrink-0 transform transition-transform ui-expanded:rotate-180 text-[--gray-500]" />
843+
</KSelect.Icon>
844+
</KSelect.Trigger>
845+
<KSelect.Portal>
846+
<PopperContent<typeof KSelect.Content>
847+
as={KSelect.Content}
848+
class={cx(topSlideAnimateClasses, "z-50")}
849+
>
850+
<MenuItemList<typeof KSelect.Listbox>
851+
class="overflow-y-auto max-h-40"
852+
as={KSelect.Listbox}
803853
/>
804-
</div>
805-
</Show>
806-
</div>
854+
</PopperContent>
855+
</KSelect.Portal>
856+
</KSelect>
807857
</Field>
808858

809859
<Field name="Export Options" icon={<IconCapMessageBubble />}>

0 commit comments

Comments
 (0)