Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/desktop/src-tauri/src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ impl ShowCapWindow {
.visible_on_all_workspaces(true)
.content_protected(should_protect)
.center()
.transparent(true)
.initialization_script(format!(
"
window.__CAP__ = window.__CAP__ ?? {{}};
Expand Down Expand Up @@ -428,6 +429,7 @@ impl ShowCapWindow {
.resizable(true)
.maximized(false)
.center()
.transparent(true)
.build()?
}
Self::Editor { .. } => {
Expand Down
75 changes: 75 additions & 0 deletions apps/desktop/src/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,78 @@ export const SettingsIcon = (props: { class: string }) => {
</svg>
);
};

export const CameraIcon = (props: { class: string }) => {
return (
<svg class={props.class} xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path
d="M9.90039 5C9.90039 4.76131 9.8055 4.53206 9.63672 4.36328C9.48902 4.21558 9.29493 4.12493 9.08887 4.10449L9 4.09961H3C2.76131 4.09961 2.53206 4.1945 2.36328 4.36328C2.1945 4.53206 2.09961 4.76131 2.09961 5V11L2.10449 11.0889C2.12493 11.2949 2.21558 11.489 2.36328 11.6367C2.53206 11.8055 2.76131 11.9004 3 11.9004H9L9.08887 11.8955C9.29493 11.8751 9.48902 11.7844 9.63672 11.6367C9.8055 11.4679 9.90039 11.2387 9.90039 11V5ZM11.0996 7.24805V8.75098L13.9004 11.5518V4.44727L11.0996 7.24805ZM11.0996 5.55176L13.2227 3.42871L13.3457 3.32324C13.4757 3.22701 13.6262 3.1597 13.7861 3.12793L13.9463 3.1084C14.054 3.10313 14.162 3.11352 14.2666 3.13965L14.4209 3.19043L14.5654 3.26367C14.658 3.31915 14.7419 3.38788 14.8145 3.46777L14.9141 3.5957L14.9941 3.73633C15.0634 3.88257 15.0995 4.04292 15.0996 4.20605V11.7939C15.0994 12.0113 15.0349 12.2236 14.9141 12.4043C14.7932 12.585 14.6218 12.7264 14.4209 12.8096C14.2202 12.8927 13.9992 12.9143 13.7861 12.8721C13.6262 12.8403 13.4757 12.773 13.3457 12.6768L13.2227 12.5713L11.0996 10.4473V11C11.0996 11.557 10.8792 12.0915 10.4854 12.4854C10.1409 12.8298 9.68859 13.042 9.20801 13.0898L9 13.0996H3C2.44305 13.0996 1.90847 12.8792 1.51465 12.4854C1.17015 12.1409 0.957974 11.6886 0.910156 11.208L0.900391 11V5C0.900391 4.44305 1.12082 3.90847 1.51465 3.51465C1.90847 3.12082 2.44305 2.90039 3 2.90039H9L9.20801 2.91016C9.68859 2.95797 10.1409 3.17015 10.4854 3.51465C10.8792 3.90847 11.0996 4.44305 11.0996 5V5.55176Z"
fill="currentColor"
/>
</svg>
);
};

export const MicrophoneIcon = (props: { class: string }) => {
return (
<svg class={props.class} xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path
d="M7.40039 14.666V13.2266C6.26146 13.0847 5.05403 12.5525 4.08105 11.7012C2.92345 10.6882 2.06738 9.19761 2.06738 7.33301C2.06738 7.00164 2.33562 6.7334 2.66699 6.7334C2.99836 6.7334 3.2666 7.00164 3.2666 7.33301C3.2666 8.80161 3.93455 9.97752 4.87207 10.7979C5.82099 11.6281 7.01632 12.0663 8 12.0664C8.98379 12.0664 10.1799 11.6283 11.1289 10.7979C12.0664 9.97752 12.7334 8.80158 12.7334 7.33301C12.7334 7.00164 13.0026 6.7334 13.334 6.7334C13.6652 6.73357 13.9336 7.00175 13.9336 7.33301C13.9336 9.19774 13.0767 10.6882 11.9189 11.7012C10.9461 12.5524 9.73936 13.0846 8.60059 13.2266V14.666C8.60059 14.9974 8.33137 15.2666 8 15.2666C7.66878 15.2664 7.40039 14.9973 7.40039 14.666ZM10.7334 4.66602C10.7332 3.15659 9.50947 1.93262 8 1.93262C6.49068 1.93279 5.26678 3.1567 5.2666 4.66602V7.33301C5.2666 8.84248 6.49057 10.0662 8 10.0664C9.50958 10.0664 10.7334 8.84259 10.7334 7.33301V4.66602ZM11.9336 7.33301C11.9336 9.50533 10.1723 11.2666 8 11.2666C5.82783 11.2664 4.06738 9.50522 4.06738 7.33301V4.66602C4.06756 2.49395 5.82794 0.733574 8 0.733398C10.1722 0.733398 11.9334 2.49385 11.9336 4.66602V7.33301Z"
fill="currentColor"
/>
</svg>
);
};

export const SystemAudioIcon = (props: { class: string }) => {
return (
<svg class={props.class} xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8.00006 1.39941C8.33144 1.39941 8.60006 1.66804 8.60006 1.99941L8.60007 13.9994C8.60007 14.3308 8.33144 14.5994 8.00007 14.5994C7.6687 14.5994 7.40007 14.3308 7.40007 13.9994L7.40006 1.99941C7.40006 1.66804 7.66869 1.39941 8.00006 1.39941ZM4.66673 4.06608C4.9981 4.06608 5.26673 4.33471 5.26673 4.66608L5.26673 11.3327C5.26673 11.6641 4.9981 11.9327 4.66673 11.9327C4.33536 11.9327 4.06673 11.6641 4.06673 11.3327L4.06673 4.66608C4.06673 4.33471 4.33536 4.06608 4.66673 4.06608ZM11.3334 4.06608C11.6648 4.06608 11.9334 4.33471 11.9334 4.66608L11.9334 11.3327C11.9334 11.6641 11.6648 11.9327 11.3334 11.9327C11.002 11.9327 10.7334 11.6641 10.7334 11.3327L10.7334 4.66608C10.7334 4.33471 11.002 4.06608 11.3334 4.06608ZM1.3334 6.73275C1.66477 6.73275 1.9334 7.00138 1.9334 7.33275L1.9334 8.66608C1.9334 8.99745 1.66477 9.26608 1.3334 9.26608C1.00203 9.26608 0.7334 8.99745 0.7334 8.66608L0.733399 7.33275C0.733399 7.00138 1.00203 6.73275 1.3334 6.73275ZM14.6667 6.73274C14.9981 6.73274 15.2667 7.00137 15.2667 7.33274L15.2667 8.66608C15.2667 8.99745 14.9981 9.26608 14.6667 9.26608C14.3354 9.26608 14.0667 8.99745 14.0667 8.66608L14.0667 7.33274C14.0667 7.00137 14.3354 6.73274 14.6667 6.73274Z"
fill="currentColor"
/>
</svg>
);
};

export const DisplayIcon = (props: { class?: string }) => {
return (
<svg class={props.class} xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path
d="M10 13.4004C10.3313 13.4004 10.5994 13.6688 10.5996 14C10.5996 14.3314 10.3314 14.5996 10 14.5996H6C5.66863 14.5996 5.40039 14.3314 5.40039 14C5.40057 13.6688 5.66874 13.4004 6 13.4004H10ZM13 2.40039C13.8835 2.40039 14.5994 3.1165 14.5996 4V11C14.5996 11.8837 13.8837 12.5996 13 12.5996H3C2.11634 12.5996 1.40039 11.8837 1.40039 11V4C1.40057 3.1165 2.11645 2.40039 3 2.40039H13ZM3 3.59961C2.7792 3.59961 2.59979 3.77924 2.59961 4V11C2.59961 11.2209 2.77909 11.4004 3 11.4004H13C13.2209 11.4004 13.4004 11.2209 13.4004 11V4C13.4002 3.77924 13.2208 3.59961 13 3.59961H3Z"
fill="white"
/>
</svg>
);
};

export const WindowIcon = (props: { class?: string }) => {
return (
<svg class={props.class} xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path
d="M13.5 2.40039C14.1075 2.40039 14.5996 2.89249 14.5996 3.5V12.5C14.5996 13.1075 14.1075 13.5996 13.5 13.5996H2.5C1.89249 13.5996 1.40039 13.1075 1.40039 12.5V3.5C1.40039 2.89249 1.89249 2.40039 2.5 2.40039H13.5ZM2.59961 12.4004H13.4004V3.59961H2.59961V12.4004ZM4.25 4.5C4.66421 4.5 5 4.83579 5 5.25C5 5.66421 4.66421 6 4.25 6C3.83579 6 3.5 5.66421 3.5 5.25C3.5 4.83579 3.83579 4.5 4.25 4.5ZM6.75 4.5C7.16421 4.5 7.5 4.83579 7.5 5.25C7.5 5.66421 7.16421 6 6.75 6C6.33579 6 6 5.66421 6 5.25C6 4.83579 6.33579 4.5 6.75 4.5Z"
fill="white"
/>
</svg>
);
};

export const CropIcon = (props: { class?: string }) => {
return (
<svg class={props.class} xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<g clip-path="url(#clip0_2170_2470)">
<path
d="M4 0.900391C4.33137 0.900391 4.59961 1.16863 4.59961 1.5V11.4004H14.5C14.8314 11.4004 15.0996 11.6686 15.0996 12C15.0996 12.3314 14.8314 12.5996 14.5 12.5996H12.5996V14.5C12.5996 14.8314 12.3314 15.0996 12 15.0996C11.6686 15.0996 11.4004 14.8314 11.4004 14.5V12.5996H4C3.66863 12.5996 3.40039 12.3314 3.40039 12V4.59961H1.5C1.16863 4.59961 0.900391 4.33137 0.900391 4C0.900391 3.66863 1.16863 3.40039 1.5 3.40039H3.40039V1.5C3.40039 1.16863 3.66863 0.900391 4 0.900391ZM12 3.40039C12.3314 3.40039 12.5996 3.66863 12.5996 4V10C12.5996 10.3314 12.3314 10.5996 12 10.5996C11.6686 10.5996 11.4004 10.3314 11.4004 10V4.59961H6C5.66863 4.59961 5.40039 4.33137 5.40039 4C5.40039 3.66863 5.66863 3.40039 6 3.40039H12Z"
fill="white"
/>
</g>
<defs>
<clipPath id="clip0_2170_2470">
<rect width="16" height="16" fill="white" />
</clipPath>
</defs>
</svg>
);
};
15 changes: 13 additions & 2 deletions apps/desktop/src/routes/(window-chrome).tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,18 @@ export default function (props: RouteSectionProps) {

return (
<WindowChromeContext>
<div class="flex overflow-hidden flex-col w-screen h-screen max-h-screen bg-gray-1 p-1">
<div
class="flex overflow-hidden flex-col w-screen h-screen max-h-screen p-1"
style={{
"border-radius": "20px",
border: "1px solid rgba(255, 255, 255, 0.10)",
background: "rgba(9, 10, 11, 1)",
// "box-shadow":
// "0 1px 1px -0.5px rgba(0, 0, 0, 0.16), 0 3px 3px -1.5px rgba(0, 0, 0, 0.16), 0 6px 6px -3px rgba(0, 0, 0, 0.16), 0 12px 12px -6px rgba(0, 0, 0, 0.16), 0 24px 24px -12px rgba(0, 0, 0, 0.16)",
// "backdrop-filter": "blur(15px)",
// "-webkit-backdrop-filter": "blur(15px)", // For Safari/WebKit
}}
>
<Header />

{/* breaks sometimes */}
Expand Down Expand Up @@ -76,7 +87,7 @@ function Header() {

return (
<header
class={cx("flex items-center space-x-1 h-10 select-none shrink-0 bg-gray-1", isWindows ? "flex-row" : "flex-row")}
class={cx("flex items-center space-x-1 h-10 select-none shrink-0", isWindows ? "flex-row" : "flex-row")}
data-tauri-drag-region
>
{ctx.state()?.items}
Expand Down
28 changes: 11 additions & 17 deletions apps/desktop/src/routes/(window-chrome)/new-main/CameraSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { CameraInfo } from "~/utils/tauri";
import InfoPill from "./InfoPill";
import TargetSelectInfoPill from "./TargetSelectInfoPill";
import useRequestPermission from "./useRequestPermission";
import { CameraIcon } from "~/icons";

const NO_CAMERA = "No Camera";

Expand All @@ -20,8 +21,8 @@ export default function CameraSelect(props: {
<CameraSelectBase
{...props}
PillComponent={InfoPill}
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"
iconClass="text-gray-10 size-4"
class="flex flex-row gap-2 items-center px-2 w-full h-9 rounded-lg transition-colors cursor-default disabled:opacity-70 cursor-pointer hover:bg-white/[0.03] disabled:text-gray-11 text-neutral-300 hover:text-white KSelect"
iconClass="size-4"
/>
);
}
Expand All @@ -31,23 +32,18 @@ export function CameraSelectBase(props: {
options: CameraInfo[];
value: CameraInfo | null;
onChange: (camera: CameraInfo | null) => void;
PillComponent: Component<
ComponentProps<"button"> & { variant: "blue" | "red" }
>;
PillComponent: Component<ComponentProps<"button"> & { variant: "blue" | "red" }>;
class: string;
iconClass: string;
}) {
const currentRecording = createCurrentRecordingQuery();
const permissions = createQuery(() => getPermissions);
const requestPermission = useRequestPermission();

const permissionGranted = () =>
permissions?.data?.camera === "granted" ||
permissions?.data?.camera === "notNeeded";
const permissionGranted = () => permissions?.data?.camera === "granted" || permissions?.data?.camera === "notNeeded";

const onChange = (cameraLabel: CameraInfo | null) => {
if (!cameraLabel && !permissionGranted())
return requestPermission("camera");
if (!cameraLabel && !permissionGranted()) return requestPermission("camera");

props.onChange(cameraLabel);

Expand Down Expand Up @@ -80,7 +76,7 @@ export function CameraSelectBase(props: {
text: o.display_name,
checked: o === props.value,
action: () => onChange(o),
}),
})
),
])
.then((items) => Menu.new({ items }))
Expand All @@ -90,11 +86,9 @@ export function CameraSelectBase(props: {
}}
class={props.class}
>
<IconCapCamera class={props.iconClass} />
<p class="flex-1 text-sm text-left truncate">
{props.value?.display_name ?? NO_CAMERA}
</p>
<TargetSelectInfoPill
<CameraIcon class={props.iconClass} />
<p class="flex-1 text-sm text-left truncate">{props.value?.display_name ?? NO_CAMERA}</p>
{/* <TargetSelectInfoPill
PillComponent={props.PillComponent}
value={props.value}
permissionGranted={permissionGranted()}
Expand All @@ -106,7 +100,7 @@ export function CameraSelectBase(props: {
props.onChange(null);
}
}}
/>
/> */}
</button>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { cx } from "cva";
import { type Component, type ComponentProps, splitProps } from "solid-js";

type HorizontalTargetButtonProps = {
selected: boolean;
Component: Component<ComponentProps<"svg">>;
name: string;
disabled?: boolean;
} & ComponentProps<"button">;

function HorizontalTargetButton(props: HorizontalTargetButtonProps) {
const [local, rest] = splitProps(props, ["selected", "Component", "name", "disabled", "class"]);

return (
<button
{...rest}
type="button"
disabled={local.disabled}
aria-pressed={local.selected ? "true" : "false"}
class={cx(
"flex w-full h-9 flex-row items-center gap-3 px-3 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-9 focus-visible:ring-offset-2 focus-visible:ring-offset-gray-1",
local.selected ? "text-gray-12" : "text-gray-12 hover:bg-white/[0.08]",
local.disabled && "pointer-events-none opacity-60",
local.class
)}
style={{
"border-radius": "10px",
border: "0.5px solid rgba(255, 255, 255, 0.10)",
background: "rgba(255, 255, 255, 0.05)",
"box-shadow": "0 1px 1px -0.5px rgba(0, 0, 0, 0.16)",
}}
>
<local.Component
class={cx("size-5 transition-colors flex-shrink-0", local.selected ? "text-gray-12" : "text-gray-9")}
/>
<p class="text-sm font-medium">{local.name}</p>
</button>
);
}

export default HorizontalTargetButton;
18 changes: 13 additions & 5 deletions apps/desktop/src/routes/(window-chrome)/new-main/InfoPill.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
import { cx } from "cva";
import type { ComponentProps } from "solid-js";

export default function InfoPill(
props: ComponentProps<"button"> & { variant: "blue" | "red" },
) {
export default function InfoPill(props: ComponentProps<"button"> & { variant: "blue" | "red" }) {
return (
<button
{...props}
type="button"
class={cx("px-2 py-0.5 rounded-full text-white text-[11px]", props.variant === "blue" ? "bg-blue-9" : "bg-red-9")}
/>
);
}

export function InfoPillNew(props: ComponentProps<"button"> & { variant: "on" | "off" }) {
return (
<button
{...props}
type="button"
class={cx(
"px-2 py-0.5 rounded-full text-white text-[11px]",
props.variant === "blue" ? "bg-blue-9" : "bg-red-9",
"px-2 h-6 rounded-[6px] text-[11px] font-medium",
props.variant === "on" ? "bg-blue-9 text-white " : "bg-white/10 text-white/40"
)}
/>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import { createQuery } from "@tanstack/solid-query";
import { CheckMenuItem, Menu, PredefinedMenuItem } from "@tauri-apps/api/menu";
import { cx } from "cva";
import {
type Component,
type ComponentProps,
createSignal,
onMount,
Show,
} from "solid-js";
import { type Component, type ComponentProps, createSignal, onMount, Show } from "solid-js";
import { trackEvent } from "~/utils/analytics";
import { createTauriEventListener } from "~/utils/createEventListener";
import { createCurrentRecordingQuery, getPermissions } from "~/utils/queries";
import { events } from "~/utils/tauri";
import InfoPill from "./InfoPill";
import TargetSelectInfoPill from "./TargetSelectInfoPill";
import useRequestPermission from "./useRequestPermission";
import { MicrophoneIcon } from "~/icons";

const NO_MICROPHONE = "No Microphone";

Expand All @@ -27,9 +22,9 @@ export default function MicrophoneSelect(props: {
return (
<MicrophoneSelectBase
{...props}
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"
class="flex flex-row gap-2 items-center px-2 w-full h-9 rounded-lg transition-colors cursor-default disabled:opacity-70 cursor-pointer hover:bg-white/[0.03] disabled:text-gray-11 text-neutral-300 hover:text-white KSelect"
levelIndicatorClass="bg-blue-7"
iconClass="text-gray-10 size-4"
iconClass="size-4"
PillComponent={InfoPill}
/>
);
Expand All @@ -43,9 +38,7 @@ export function MicrophoneSelectBase(props: {
class: string;
levelIndicatorClass: string;
iconClass: string;
PillComponent: Component<
ComponentProps<"button"> & { variant: "blue" | "red" }
>;
PillComponent: Component<ComponentProps<"button"> & { variant: "blue" | "red" }>;
}) {
const DB_SCALE = 40;

Expand All @@ -58,8 +51,7 @@ export function MicrophoneSelectBase(props: {
const requestPermission = useRequestPermission();

const permissionGranted = () =>
permissions?.data?.microphone === "granted" ||
permissions?.data?.microphone === "notNeeded";
permissions?.data?.microphone === "granted" || permissions?.data?.microphone === "notNeeded";

type Option = { name: string };

Expand All @@ -80,8 +72,7 @@ export function MicrophoneSelectBase(props: {
});

// visual audio level from 0 -> 1
const audioLevel = () =>
(1 - Math.max((dbs() ?? 0) + DB_SCALE, 0) / DB_SCALE) ** 0.5;
const audioLevel = () => (1 - Math.max((dbs() ?? 0) + DB_SCALE, 0) / DB_SCALE) ** 0.5;

// Initialize audio input if needed - only once when component mounts
onMount(() => {
Expand Down Expand Up @@ -116,7 +107,7 @@ export function MicrophoneSelectBase(props: {
text: name,
checked: name === props.value,
action: () => handleMicrophoneChange({ name: name }),
}),
})
),
])
.then((items) => Menu.new({ items }))
Expand All @@ -130,17 +121,15 @@ export function MicrophoneSelectBase(props: {
<div
class={cx(
"opacity-50 left-0 inset-y-0 absolute transition-[right] duration-100 -z-10",
props.levelIndicatorClass,
props.levelIndicatorClass
)}
style={{ right: `${audioLevel() * 100}%` }}
/>
)}
</Show>
<IconCapMicrophone class={props.iconClass} />
<p class="flex-1 text-sm text-left truncate">
{props.value ?? NO_MICROPHONE}
</p>
<TargetSelectInfoPill
<MicrophoneIcon class={props.iconClass} />
<p class="flex-1 text-sm text-left truncate">{props.value ?? NO_MICROPHONE}</p>
{/* <TargetSelectInfoPill
PillComponent={props.PillComponent}
value={props.value}
permissionGranted={permissionGranted()}
Expand All @@ -151,7 +140,7 @@ export function MicrophoneSelectBase(props: {
props.onChange(null);
}
}}
/>
/> */}
</button>
</div>
);
Expand Down
Loading