feat: add Vite configuration with custom plugins and server settings

This commit is contained in:
2026-02-21 16:16:15 +11:00
parent 576e6d77c1
commit 6c741577ce
32 changed files with 1937 additions and 20 deletions

View File

@@ -0,0 +1,156 @@
import React, { FormEventHandler, useCallback, useState } from 'react';
import {
Box,
Button,
color,
Input,
Spinner,
Text,
} from 'folds';
import { SequenceCard } from '../../../components/sequence-card';
import { SequenceCardStyle } from '../../room-settings/styles.css';
import { SettingTile } from '../../../components/setting-tile';
import { useRoom } from '../../../hooks/useRoom';
import { useStateEvent } from '../../../hooks/useStateEvent';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { usePowerLevels } from '../../../hooks/usePowerLevels';
import { useRoomCreators } from '../../../hooks/useRoomCreators';
import { useRoomPermissions } from '../../../hooks/useRoomPermissions';
import { StateEvent } from '../../../../types/matrix/room';
interface LivekitConfigContent {
service_url?: string;
}
/**
* Room-specific LiveKit configuration component.
* Allows room admins to override the LiveKit service URL for calls in this room.
*/
export function LivekitConfig() {
const mx = useMatrixClient();
const room = useRoom();
const powerLevels = usePowerLevels(room);
const creators = useRoomCreators(room);
const permissions = useRoomPermissions(creators, powerLevels);
const stateEvent = useStateEvent(room, StateEvent.RoomLivekitConfig);
const currentConfig = stateEvent?.getContent<LivekitConfigContent>();
const currentUrl = currentConfig?.service_url || '';
const [urlValue, setUrlValue] = useState(currentUrl);
const [urlError, setUrlError] = useState<string>();
const [saveState, saveConfig] = useAsyncCallback(
useCallback(
async (serviceUrl: string) => {
const content: LivekitConfigContent = serviceUrl
? { service_url: serviceUrl }
: {};
await mx.sendStateEvent(room.roomId, StateEvent.RoomLivekitConfig as any, content, '');
},
[mx, room]
)
);
const canEdit = permissions.stateEvent(StateEvent.RoomLivekitConfig, mx.getUserId()!);
const handleSave: FormEventHandler<HTMLFormElement> = async (evt) => {
evt.preventDefault();
const serviceUrl = urlValue.trim();
// Basic URL validation
if (serviceUrl && !serviceUrl.startsWith('http://') && !serviceUrl.startsWith('https://')) {
setUrlError('URL must start with http:// or https://');
return;
}
setUrlError(undefined);
await saveConfig(serviceUrl);
};
const handleClear = async () => {
setUrlValue('');
setUrlError(undefined);
await saveConfig('');
};
const hasChanges = urlValue !== currentUrl;
const isSaving = saveState.status === AsyncStatus.Loading;
return (
<SequenceCard
className={SequenceCardStyle}
variant="SurfaceVariant"
direction="Column"
gap="400"
>
<SettingTile
title="LiveKit Service URL"
description="Override the LiveKit JWT service endpoint for Matrix RTC calls in this room. Leave empty to use the server default or user preference."
/>
<Box as="form" onSubmit={handleSave} direction="Column" gap="200" style={{ padding: '0 16px' }}>
<Input
value={urlValue}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setUrlValue(e.target.value);
setUrlError(undefined);
}}
placeholder="https://example.com/livekit/jwt"
size="300"
variant="Secondary"
radii="300"
disabled={!canEdit || isSaving}
style={{ width: '100%' }}
/>
{urlError && (
<Text size="T200" style={{ color: color.Critical.Main }}>
{urlError}
</Text>
)}
{saveState.status === AsyncStatus.Error && (
<Text size="T200" style={{ color: color.Critical.Main }}>
Failed to save: {saveState.error instanceof Error ? saveState.error.message : 'Unknown error'}
</Text>
)}
{saveState.status === AsyncStatus.Success && !hasChanges && (
<Text size="T200" style={{ color: color.Success.Main }}>
Configuration saved
</Text>
)}
<Box gap="200" alignItems="Center">
<Button
type="submit"
size="300"
variant="Primary"
fill="Solid"
radii="300"
disabled={!canEdit || isSaving || !hasChanges}
before={isSaving && <Spinner size="100" variant="Primary" fill="Solid" />}
>
<Text size="B300">Save</Text>
</Button>
{currentUrl && (
<Button
type="button"
size="300"
variant="Critical"
fill="None"
radii="300"
disabled={!canEdit || isSaving}
onClick={handleClear}
>
<Text size="B300">Clear</Text>
</Button>
)}
{!canEdit && (
<Text size="T200" priority="300">
You don't have permission to change this setting
</Text>
)}
</Box>
</Box>
</SequenceCard>
);
}