-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add usePersistentUserChoices hook to save device settings and usernam…
…e to local storage (#683)
- Loading branch information
Showing
11 changed files
with
356 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
'@livekit/components-core': minor | ||
'@livekit/components-react': minor | ||
--- | ||
|
||
Add `usePersistentUserChoices` hook to save user choices saving functionality. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { saveUserChoices, loadUserChoices, type UserChoices } from './user-choices'; |
45 changes: 45 additions & 0 deletions
45
packages/core/src/persistent-storage/local-storage-helpers.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { log } from '../logger'; | ||
|
||
/** | ||
* Set an object to local storage by key | ||
* @param key - the key to set the object to local storage | ||
* @param value - the object to set to local storage | ||
* @internal | ||
*/ | ||
export function setLocalStorageObject<T extends object>(key: string, value: T): void { | ||
if (typeof localStorage === 'undefined') { | ||
log.error('Local storage is not available.'); | ||
return; | ||
} | ||
|
||
try { | ||
localStorage.setItem(key, JSON.stringify(value)); | ||
} catch (error) { | ||
log.error(`Error setting item to local storage: ${error}`); | ||
} | ||
} | ||
|
||
/** | ||
* Get an object from local storage by key | ||
* @param key - the key to retrieve the object from local storage | ||
* @returns the object retrieved from local storage, or null if the key does not exist | ||
* @internal | ||
*/ | ||
export function getLocalStorageObject<T extends object>(key: string): T | undefined { | ||
if (typeof localStorage === 'undefined') { | ||
log.error('Local storage is not available.'); | ||
return undefined; | ||
} | ||
|
||
try { | ||
const item = localStorage.getItem(key); | ||
if (!item) { | ||
log.warn(`Item with key ${key} does not exist in local storage.`); | ||
return undefined; | ||
} | ||
return JSON.parse(item); | ||
} catch (error) { | ||
log.error(`Error getting item from local storage: ${error}`); | ||
return undefined; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import { cssPrefix } from '../constants'; | ||
import { getLocalStorageObject, setLocalStorageObject } from './local-storage-helpers'; | ||
|
||
const USER_CHOICES_KEY = `${cssPrefix}-device-settings` as const; | ||
|
||
/** | ||
* Represents the user's choices for video and audio input devices, | ||
* as well as their username. | ||
*/ | ||
export type UserChoices = { | ||
/** | ||
* Whether video input is enabled. | ||
* @defaultValue `true` | ||
*/ | ||
videoInputEnabled: boolean; | ||
/** | ||
* Whether audio input is enabled. | ||
* @defaultValue `true` | ||
*/ | ||
audioInputEnabled: boolean; | ||
/** | ||
* The device ID of the video input device to use. | ||
* @defaultValue `''` | ||
*/ | ||
videoInputDeviceId: string; | ||
/** | ||
* The device ID of the audio input device to use. | ||
* @defaultValue `''` | ||
*/ | ||
audioInputDeviceId: string; | ||
/** | ||
* The username to use. | ||
* @defaultValue `''` | ||
*/ | ||
username: string; | ||
}; | ||
|
||
const defaultUserChoices: UserChoices = { | ||
videoInputEnabled: true, | ||
audioInputEnabled: true, | ||
videoInputDeviceId: '', | ||
audioInputDeviceId: '', | ||
username: '', | ||
} as const; | ||
|
||
/** | ||
* Saves user choices to local storage. | ||
* @param deviceSettings - The device settings to be stored. | ||
* @alpha | ||
*/ | ||
export function saveUserChoices( | ||
deviceSettings: UserChoices, | ||
/** | ||
* Whether to prevent saving user choices to local storage. | ||
*/ | ||
preventSave: boolean = false, | ||
): void { | ||
if (preventSave === true) { | ||
return; | ||
} | ||
setLocalStorageObject(USER_CHOICES_KEY, deviceSettings); | ||
} | ||
|
||
/** | ||
* Reads the user choices from local storage, or returns the default settings if none are found. | ||
* @param defaults - The default device settings to use if none are found in local storage. | ||
* @defaultValue `defaultUserChoices` | ||
* | ||
* @alpha | ||
*/ | ||
export function loadUserChoices( | ||
defaults?: Partial<UserChoices>, | ||
/** | ||
* Whether to prevent loading from local storage and return default values instead. | ||
* @defaultValue false | ||
*/ | ||
preventLoad: boolean = false, | ||
): UserChoices { | ||
const fallback: UserChoices = { | ||
videoInputEnabled: defaults?.videoInputEnabled ?? defaultUserChoices.videoInputEnabled, | ||
audioInputEnabled: defaults?.audioInputEnabled ?? defaultUserChoices.audioInputEnabled, | ||
videoInputDeviceId: defaults?.videoInputDeviceId ?? defaultUserChoices.videoInputDeviceId, | ||
audioInputDeviceId: defaults?.audioInputDeviceId ?? defaultUserChoices.audioInputDeviceId, | ||
username: defaults?.username ?? defaultUserChoices.username, | ||
}; | ||
|
||
if (preventLoad) { | ||
return fallback; | ||
} else { | ||
return getLocalStorageObject(USER_CHOICES_KEY) ?? fallback; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import type { UserChoices } from '@livekit/components-core'; | ||
import { loadUserChoices, saveUserChoices } from '@livekit/components-core'; | ||
import * as React from 'react'; | ||
|
||
/** | ||
* Options for the `usePersistentDeviceSettings` hook. | ||
* @alpha | ||
*/ | ||
export interface UsePersistentUserChoicesOptions { | ||
/** | ||
* The default value to use if reading from local storage returns no results or fails. | ||
*/ | ||
defaults?: Partial<UserChoices>; | ||
/** | ||
* Whether to prevent saving to persistent storage. | ||
* @defaultValue false | ||
*/ | ||
preventSave?: boolean; | ||
/** | ||
* Whether to prevent loading user choices from persistent storage and use `defaults` instead. | ||
* @defaultValue false | ||
*/ | ||
preventLoad?: boolean; | ||
} | ||
|
||
/** | ||
* A hook that provides access to user choices stored in local storage, such as | ||
* selected media devices and their current state (on or off), as well as the user name. | ||
* @alpha | ||
*/ | ||
export function usePersistentUserChoices(options: UsePersistentUserChoicesOptions = {}) { | ||
const [userChoices, setSettings] = React.useState<UserChoices>( | ||
loadUserChoices(options.defaults, options.preventLoad ?? false), | ||
); | ||
|
||
const saveAudioInputEnabled = React.useCallback((isEnabled: boolean) => { | ||
setSettings((prev) => ({ ...prev, audioInputEnabled: isEnabled })); | ||
}, []); | ||
const saveVideoInputEnabled = React.useCallback((isEnabled: boolean) => { | ||
setSettings((prev) => ({ ...prev, videoInputEnabled: isEnabled })); | ||
}, []); | ||
const saveAudioInputDeviceId = React.useCallback((deviceId: string) => { | ||
setSettings((prev) => ({ ...prev, audioInputDeviceId: deviceId })); | ||
}, []); | ||
const saveVideoInputDeviceId = React.useCallback((deviceId: string) => { | ||
setSettings((prev) => ({ ...prev, videoInputDeviceId: deviceId })); | ||
}, []); | ||
|
||
React.useEffect(() => { | ||
saveUserChoices(userChoices, options.preventSave ?? false); | ||
}, [userChoices, options.preventSave]); | ||
|
||
return { | ||
userChoices, | ||
saveAudioInputEnabled, | ||
saveVideoInputEnabled, | ||
saveAudioInputDeviceId, | ||
saveVideoInputDeviceId, | ||
}; | ||
} |
Oops, something went wrong.