Skip to content
4 changes: 4 additions & 0 deletions src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@
@apply border-border;
}

html {
scrollbar-gutter: stable;
}

body {
@apply bg-background text-foreground;
/* font-feature-settings: "rlig" 1, "calt" 1; */
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/ui/button/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { type VariantProps, tv } from 'tailwind-variants';
import type { Button as ButtonPrimitive } from 'bits-ui';

const buttonVariants = tv({
base: 'focus-visible:ring-ring inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50',
base: 'focus-visible:ring-ring inline-flex gap-2 items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50',
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90 shadow',
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/ui/label/label.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export { className as class };
</script>

<LabelPrimitive.Root
class={cn('text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70', className)}
class={cn('block mb-1.5 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70', className)}
{...$$restProps}>
<slot />
</LabelPrimitive.Root>
8 changes: 4 additions & 4 deletions src/lib/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Advanced from '../../main/Tabs/Advanced.svelte';
import General from '../../main/Tabs/General.svelte';
import Network from '../../main/Tabs/Network.svelte';
import Settings from '../../main/Tabs/Settings.svelte';
import Advanced from '../../main/tabs/Advanced.svelte';
import General from '../../main/tabs/General.svelte';
import Network from '../../main/tabs/Network.svelte';
import Settings from '../../main/tabs/Settings.svelte';
import type { ComponentProps } from 'svelte';

//eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
70 changes: 70 additions & 0 deletions src/lib/helpers/NavigationHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { type NavElements, defaultNavElement, navElements } from '$lib/config';

/**
* Get the navigation element that matches the current URL hash
*/
export function getNavFromHash(): NavElements {
const hash = window.location.hash.substring(1); // Remove the # character

if (!hash) return defaultNavElement;

// Find the navigation element with matching label
for (const [identifier, nav] of Object.entries(navElements)) {
if (nav.label === hash) {
return { [identifier]: nav };
}
}

return defaultNavElement;
}

/**
* Update the URL hash based on the current navigation
*/
export function updateHash(navigation: NavElements): void {
if (!navigation) return;

const identifier = Object.keys(navigation)[0];
const navElement = navigation[identifier];

// Only update if hash is different to avoid unnecessary refreshes
if (window.location.hash !== `#${navElement.label}`) {
history.pushState(null, '', `#${navElement.label}`);
}
}

/**
* Setup hash-based navigation
* @param navigationStore The store to sync with the URL hash
* @returns A cleanup function
*/
export function setupHashNavigation(navigationStore: {
set: (value: NavElements) => void;
subscribe: (callback: (value: NavElements) => void) => () => void;
}): () => void {
// Set initial navigation based on hash
const initialNav = getNavFromHash();
navigationStore.set(initialNav);

// Handler for hash changes
const handleHashChange = () => {
const navFromHash = getNavFromHash();
navigationStore.set(navFromHash);
};

// Listen for hash changes
window.addEventListener('hashchange', handleHashChange);

// Subscribe to navigation changes
const unsubscribe = navigationStore.subscribe(navigation => {
if (navigation) {
updateHash(navigation);
}
});

// Return cleanup function
return () => {
window.removeEventListener('hashchange', handleHashChange);
unsubscribe();
};
}
24 changes: 10 additions & 14 deletions src/lib/helpers/PipelineHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,31 +29,27 @@ export type GroupedPipelines = {
export function parsePipelineName(name: string): PipelineInfo {
// Basic device extraction
const deviceMatch = name.match(/^([^/]+)/);

// Extract encoder (h264 or h265)
const encoderMatch = name.match(/(h264|h265)/);

// Format extraction - comes after h264/h265_ prefix
const formatMatch = name.match(/(?:h264|h265)_([^_]+)/);

// Extract resolution - typically NNNp format (like 720p, 1080p)
const resolutionMatch = name.match(/(\d{3,4}p)/);

// Extract framerate - typically pNN format (like p30, p60)
// Handle both underscore separated and inline formats
const fpsMatch = name.match(/p(\d+(?:\.\d+)?)/);


// Extract framerate - typically pNN format (like p30, p60) or _NNfps (like _30fps, _60fps)
const fpsMatch = name.match(/p(\d+(?:\.\d+)?)|_(\d+(?:\.\d+)?)fps/);
// Special case for libuvch264
const isLibUVC = name.includes('libuvch264');

return {
device: deviceMatch ? deviceMatch[0] : null,
encoder: encoderMatch ? encoderMatch[0] : null,
format: formatMatch
? (isLibUVC ? 'usb-libuvch264' : formatMatch[1].replace(/_/g, ' '))
: null,
format: formatMatch ? (isLibUVC ? 'usb-libuvch264' : formatMatch[1].replace(/_/g, ' ')) : null,
resolution: resolutionMatch ? resolutionMatch[0] : '[Match device resolution]',
fps: fpsMatch ? parseFloat(fpsMatch[1]) : '[Match device output]',
fps: fpsMatch ? parseFloat(fpsMatch[1] || fpsMatch[2]) : '[Match device output]',
};
}

Expand Down Expand Up @@ -91,4 +87,4 @@ export const groupPipelinesByDeviceAndFormat = (pipelines: PipelinesMessage): Gr
});

return groupedPipelines;
};
};
Loading