Skip to content
Open
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
24 changes: 21 additions & 3 deletions apps/desktop/src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub fn open_settings(window: WebviewWindow, update: bool) {
let app = window.app_handle();
let settings_windows = app.get_webview_window(SETTINGS_WINDOW_NAME);
if let Some(settings_windows) = settings_windows {
let _ = settings_windows.unminimize();
settings_windows.show();
settings_windows.set_focus();
if update {
Expand Down Expand Up @@ -47,12 +48,18 @@ pub fn toggle_pin(window: WebviewWindow, pin: State<Pinned>, menu: State<TrayMen
let app = window.app_handle();
let value = !get_pin(app.state::<Pinned>());

_set_pin(value, &window, pin, menu);
// Always target the main overlay window so pinning from other windows
// (e.g., settings) does not affect those windows.
if let Some(main_win) = app.get_webview_window(MAIN_WINDOW_NAME) {
_set_pin(value, &main_win, pin, menu);
}
}

#[tauri::command]
pub fn set_pin(window: WebviewWindow, pin: State<Pinned>, menu: State<TrayMenu>, value: bool) {
_set_pin(value, &window, pin, menu);
if let Some(main_win) = window.app_handle().get_webview_window(MAIN_WINDOW_NAME) {
_set_pin(value, &main_win, pin, menu);
}
}

impl Deref for Pinned {
Expand All @@ -75,9 +82,20 @@ fn _set_pin(value: bool, window: &WebviewWindow, pinned: State<Pinned>, menu: St
// @d0nutptr cooked here
pinned.store(value, std::sync::atomic::Ordering::Relaxed);

// let the client know
// emit to the main window and also broadcast to all webviews so other
// windows (like settings) can react to the change.
window.emit(TRAY_TOGGLE_PIN, value).unwrap();

// Broadcast the pin change to all webviews so other
// windows (like settings) can react to the change.
let app_handle = window.app_handle();
if let Some(main_win) = app_handle.get_webview_window(MAIN_WINDOW_NAME) {
let _ = main_win.emit(TRAY_TOGGLE_PIN, value);
}
if let Some(settings_win) = app_handle.get_webview_window(SETTINGS_WINDOW_NAME) {
let _ = settings_win.emit(TRAY_TOGGLE_PIN, value);
}

// invert the label for the tray
if let Some(toggle_pin_menu_item) = menu.lock().ok().and_then(|m| m.get(TRAY_TOGGLE_PIN)) {
let enable_or_disable = if value { "Unpin" } else { "Pin" };
Expand Down
46 changes: 33 additions & 13 deletions apps/desktop/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ fn main() {
width: SETTINGS_WINDOW_WIDTH,
height: SETTINGS_WINDOW_HEIGHT,
});
// Start the settings window minimized so it's available but not intrusive
// The `show` call from the UI will restore/unminimize it.
settings.minimize().ok();

Ok(())
})
Expand All @@ -168,22 +171,39 @@ fn main() {
.build(tauri::generate_context!())
.expect("An error occured while running the app!")
.run(|app, event| {
use tauri::WindowEvent as WEvent;

if let tauri::RunEvent::WindowEvent {
event: tauri::WindowEvent::CloseRequested { api, .. },
label,
..
event: we, label, ..
} = event
{
if label == SETTINGS_WINDOW_NAME {
let win = app.get_webview_window(label.as_str()).unwrap();
win.hide().unwrap();
}

if label == MAIN_WINDOW_NAME {
app.save_window_state(StateFlags::POSITION | StateFlags::SIZE);
std::process::exit(0);
} else {
api.prevent_close();
match we {
WEvent::CloseRequested { api, .. } => {
if label == SETTINGS_WINDOW_NAME {
let win = app.get_webview_window(label.as_str()).unwrap();
// Minimize the settings window instead of hiding/closing it when
// the user clicks the X so it remains available to restore.
win.minimize().ok();
}

if label == MAIN_WINDOW_NAME {
// Save state on close
app.save_window_state(StateFlags::POSITION | StateFlags::SIZE);
std::process::exit(0);
} else {
api.prevent_close();
}
}

WEvent::Resized(_) | WEvent::Moved(_) => {
if label == MAIN_WINDOW_NAME {
// Also save state whenever the main window is moved or resized so
// we persist the latest bounds even if the process is terminated
app.save_window_state(StateFlags::POSITION | StateFlags::SIZE);
}
}

_ => {}
}
}
});
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"transparent": true,
"decorations": true,
"resizable": false,
"visible": false,
"visible": true,
"label": "settings",
"url": "#settings",
"title": "Overlayed - Settings",
Expand Down
13 changes: 8 additions & 5 deletions apps/desktop/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Routes, Route } from "react-router-dom";
import { Routes, Route, useLocation } from "react-router-dom";
import { MainView } from "./views/main";
import { ChannelView } from "./views/channel";

Expand Down Expand Up @@ -30,12 +30,15 @@ function App() {
const { pin } = usePin();
const { horizontal, setHorizontalDirection } = useAlign();
const visibleClass = visible ? "opacity-100" : "opacity-0";
const location = useLocation();
const isSettingsWindow = location.pathname === "/settings";

return (
<div
className={cn(
`text-white h-screen select-none rounded-lg ${visibleClass}`,
pin ? null : "border border-zinc-600"
`text-white h-screen select-none rounded-lg flex flex-col ${visibleClass}`,
// Only show the border on the overlay (main) window when not pinned.
pin || isSettingsWindow ? null : "border border-zinc-600"
)}
>
{!pin && (
Expand All @@ -48,10 +51,10 @@ function App() {
)}
<Toaster />
<Routes>
<Route path="/" Component={MainView} />
<Route path="/" element={<MainView />} />
<Route path="/channel" element={<ChannelView alignDirection={horizontal} />} />
<Route path="/settings" element={<SettingsView update={update} />} />
<Route path="/error" Component={ErrorView} />
<Route path="/error" element={<ErrorView />} />
</Routes>
</div>
);
Expand Down
24 changes: 18 additions & 6 deletions apps/desktop/src/components/nav-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { useState } from "react";
import { CHANNEL_TYPES } from "@/constants";
import { Metric, track } from "@/metrics";
import { invoke } from "@tauri-apps/api/core";
import { emit } from "@tauri-apps/api/event";
const mapping = {
left: 0,
center: 1,
Expand Down Expand Up @@ -64,6 +65,11 @@ export const NavBar = ({
const [channelName, setChannelName] = useState<string>();
const [currentAlignment, setCurrentAlignment] = useState(mapping[alignDirection]);

// keep local currentAlignment in sync when the prop changes (e.g., loaded from config)
useEffect(() => {
setCurrentAlignment(mapping[alignDirection] ?? mapping.right);
}, [alignDirection]);

const opacity = pin && location.pathname === "/channel" ? "opacity-0" : "opacity-100";
const IconComponent = horizontalAlignments[currentAlignment]?.icon || ArrowLeftToLine;
const showUpdateButton = location.pathname !== "/settings" && isUpdateAvailable;
Expand All @@ -87,7 +93,7 @@ export const NavBar = ({
<div data-tauri-drag-region className="flex justify-between">
<div className="flex items-center">
<img
src={canary ? "/img/32x32-canary.png " : "/img/32x32.png"}
src={canary ? "/img/32x32-canary.png" : "/img/32x32.png"}
alt="logo"
data-tauri-drag-region
className="w-8 h-8 mr-2"
Expand All @@ -104,7 +110,7 @@ export const NavBar = ({
{location.pathname !== "/settings" && (
<div className="hidden gap-4 md:flex">
{!canary && showUpdateButton && (
<button>
<button className="cursor-pointer">
<Download
className="text-green-500"
size={20}
Expand All @@ -116,18 +122,23 @@ export const NavBar = ({
/>
</button>
)}
<button title={horizontalAlignments[currentAlignment]?.name + "-aligned. Click to toggle."}>
<button
className="cursor-pointer"
title={horizontalAlignments[currentAlignment]?.name + "-aligned. Click to toggle."}
>
<IconComponent
size={20}
onClick={async () => {
const newAlignment = (currentAlignment + 1) % horizontalAlignments.length;
setCurrentAlignment(newAlignment);
setAlignDirection(horizontalAlignments[newAlignment]?.direction || "center");
await Config.set("horizontal", horizontalAlignments[newAlignment]?.direction || "center");
const dir = horizontalAlignments[newAlignment]?.direction || "center";
setAlignDirection(dir);
await Config.set("horizontal", dir);
await emit("config_update", await Config.getConfig());
}}
/>
</button>
<button title="Enable pin">
<button className="cursor-pointer" title="Enable pin">
<Pin
size={20}
onClick={async () => {
Expand All @@ -140,6 +151,7 @@ export const NavBar = ({
/>
</button>
<button
className="cursor-pointer"
title="Settings"
onClick={() => {
invoke("open_settings", { update: false });
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/components/ui/slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const Slider = React.forwardRef<
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
<SliderPrimitive.Range className="absolute h-full bg-primary" />
</SliderPrimitive.Track>
<SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
<SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 cursor-grab active:cursor-grabbing" />
</SliderPrimitive.Root>
));
Slider.displayName = SliderPrimitive.Root.displayName;
Expand Down
6 changes: 5 additions & 1 deletion apps/desktop/src/components/ui/tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ const TabsTrigger = React.forwardRef<
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-zinc-700 data-[state=active]:text-foreground data-[state=active]:shadow-xs",
"inline-flex items-center justify-center whitespace-nowrap",
"rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all",
"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
"cursor-pointer disabled:pointer-events-none disabled:opacity-50",
"data-[state=active]:bg-zinc-700 data-[state=active]:text-foreground data-[state=active]:shadow-xs",
className
)}
{...props}
Expand Down
54 changes: 42 additions & 12 deletions apps/desktop/src/components/user.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import type { DirectionLR } from "@/config";
import type { DirectionLR, OpacityTarget } from "@/config";
import { useConfigValue } from "@/hooks/use-config-value";
import type { OverlayedUser } from "../types";
import { HeadphonesOff } from "./icons/headphones-off";
import { MicOff } from "./icons/mic-off";
import { cn } from "@/utils/tw";

export const User = ({
item,
alignDirection,
opacity,
opacityTarget,
userScale,
}: {
item: OverlayedUser;
alignDirection: DirectionLR;
opacity: number;
opacityTarget: OpacityTarget;
userScale: number;
}) => {
const { id, selfMuted, selfDeafened, talking, muted, deafened, avatarHash } = item;

Expand All @@ -19,8 +25,21 @@ export const User = ({
const talkingClass = talking ? "border-green-500" : "border-zinc-800";
const mutedClass = selfMuted || muted ? "text-zinc-400" : "";
const opacityStyle = talking ? "100%" : `${opacity}%`;
const scaleFactor = (userScale ?? 100) / 100;
let transformOrigin = "center center";
if (alignDirection === "left") {
transformOrigin = "left center";
} else if (alignDirection === "right") {
transformOrigin = "right center";
}

const { value: maxUsernameLength } = useConfigValue("maxUsernameLength");

// TODO: use tw merge so this looks better i guess
const displayName = (() => {
const name = item.username ?? "";
if (!maxUsernameLength || name.length <= maxUsernameLength) return name;
return name.slice(0, Math.max(0, maxUsernameLength)) + "…";
})();

function renderCheeseMicIcon() {
let icon = null;
Expand All @@ -45,9 +64,11 @@ export const User = ({

return (
<div
className={`absolute left-[12px] bottom-[-8px] pr-[4px] py-[2px] min-w-[24px] h-[24px] ${anyState ? "bg-black/80" : "bg-transparent"} rounded-full ${
alignDirection == "center" ? "flex" : "md:hidden"
}`}
className={cn(
"absolute left-3 -bottom-2 pr-1 py-0.5 min-w-6 h-6 rounded-full",
anyState ? "bg-black/80" : "bg-transparent",
alignDirection === "center" ? "flex" : "md:hidden"
)}
>
{icon}
</div>
Expand All @@ -56,10 +77,16 @@ export const User = ({

return (
<div
className={`flex gap-2 py-1 p-2 justify-start items-center transition-opacity ${
className={cn(
"flex gap-2 py-1 p-2 justify-start items-center transition-opacity",
alignDirection == "right" ? "flex-row-reverse" : "flex-row"
}`}
style={{ opacity: opacityStyle }}
)}
style={{
...(opacityTarget === "all" ? { opacity: opacityStyle } : {}),
transform: scaleFactor !== 1 ? `scale(${scaleFactor})` : undefined,
transformOrigin: scaleFactor !== 1 ? transformOrigin : undefined,
willChange: scaleFactor !== 1 ? "transform" : undefined,
}}
>
<div className={`pointer-events-none relative rounded-full border-2 ${talkingClass}`}>
<img
Expand All @@ -80,11 +107,14 @@ export const User = ({

{/* This is the normal list */}
<div
className={`max-w-[calc(100%_-_50px)] md:flex hidden pointer-events-none items-center rounded-md bg-zinc-800 ${mutedClass} p-1 pl-2 pr-2 ${
alignDirection == "center" ? "hidden md:hidden" : ""
}`}
className={cn(
"max-w-[calc(100%-50px)] md:flex hidden pointer-events-none items-center rounded-md p-1 pl-2 pr-2",
mutedClass,
alignDirection === "center" ? "hidden md:hidden" : undefined
)}
style={{ backgroundColor: `rgba(40, 40, 40, ${opacityTarget === "username-box" ? opacity / 100 : 1})` }}
>
<span className="truncate text-ellipsis">{item.username}</span>
<span className="truncate text-ellipsis">{displayName}</span>
<div className="flex">
{(selfMuted || muted) && <MicOff className={muted ? "fill-red-600" : "fill-current"} />}
{(selfDeafened || deafened) && <HeadphonesOff className={deafened ? "fill-red-600" : "fill-current"} />}
Expand Down
Loading