refactor(schema): simplify validation and OpenAPI generation (#2709)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-11 23:24:26 +02:00
1 parent 4bc5593f9f
commit de1fd95a99
510 files changed
+29494 -36131

No files matched your search

@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {StickerAnimationOptions} from '@fluxer/constants/src/UserConstants';
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export type StickerAnimation = number;
export type AnimatedMediaKind = 'emoji' | 'gif' | 'sticker';
@@ -388,7 +388,7 @@ export type MotionPreferencesSnapshot = SnapshotFrom<typeof motionPreferencesMac
export type MotionPreferencesStateValue = 'full' | 'reduced';
export function createMotionPreferencesSnapshot(input: MotionPreferencesInput = {}): MotionPreferencesSnapshot {
return getInitialSnapshot(motionPreferencesMachine, input);
return initialTransition(motionPreferencesMachine, input)[0];
}
export function transitionMotionPreferencesSnapshot(
@@ -99,7 +99,7 @@ const RootComponent = React.forwardRef<HTMLDivElement, ModalProps>(
if (typeof ref === 'function') {
ref(node);
} else if (ref) {
(ref as React.MutableRefObject<HTMLDivElement | null>).current = node;
ref.current = node;
}
},
[ref],
@@ -129,7 +129,7 @@ export const SettingsModalDesktopScroll: React.FC<SettingsModalDesktopScrollProp
if (typeof scrollerRef === 'function') {
scrollerRef(node);
} else {
(scrollerRef as React.MutableRefObject<HTMLElement | null>).current = node;
scrollerRef.current = node;
}
});
return (
@@ -423,11 +423,11 @@ function resolvePinnedCallChannel(directMessagesDisabled: boolean): Channel | nu
interface DMChannelVisibilityControllerState {
readonly removalDelayMs: number;
readonly removalTimers: React.MutableRefObject<Map<string, number>>;
readonly removalTimers: React.RefObject<Map<string, number>>;
readonly orderedUnreadChannels: ReadonlyArray<Channel>;
readonly setVisibleChannels: React.Dispatch<React.SetStateAction<ReadonlyArray<Channel>>>;
readonly unreadChannels: ReadonlyArray<Channel>;
readonly unreadIdsRef: React.MutableRefObject<ReadonlySet<string>>;
readonly unreadIdsRef: React.RefObject<ReadonlySet<string>>;
}
interface ProjectVisibleDMChannelsRequest {
@@ -437,11 +437,11 @@ interface ProjectVisibleDMChannelsRequest {
class DMChannelVisibilityController {
private readonly removalDelayMs: number;
private readonly removalTimers: React.MutableRefObject<Map<string, number>>;
private readonly removalTimers: React.RefObject<Map<string, number>>;
private readonly orderedUnreadChannels: ReadonlyArray<Channel>;
private readonly setVisibleChannels: React.Dispatch<React.SetStateAction<ReadonlyArray<Channel>>>;
private readonly unreadChannels: ReadonlyArray<Channel>;
private readonly unreadIdsRef: React.MutableRefObject<ReadonlySet<string>>;
private readonly unreadIdsRef: React.RefObject<ReadonlySet<string>>;
public constructor({
removalDelayMs,
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export type ScrollIndicatorSeverity = 'mention' | 'unread';
export type ScrollIndicatorDirection = 'top' | 'bottom';
@@ -238,7 +238,7 @@ export type ScrollIndicatorMachineSnapshot = SnapshotFrom<typeof scrollIndicator
export type ScrollIndicatorStateValue = 'hidden' | 'visible';
export function createScrollIndicatorSnapshot(input: ScrollIndicatorMachineInput = {}): ScrollIndicatorMachineSnapshot {
return getInitialSnapshot(scrollIndicatorStateMachine, input);
return initialTransition(scrollIndicatorStateMachine, input)[0];
}
export function transitionScrollIndicatorSnapshot(
@@ -3,7 +3,7 @@
import {type DragItem, DragItemType, type DropResult} from '@app/features/app/components/layout/types/DndTypes';
import {getVerticalDropTargetHeight, resolveVerticalDropEdge} from '@app/features/ui/dnd/DropGeometry';
import {ChannelTypes} from '@fluxer/constants/src/ChannelConstants';
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export interface ChannelReorderPoint {
x: number;
@@ -244,7 +244,7 @@ export type ChannelReorderSnapshot = SnapshotFrom<typeof channelReorderStateMach
export type ChannelReorderStateValue = 'idle' | 'resolving' | 'targeting' | 'voiceParticipantTransfer' | 'blocked';
export function createChannelReorderSnapshot(): ChannelReorderSnapshot {
return getInitialSnapshot(channelReorderStateMachine);
return initialTransition(channelReorderStateMachine)[0];
}
export function transitionChannelReorderSnapshot(
@@ -18,7 +18,7 @@ import {
import {RelativePosition} from '@app/features/ui/RelativePosition';
import {
assign,
getInitialSnapshot,
initialTransition,
type MachineSnapshot,
type MetaObject,
type NonReducibleUnknown,
@@ -380,7 +380,7 @@ export type GuildReorderSnapshot = MachineSnapshot<
>;
export function createGuildReorderSnapshot(): GuildReorderSnapshot {
return getInitialSnapshot(guildReorderStateMachine);
return initialTransition(guildReorderStateMachine)[0];
}
export function transitionGuildReorderSnapshot({
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {MessageNotifications} from '@fluxer/constants/src/NotificationConstants';
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
import type {ChannelUnreadState, ChannelUnreadStateInput} from './ChannelUnreadState';
interface ChannelUnreadMachineContext extends ChannelUnreadStateInput {}
@@ -74,7 +74,7 @@ export const channelUnreadStateMachine = setup({
export type ChannelUnreadSnapshot = SnapshotFrom<typeof channelUnreadStateMachine>;
export function createChannelUnreadSnapshot(input: ChannelUnreadStateInput): ChannelUnreadSnapshot {
return getInitialSnapshot(channelUnreadStateMachine, input);
return initialTransition(channelUnreadStateMachine, input)[0];
}
export function transitionChannelUnreadSnapshot(
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export type WizardStep =
| 'welcome'
@@ -157,7 +157,7 @@ export const setupWizardStateMachine = setup({
export type SetupWizardSnapshot = SnapshotFrom<typeof setupWizardStateMachine>;
export function createSetupWizardSnapshot(): SetupWizardSnapshot {
return getInitialSnapshot(setupWizardStateMachine);
return initialTransition(setupWizardStateMachine)[0];
}
export function transitionSetupWizardSnapshot(
@@ -127,8 +127,7 @@ export const FriendSelector: React.FC<FriendSelectorProps> = observer(
const isMaxed = maxSelections !== undefined && selectedUserIds.length >= maxSelections;
const isMutableRefObject = (
ref: React.Ref<HTMLInputElement> | undefined,
): ref is React.MutableRefObject<HTMLInputElement | null> =>
typeof ref === 'object' && ref !== null && 'current' in ref;
): ref is React.RefObject<HTMLInputElement | null> => typeof ref === 'object' && ref !== null && 'current' in ref;
const renderSearchInput = ({inputProps, inputClassName, ref: forwardedRef}: RenderInputArgs) => {
const handleRef = (node: HTMLInputElement | null) => {
inputRef.current = node;
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export type HoverStateValue = 'idle' | 'hovering';
@@ -32,7 +32,7 @@ export const hoverStateMachine = setup({
export type HoverStateSnapshot = SnapshotFrom<typeof hoverStateMachine>;
export function createHoverStateSnapshot(): HoverStateSnapshot {
return getInitialSnapshot(hoverStateMachine);
return initialTransition(hoverStateMachine)[0];
}
export function transitionHoverStateSnapshot(snapshot: HoverStateSnapshot, event: HoverStateEvent): HoverStateSnapshot {
+3 -3
View File
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {FormEvent} from 'react';
import type {SubmitEvent} from 'react';
import {useCallback, useLayoutEffect, useState} from 'react';
interface FormField {
@@ -33,7 +33,7 @@ export interface UseFormReturn {
setErrors: (errors: ReadonlyMap<string, string>) => void;
getValue: (fieldName: string) => string;
getError: (fieldName: string) => string | undefined;
handleSubmit: (event?: FormEvent) => Promise<void>;
handleSubmit: (event?: SubmitEvent<HTMLFormElement>) => Promise<void>;
isSubmitting: boolean;
}
@@ -93,7 +93,7 @@ export function useForm({initialValues, onSubmit}: UseFormOptions): UseFormRetur
const getValue = useCallback((fieldName: string): string => fields.get(fieldName)?.value ?? '', [fields]);
const getError = useCallback((fieldName: string): string | undefined => fields.get(fieldName)?.error, [fields]);
const handleSubmit = useCallback(
async (event?: FormEvent) => {
async (event?: SubmitEvent<HTMLFormElement>) => {
event?.preventDefault();
if (lifecycle.activeSubmission != null) {
return;
@@ -1,10 +1,8 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {type LegacyRef, type MutableRefObject, type RefCallback, useCallback, useRef} from 'react';
import {type Ref, type RefCallback, useCallback, useRef} from 'react';
export function useMergeRefs<T>(
refs: Array<MutableRefObject<T | null> | LegacyRef<T> | undefined | null>,
): RefCallback<T> {
export function useMergeRefs<T>(refs: Array<Ref<T> | undefined>): RefCallback<T> {
const latestRefs = useRef(refs);
latestRefs.current = refs;
return useCallback((value: T | null) => {
@@ -12,7 +10,7 @@ export function useMergeRefs<T>(
if (typeof ref === 'function') {
ref(value);
} else if (ref != null) {
(ref as MutableRefObject<T | null>).current = value;
ref.current = value;
}
}
}, []);
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export type ReviewStep = 'account' | 'scopes' | 'community' | 'permissions';
export type AuthorizePhase =
@@ -80,7 +80,7 @@ export type AuthorizeMachineSnapshot = SnapshotFrom<typeof authorizeStateMachine
export type AuthorizeMachineStateValue = 'loading' | 'session_expired' | 'invalid_request' | 'review' | 'success';
export function createAuthorizeSnapshot(): AuthorizeMachineSnapshot {
return getInitialSnapshot(authorizeStateMachine);
return initialTransition(authorizeStateMachine)[0];
}
export function transitionAuthorizeSnapshot(
@@ -10,7 +10,7 @@ import {useId} from 'react';
type FieldErrors = ReadonlyMap<string, string> | null | undefined;
export interface AuthFormControllerLike {
handleSubmit: (event: React.FormEvent<HTMLFormElement>) => void;
handleSubmit: (event: React.SubmitEvent<HTMLFormElement>) => void;
getValue: (name: string) => string;
setValue: (name: string, value: string) => void;
getError: (name: string) => string | null | undefined;
@@ -25,7 +25,7 @@ export const AutocompleteChannel = observer(
options: Array<AutocompleteOption>;
onMouseEnter: (index: number) => void;
onMouseLeave: () => void;
rowRefs?: React.MutableRefObject<Array<HTMLButtonElement | null>>;
rowRefs?: React.RefObject<Array<HTMLButtonElement | null>>;
getOptionId?: (index: number) => string;
}) => {
const channels = options.filter(isChannel);
@@ -3,7 +3,7 @@
import {type AutocompleteOption, isCommand} from '@app/features/channel/components/Autocomplete';
import {AutocompleteItem} from '@app/features/channel/components/AutocompleteItem';
import {observer} from 'mobx-react-lite';
import type {MutableRefObject} from 'react';
import type {RefObject} from 'react';
interface Props {
onSelect: (option: AutocompleteOption) => void;
@@ -12,7 +12,7 @@ interface Props {
options: Array<AutocompleteOption>;
onMouseEnter: (index: number) => void;
onMouseLeave: () => void;
rowRefs?: MutableRefObject<Array<HTMLButtonElement | null>>;
rowRefs?: RefObject<Array<HTMLButtonElement | null>>;
getOptionId?: (index: number) => string;
}
@@ -11,7 +11,7 @@ interface AutocompleteCommandChoiceProps {
options: Array<AutocompleteOption>;
onMouseEnter: (index: number) => void;
onMouseLeave: () => void;
rowRefs?: React.MutableRefObject<Array<HTMLButtonElement | null>>;
rowRefs?: React.RefObject<Array<HTMLButtonElement | null>>;
getOptionId?: (index: number) => string;
}
@@ -11,7 +11,7 @@ interface AutocompleteCommandOptionalAddProps {
options: Array<AutocompleteOption>;
onMouseEnter: (index: number) => void;
onMouseLeave: () => void;
rowRefs?: React.MutableRefObject<Array<HTMLButtonElement | null>>;
rowRefs?: React.RefObject<Array<HTMLButtonElement | null>>;
getOptionId?: (index: number) => string;
}
@@ -97,7 +97,7 @@ export const AutocompleteEmoji = observer(
options: Array<AutocompleteOption>;
onMouseEnter: (index: number) => void;
onMouseLeave: () => void;
rowRefs?: React.MutableRefObject<Array<HTMLButtonElement | null>>;
rowRefs?: React.RefObject<Array<HTMLButtonElement | null>>;
getOptionId?: (index: number) => string;
}) => {
const {i18n} = useLingui();
@@ -85,7 +85,7 @@ export const AutocompleteGif = observer(
options: Array<AutocompleteOption>;
onMouseEnter: (index: number) => void;
onMouseLeave: () => void;
rowRefs?: React.MutableRefObject<Array<HTMLButtonElement | null>>;
rowRefs?: React.RefObject<Array<HTMLButtonElement | null>>;
getOptionId?: (index: number) => string;
}) => {
const {i18n} = useLingui();
@@ -24,7 +24,7 @@ export const AutocompleteMeme = observer(
options: Array<AutocompleteOption>;
onMouseEnter: (index: number) => void;
onMouseLeave: () => void;
rowRefs?: React.MutableRefObject<Array<HTMLButtonElement | null>>;
rowRefs?: React.RefObject<Array<HTMLButtonElement | null>>;
getOptionId?: (index: number) => string;
}) => {
const memes = options.filter(isMeme);
@@ -37,7 +37,7 @@ const NOTIFY_USERS_WITH_THIS_ROLE_WHO_HAVE_PERMISSION_DESCRIPTOR = msg({
comment: 'Description text in the channel and chat autocomplete mention. Keep the tone plain and specific.',
});
type RowRef = React.MutableRefObject<Array<HTMLButtonElement | null>> | undefined;
type RowRef = React.RefObject<Array<HTMLButtonElement | null>> | undefined;
type OptionIdGetter = ((index: number) => string) | undefined;
function resolveOptionId(getOptionId: OptionIdGetter, index: number): string | undefined {
@@ -76,7 +76,7 @@ export const AutocompleteMention = observer(function AutocompleteMention({
options: Array<AutocompleteOption>;
onMouseEnter: (index: number) => void;
onMouseLeave: () => void;
rowRefs?: React.MutableRefObject<Array<HTMLButtonElement | null>>;
rowRefs?: React.RefObject<Array<HTMLButtonElement | null>>;
getOptionId?: (index: number) => string;
}) {
const {i18n} = useLingui();
@@ -43,7 +43,7 @@ export const AutocompleteSticker = observer(
options: Array<AutocompleteOption>;
onMouseEnter: (index: number) => void;
onMouseLeave: () => void;
rowRefs?: React.MutableRefObject<Array<HTMLButtonElement | null>>;
rowRefs?: React.RefObject<Array<HTMLButtonElement | null>>;
getOptionId?: (index: number) => string;
}) => {
const stickers = options.filter(isSticker);
@@ -2,7 +2,7 @@
import type {Channel} from '@app/features/channel/models/Channel';
import type {Message} from '@app/features/messaging/models/MessagingMessage';
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export interface SearchMachineStateIdle {
status: 'idle';
@@ -132,7 +132,7 @@ export const searchStateMachine = setup({
export type SearchMachineSnapshot = SnapshotFrom<typeof searchStateMachine>;
export function createSearchMachineSnapshot(): SearchMachineSnapshot {
return getInitialSnapshot(searchStateMachine);
return initialTransition(searchStateMachine)[0];
}
export function transitionSearchMachineSnapshot(
@@ -123,7 +123,7 @@ export const AddFriendForm: React.FC<AddFriendFormProps> = observer(({onSuccess}
}
return getSendFriendRequestErrorMessage(i18n, errorCode, null);
};
const handleSubmit = (e: React.FormEvent) => {
const handleSubmit = (e: React.SubmitEvent<HTMLFormElement>) => {
e.preventDefault();
const [username, discriminator] = parseInput(input['trim']());
if (!username || !discriminator || !/^\d{4}$/.test(discriminator)) {
@@ -34,7 +34,7 @@ interface VirtualRowRendererProps {
selectedColumnInRow: number;
emojiRowIndex: number;
shouldScrollOnSelection?: boolean;
emojiRefs: React.MutableRefObject<Map<string, HTMLButtonElement>>;
emojiRefs: React.RefObject<Map<string, HTMLButtonElement>>;
}
const VirtualRowRenderer: React.FC<VirtualRowRendererProps> = React.memo(
@@ -208,7 +208,7 @@ interface VirtualizedRowProps {
selectedColumn: number;
emojiRowIndex: number;
shouldScrollOnSelection?: boolean;
emojiRefs: React.MutableRefObject<Map<string, HTMLButtonElement>>;
emojiRefs: React.RefObject<Map<string, HTMLButtonElement>>;
}
export const VirtualizedRow: React.FC<VirtualizedRowProps> = observer(
@@ -33,7 +33,7 @@ export interface MembersPaginationProps {
isSearching: boolean;
activeEllipsis: PaginationEllipsisSide | null;
pageJumpValue: string;
ellipsisInputRef: React.MutableRefObject<HTMLInputElement | null>;
ellipsisInputRef: React.RefObject<HTMLInputElement | null>;
onPageSelect: (page: number) => void;
setActiveEllipsis: React.Dispatch<React.SetStateAction<PaginationEllipsisSide | null>>;
setPageJumpValue: React.Dispatch<React.SetStateAction<string>>;
@@ -57,7 +57,7 @@ export function MembersPagination({
setPageJumpValue('');
}, [setActiveEllipsis, setPageJumpValue]);
const handleJumpSubmit = useCallback(
(event: React.FormEvent<HTMLFormElement>) => {
(event: React.SubmitEvent<HTMLFormElement>) => {
event.preventDefault();
const nextPage = parseInt(pageJumpValue, 10);
if (!Number.isNaN(nextPage) && nextPage >= 1 && nextPage <= totalPages) {
@@ -21,7 +21,7 @@ export interface MembersTableFooterProps {
isSearching: boolean;
activeEllipsis: PaginationEllipsisSide | null;
pageJumpValue: string;
ellipsisInputRef: React.MutableRefObject<HTMLInputElement | null>;
ellipsisInputRef: React.RefObject<HTMLInputElement | null>;
onPageSelect: (page: number) => void;
setActiveEllipsis: React.Dispatch<React.SetStateAction<PaginationEllipsisSide | null>>;
setPageJumpValue: React.Dispatch<React.SetStateAction<string>>;
@@ -254,7 +254,7 @@ export function assignRef<T>(ref: React.Ref<T> | undefined, value: T | null): vo
ref(value);
return;
}
(ref as React.MutableRefObject<T | null>).current = value;
ref.current = value;
}
export function normalizeFilterKey(filterKey: string): string {
@@ -90,10 +90,7 @@ const UNNAMED_DESCRIPTOR = msg({
comment: 'Short label in the channel and chat use message search autocomplete. Keep it concise.',
});
function resolveInputCursorPosition(
inputRef: React.MutableRefObject<LexicalSearchInputHandle | null>,
value: string,
): number {
function resolveInputCursorPosition(inputRef: React.RefObject<LexicalSearchInputHandle | null>, value: string): number {
const input = inputRef.current;
if (input == null) {
return value.length;
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {View} from '@app/features/channel/components/pickers/gif/GifPickerTypes';
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
declare const gifPickerRequestIdBrand: unique symbol;
@@ -576,7 +576,7 @@ export const gifPickerStateMachine = setup({
});
export function createGifPickerSnapshot(): GifPickerMachineSnapshot {
return getInitialSnapshot(gifPickerStateMachine);
return initialTransition(gifPickerStateMachine)[0];
}
export function transitionGifPickerSnapshot(
@@ -59,7 +59,7 @@ const assignRef = <T,>(ref: React.Ref<T> | null | undefined, value: T | null) =>
ref(value);
return;
}
(ref as React.MutableRefObject<T | null>).current = value;
ref.current = value;
};
export const PickerSearchInput = React.forwardRef<HTMLInputElement, PickerSearchInputProps>(
(
@@ -32,7 +32,7 @@ interface VirtualRowRendererProps {
selectedColumn: number;
stickerRowIndex: number;
shouldScrollOnSelection?: boolean;
stickerRefs: React.MutableRefObject<Map<string, HTMLButtonElement>>;
stickerRefs: React.RefObject<Map<string, HTMLButtonElement>>;
channel?: Channel | null;
}
@@ -40,7 +40,7 @@ interface StickerButtonProps {
sticker: GuildSticker;
stickerKey: string;
isSelected: boolean;
stickerRefs: React.MutableRefObject<Map<string, HTMLButtonElement>>;
stickerRefs: React.RefObject<Map<string, HTMLButtonElement>>;
shouldScrollOnSelection?: boolean;
handleStickerClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
handleContextMenu: (event: React.MouseEvent<HTMLButtonElement>) => void;
@@ -296,7 +296,7 @@ interface VirtualRowWrapperProps {
selectedColumn: number;
stickerRowIndex: number;
shouldScrollOnSelection?: boolean;
stickerRefs: React.MutableRefObject<Map<string, HTMLButtonElement>>;
stickerRefs: React.RefObject<Map<string, HTMLButtonElement>>;
channel?: Channel | null;
}
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {hasVisibleMessageContent} from '@app/features/messaging/utils/VisibleMessageContent';
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export type MobileTextareaVisibleButton = 'send' | 'voice';
export type MobileTextareaButtonMode = 'voice' | 'sendReady' | 'sendBlocked';
@@ -144,7 +144,7 @@ export const mobileTextareaButtonStateMachine = setup({
export type MobileTextareaButtonSnapshot = SnapshotFrom<typeof mobileTextareaButtonStateMachine>;
export function createMobileTextareaButtonSnapshot(): MobileTextareaButtonSnapshot {
return getInitialSnapshot(mobileTextareaButtonStateMachine);
return initialTransition(mobileTextareaButtonStateMachine)[0];
}
export function transitionMobileTextareaButtonSnapshot(
@@ -36,7 +36,7 @@ interface TextareaInputFieldProps {
placeholder: string;
textareaRef: React.RefObject<HTMLTextAreaElement | null>;
scrollerRef?: React.RefObject<ScrollerHandle | null>;
shouldStickToBottomRef?: React.MutableRefObject<boolean>;
shouldStickToBottomRef?: React.RefObject<boolean>;
isFocused?: boolean;
isAutocompleteAttached: boolean;
autocompleteListId?: string;
@@ -16,7 +16,7 @@ import {useLingui} from '@lingui/react/macro';
import {ArrowLeftIcon, MagnifyingGlassIcon, XIcon} from '@phosphor-icons/react';
import {clsx} from 'clsx';
import {observer} from 'mobx-react-lite';
import {type FormEvent, type KeyboardEvent, useCallback, useEffect, useRef, useState} from 'react';
import {type KeyboardEvent, type SubmitEvent, useCallback, useEffect, useRef, useState} from 'react';
const ALL_DESCRIPTOR = msg({
message: 'All',
@@ -155,7 +155,7 @@ export const DiscoveryNavbar = observer(function DiscoveryNavbar({
[onCategorySelect],
);
const handleSubmit = useCallback(
(event: FormEvent<HTMLFormElement>) => {
(event: SubmitEvent<HTMLFormElement>) => {
event.preventDefault();
const trimmedQuery = draftQuery.trim();
setDraftQuery(trimmedQuery);
@@ -47,29 +47,6 @@ const CANARY_ENVELOPE: ExperimentAssignmentsResponse = {
assignments: {voice_noise_suppression: CANARY_ASSIGNMENT},
};
interface ScheduleRecorder {
delays: Array<number>;
restore: () => void;
}
function recordScheduledDelays(): ScheduleRecorder {
const delays: Array<number> = [];
const original = globalThis.setTimeout;
const patched = (...args: Array<unknown>) => {
const timeout = args[1];
delays.push(typeof timeout === 'number' ? timeout : 0);
return (original as unknown as (...callArgs: Array<unknown>) => NodeJS.Timeout)(...args);
};
globalThis.setTimeout = patched as unknown as typeof globalThis.setTimeout;
return {
delays,
restore: () => {
globalThis.setTimeout = original;
},
};
}
let recorder: ScheduleRecorder;
let visibility: DocumentVisibilityState = 'visible';
const unsubscribes: Array<() => void> = [];
@@ -82,7 +59,9 @@ function requestHeaders(index: number): Record<string, string> {
}
function lastScheduledDelayMs(): number {
return recorder.delays[recorder.delays.length - 1] ?? Number.NaN;
const call = vi.mocked(setTimeout).mock.calls.at(-1);
expect(call).toBeDefined();
return call![1]!;
}
function setVisibility(next: DocumentVisibilityState): void {
@@ -91,9 +70,7 @@ function setVisibility(next: DocumentVisibilityState): void {
}
async function settle(): Promise<void> {
for (let index = 0; index < 4; index++) {
await vi.advanceTimersByTimeAsync(0);
}
await vi.advanceTimersByTimeAsync(0);
}
function subscribe(listener: () => void): () => void {
@@ -107,16 +84,12 @@ function voiceConfigVersion(): number {
}
function deferredReply(): {resolve: (response: RestResponse<unknown>) => void} {
let resolve: (response: RestResponse<unknown>) => void = () => {};
const pending = new Promise<RestResponse<unknown>>((settleWith) => {
resolve = settleWith;
let resolve!: (response: RestResponse<unknown>) => void;
const pending = new Promise<RestResponse<unknown>>((complete) => {
resolve = complete;
});
vi.mocked(http.get).mockReturnValue(pending);
return {
resolve: (response) => {
resolve(response);
},
};
vi.mocked(http.get).mockReturnValueOnce(pending);
return {resolve};
}
async function adopt(response: ExperimentAssignmentsResponse): Promise<void> {
@@ -129,7 +102,8 @@ beforeEach(() => {
visibility = 'visible';
Object.defineProperty(document, 'visibilityState', {configurable: true, get: () => visibility});
vi.useFakeTimers();
recorder = recordScheduledDelays();
vi.spyOn(globalThis, 'setTimeout');
vi.spyOn(Math, 'random').mockReturnValue(0.5);
vi.mocked(http.get).mockReset();
});
@@ -138,9 +112,8 @@ afterEach(() => {
unsubscribe();
}
ExperimentAssignments.reset();
recorder.restore();
vi.useRealTimers();
vi.restoreAllMocks();
vi.useRealTimers();
});
describe('ExperimentAssignments cold start', () => {
@@ -217,40 +190,20 @@ describe('ExperimentAssignments response handling', () => {
});
describe('ExperimentAssignments scheduling', () => {
it('takes the poll interval from the envelope and jitters it by fifteen percent', async () => {
const observed: Array<number> = [];
for (let run = 0; run < 64; run++) {
ExperimentAssignments.reset();
await adopt({...CANARY_ENVELOPE, poll_interval_seconds: 600});
observed.push(lastScheduledDelayMs());
}
for (const delay of observed) {
expect(delay).toBeGreaterThanOrEqual(510_000);
expect(delay).toBeLessThanOrEqual(690_000);
}
expect(new Set(observed).size).toBeGreaterThan(1);
});
it('takes the jitter ratio from the envelope', async () => {
const observed: Array<number> = [];
for (let run = 0; run < 64; run++) {
ExperimentAssignments.reset();
await adopt({...CANARY_ENVELOPE, poll_interval_seconds: 600, poll_jitter_percent: 50});
observed.push(lastScheduledDelayMs());
}
for (const delay of observed) {
expect(delay).toBeGreaterThanOrEqual(300_000);
expect(delay).toBeLessThanOrEqual(900_000);
}
expect(observed.some((delay) => delay < 510_000 || delay > 690_000)).toBe(true);
});
it('schedules an exact interval when the server disables jitter', async () => {
for (let run = 0; run < 8; run++) {
ExperimentAssignments.reset();
await adopt({...CANARY_ENVELOPE, poll_interval_seconds: 600, poll_jitter_percent: 0});
expect(lastScheduledDelayMs()).toBe(600_000);
}
it.each([
{jitter: 15, random: 0, delay: 510_000},
{jitter: 15, random: 0.5, delay: 600_000},
{jitter: 15, random: 0.999999, delay: 690_000},
{jitter: 50, random: 0, delay: 300_000},
{jitter: 50, random: 0.5, delay: 600_000},
{jitter: 50, random: 0.999999, delay: 899_999},
{jitter: 0, random: 0, delay: 600_000},
{jitter: 0, random: 0.5, delay: 600_000},
{jitter: 0, random: 0.999999, delay: 600_000},
])('schedules $delay ms for $jitter% jitter and random value $random', async ({jitter, random, delay}) => {
vi.mocked(Math.random).mockReturnValue(random);
await adopt({...CANARY_ENVELOPE, poll_interval_seconds: 600, poll_jitter_percent: jitter});
expect(lastScheduledDelayMs()).toBe(delay);
});
it('never schedules a poll below half the minimum interval', async () => {
@@ -379,6 +332,58 @@ describe('ExperimentAssignments visibility', () => {
});
describe('ExperimentAssignments lifecycle', () => {
it.each([
'stop',
'reset',
] as const)('aborts an active request on %s and starts a fresh request immediately', async (method) => {
const stale = deferredReply();
ExperimentAssignments.start();
await settle();
const signal = vi.mocked(http.get).mock.calls[0]?.[1]?.signal;
expect(signal).toBeDefined();
expect(signal!.aborted).toBe(false);
ExperimentAssignments[method]();
expect(signal!.aborted).toBe(true);
vi.mocked(http.get).mockResolvedValue(reply(200, CANARY_ENVELOPE, {etag: 'W/"fresh"'}));
ExperimentAssignments.start();
await settle();
expect(http.get).toHaveBeenCalledTimes(2);
expect(ExperimentAssignments.response).toEqual(CANARY_ENVELOPE);
expect(vi.getTimerCount()).toBe(1);
stale.resolve(reply(200, INERT_EXPERIMENT_ASSIGNMENTS_RESPONSE, {etag: 'W/"stale"'}));
await settle();
expect(ExperimentAssignments.response).toEqual(CANARY_ENVELOPE);
expect(vi.getTimerCount()).toBe(1);
});
it('keeps ownership of the replacement request when the stale request finishes first', async () => {
const stale = deferredReply();
ExperimentAssignments.start();
ExperimentAssignments.stop();
const replacement = deferredReply();
ExperimentAssignments.start();
const signal = vi.mocked(http.get).mock.calls[1]?.[1]?.signal;
expect(signal).toBeDefined();
stale.resolve(reply(200, CANARY_ENVELOPE));
await settle();
await vi.advanceTimersByTimeAsync(31_000);
setVisibility('hidden');
setVisibility('visible');
await settle();
expect(http.get).toHaveBeenCalledTimes(2);
expect(signal!.aborted).toBe(false);
ExperimentAssignments.stop();
expect(signal!.aborted).toBe(true);
replacement.resolve(reply(200, CANARY_ENVELOPE));
await settle();
expect(ExperimentAssignments.response).toBe(INERT_EXPERIMENT_ASSIGNMENTS_RESPONSE);
expect(vi.getTimerCount()).toBe(0);
});
it('discards a response that lands after reset', async () => {
const deferred = deferredReply();
ExperimentAssignments.start();
@@ -25,6 +25,7 @@ const MAX_BACKOFF_DOUBLINGS = 2;
const EXPERIMENT_ASSIGNMENTS_MIN_POLL_DELAY_MS = (EXPERIMENT_MIN_POLL_INTERVAL_SECONDS * MS_PER_SECOND) / 2;
type UnobservedField =
| 'abortController'
| 'etag'
| 'failureStreak'
| 'generation'
@@ -69,6 +70,7 @@ class ExperimentAssignmentsStore {
private listening = false;
private pollTimerId: NodeJS.Timeout | null = null;
private inFlight: Promise<void> | null = null;
private abortController: AbortController | null = null;
private failureStreak = 0;
private notFoundStreak = 0;
private pollingDisabled = false;
@@ -81,6 +83,7 @@ class ExperimentAssignmentsStore {
this,
{
response: observable.ref,
abortController: false,
etag: false,
failureStreak: false,
generation: false,
@@ -117,6 +120,10 @@ class ExperimentAssignmentsStore {
stop(): void {
this.started = false;
this.generation += 1;
const controller = this.abortController;
this.abortController = null;
this.inFlight = null;
controller?.abort();
try {
this.removeVisibilityListener();
} catch (err) {
@@ -128,7 +135,6 @@ class ExperimentAssignmentsStore {
reset(): void {
this.stop();
this.etag = null;
this.inFlight = null;
this.failureStreak = 0;
this.notFoundStreak = 0;
this.pollingDisabled = false;
@@ -198,16 +204,19 @@ class ExperimentAssignmentsStore {
if (existing !== null) {
return existing;
}
const pending = this.fetchAssignments(this.generation).finally(() => {
const controller = new AbortController();
this.abortController = controller;
const pending = this.fetchAssignments(this.generation, controller.signal).finally(() => {
if (this.inFlight === pending) {
this.inFlight = null;
this.abortController = null;
}
});
this.inFlight = pending;
return pending;
}
private async fetchAssignments(generation: number): Promise<void> {
private async fetchAssignments(generation: number, signal: AbortSignal): Promise<void> {
this.lastFetchStartedAt = Date.now();
const headers: Record<string, string> = {};
if (this.etag !== null) {
@@ -218,6 +227,7 @@ class ExperimentAssignmentsStore {
headers,
mode: 'silent',
parse: 'json',
signal,
});
if (generation !== this.generation) {
return;
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export interface RoleReorderPoint {
x: number;
@@ -275,7 +275,7 @@ export type RoleReorderSnapshot = SnapshotFrom<typeof roleReorderStateMachine>;
export type RoleReorderStateValue = 'idle' | 'resolving' | 'targeting' | 'blocked';
export function createRoleReorderSnapshot(): RoleReorderSnapshot {
return getInitialSnapshot(roleReorderStateMachine);
return initialTransition(roleReorderStateMachine)[0];
}
export function transitionRoleReorderSnapshot(
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {MessageNotifications} from '@fluxer/constants/src/NotificationConstants';
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export interface GuildReadStateContributionInput {
isEligibleTextChannel: boolean;
@@ -110,7 +110,7 @@ export type GuildReadStateContributionSnapshot = SnapshotFrom<typeof guildReadSt
export function createGuildReadStateContributionSnapshot(
input: GuildReadStateContributionInput,
): GuildReadStateContributionSnapshot {
return getInitialSnapshot(guildReadStateContributionMachine, input);
return initialTransition(guildReadStateContributionMachine, input)[0];
}
export function transitionGuildReadStateContributionSnapshot(
@@ -1,12 +1,17 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import {registerComposerEnter} from '@app/features/lexical/composer/ComposerEnter';
import {$replaceComposerRange, $selectComposerRange} from '@app/features/lexical/composer/composerOffsets';
import {ComposerMentionNode} from '@app/features/lexical/composer/nodes/ComposerMentionNode';
import {ComposerPlainSegmentNode} from '@app/features/lexical/composer/nodes/ComposerPlainSegmentNode';
import {$createSlashSlotNode, SlashSlotNode} from '@app/features/lexical/composer/nodes/SlashSlotNode';
import {
$createSlashSlotNode,
$isSlashSlotNode,
SlashSlotNode,
} from '@app/features/lexical/composer/nodes/SlashSlotNode';
import {SlashSlotPlaceholderNode} from '@app/features/lexical/composer/nodes/SlashSlotPlaceholderNode';
import {$getRoot, $setSelection, createEditor, type ElementNode, KEY_ENTER_COMMAND, type LexicalEditor} from 'lexical';
import {$getRoot, $isElementNode, $setSelection, createEditor, KEY_ENTER_COMMAND, type LexicalEditor} from 'lexical';
import {describe, expect, it, vi} from 'vitest';
vi.mock('@app/features/lexical/composer/nodes/ComposerMentionPill', () => ({ComposerMentionPill: () => null}));
@@ -45,16 +50,22 @@ function typedWithRequiredSlot(text: string, offset: number): LexicalEditor {
return createComposer(() => {
$replaceComposerRange(0, 0, {kind: 'text', text}, {leading: false, trailing: false});
const slot = $createSlashSlotNode('value', 'string', true);
$getRoot().getFirstChildOrThrow<ElementNode>().append(slot);
const paragraph = $getRoot().getFirstChildOrThrow();
assert($isElementNode(paragraph), 'Expected composer paragraph');
paragraph.append(slot);
slot.ensurePlaceholder();
$selectComposerRange(offset, offset);
});
}
function slotValidity(editor: LexicalEditor): string {
return editor.read(() =>
$getRoot().getFirstChildOrThrow<ElementNode>().getLastChildOrThrow<SlashSlotNode>().getValidity(),
);
return editor.read(() => {
const paragraph = $getRoot().getFirstChildOrThrow();
assert($isElementNode(paragraph), 'Expected composer paragraph');
const slot = paragraph.getLastChildOrThrow();
assert($isSlashSlotNode(slot), 'Expected required slash command slot');
return slot.getValidity();
});
}
function pressEnter(
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import {registerComposerMarkdownHighlight} from '@app/features/lexical/composer/ComposerMarkdownHighlight';
import {$hydrateComposerFromDraft, $projectComposer} from '@app/features/lexical/composer/ComposerSerialization';
import {$getComposerSelectionRange, $selectComposerRange} from '@app/features/lexical/composer/composerOffsets';
@@ -24,7 +25,6 @@ import {
$isElementNode,
$isTextNode,
createEditor,
type ElementNode,
type LexicalEditor,
type RangeSelection,
} from 'lexical';
@@ -282,7 +282,9 @@ describe('registerComposerMarkdownHighlight with silentMessagePrefix', () => {
]);
expect(readLines(editor)).toEqual([[['@silent', SILENT_STYLE], [' ', ''], 'composer-mention']]);
const mention = editor.getEditorState().read(() => {
const pill = $getRoot().getFirstChildOrThrow<ElementNode>().getLastChild();
const paragraph = $getRoot().getFirstChildOrThrow();
assert($isElementNode(paragraph), 'Expected composer paragraph');
const pill = paragraph.getLastChild();
return $isComposerMentionNode(pill) ? {literal: pill.isLiteral(), presentation: pill.getPresentation()} : null;
});
expect(mention).toEqual({literal: false, presentation: ComposerMentionPresentation.none});
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import {
$getComposerClipboardSelection,
$insertComposerClipboardSlice,
@@ -42,6 +43,7 @@ import {
$createTextNode,
$getRoot,
$getSelection,
$isElementNode,
$isRangeSelection,
$setCompositionKey,
$setSelection,
@@ -142,7 +144,9 @@ function describeNode(node: LexicalNode): string {
}
function $paragraph(): ElementNode {
return $getRoot().getFirstChildOrThrow<ElementNode>();
const paragraph = $getRoot().getFirstChildOrThrow();
assert($isElementNode(paragraph), 'Expected composer paragraph');
return paragraph;
}
function children(editor: LexicalEditor): Array<string> {
@@ -517,9 +521,13 @@ describe('typed special mentions', () => {
'text:@everyone ',
'slash-slot:@here ',
]);
expect(editor.read(() => $paragraph().getLastChildOrThrow<ElementNode>().getChildren().map(describeNode))).toEqual([
'text:@here ',
]);
expect(
editor.read(() => {
const slot = $paragraph().getLastChildOrThrow();
assert($isElementNode(slot), 'Expected slash command slot element');
return slot.getChildren().map(describeNode);
}),
).toEqual(['text:@here ']);
});
it('restores a draft special mention and projects it back unchanged', () => {
@@ -37,7 +37,7 @@ export interface ComposerTypeaheadPluginProps {
listboxId: string;
enabled: boolean;
onSelect: (option: AutocompleteOption) => void;
activeRef: React.MutableRefObject<boolean>;
activeRef: React.RefObject<boolean>;
}
export const ComposerTypeaheadPlugin = ({
@@ -250,7 +250,7 @@ function SelectionToolbarSurface({
onFocusRequestHandled: () => void;
onDismiss: () => void;
toolbarRef: React.RefObject<HTMLElement | null>;
savedSelectionRef: React.MutableRefObject<ComposerSelectionOffsets | null>;
savedSelectionRef: React.RefObject<ComposerSelectionOffsets | null>;
}) {
const {i18n} = useLingui();
const [focusIndex, setFocusIndex] = useState(0);
@@ -33,7 +33,7 @@ export interface SlashSlotAutocompletePluginProps {
listboxId: string;
enabled: boolean;
onSelect: (option: AutocompleteOption) => void;
activeRef: React.MutableRefObject<boolean>;
activeRef: React.RefObject<boolean>;
}
export const SlashSlotAutocompletePlugin = ({
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import {registerComposerMarkdownHighlight} from '@app/features/lexical/composer/ComposerMarkdownHighlight';
import {type CodeBlockWrapPlan, planCodeBlockWrap} from '@app/features/lexical/composer/codeBlockWrap';
import {
@@ -25,8 +26,8 @@ import {createEmptyHistoryState, registerHistory} from '@lexical/history';
import {
$getNodeByKey,
$getRoot,
$isElementNode,
createEditor,
type ElementNode,
type LexicalEditor,
type NodeKey,
REDO_COMMAND,
@@ -307,13 +308,14 @@ function composerState(editor: LexicalEditor): {
}
function atomicKeys(editor: LexicalEditor): Array<NodeKey> {
return editor.read(() =>
$getRoot()
.getFirstChildOrThrow<ElementNode>()
return editor.read(() => {
const paragraph = $getRoot().getFirstChildOrThrow();
assert($isElementNode(paragraph), 'Expected composer paragraph');
return paragraph
.getChildren()
.filter((node) => $isComposerMentionNode(node) || $isComposerCustomEmojiNode(node))
.map((node) => node.getKey()),
);
.map((node) => node.getKey());
});
}
function atomicState(editor: LexicalEditor, keys: Array<NodeKey>): Array<{attached: boolean; literal: boolean} | null> {
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {normalizeMemberListRanges} from '@app/features/member/utils/MemberListRangeUtils';
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export interface MemberListViewportMachineInput {
hasReceivedInitialPayload?: boolean;
@@ -127,7 +127,7 @@ export const memberListViewportStateMachine = setup({
export function createMemberListViewportSnapshot(
input: MemberListViewportMachineInput = {},
): MemberListViewportMachineSnapshot {
return getInitialSnapshot(memberListViewportStateMachine, input);
return initialTransition(memberListViewportStateMachine, input)[0];
}
export function transitionMemberListViewportSnapshot(
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export type MessageFetchCacheHit = 'jump' | 'before' | 'after';
@@ -157,7 +157,7 @@ export type MessageFetchPreflightSnapshot = SnapshotFrom<typeof messageFetchPref
export type MessageFetchExecutionSnapshot = SnapshotFrom<typeof messageFetchExecutionMachine>;
export function createMessageFetchPreflightSnapshot(input: MessageFetchPreflightInput): MessageFetchPreflightSnapshot {
return getInitialSnapshot(messageFetchPreflightMachine, input);
return initialTransition(messageFetchPreflightMachine, input)[0];
}
export function transitionMessageFetchPreflightSnapshot(
@@ -178,7 +178,7 @@ export function resolveMessageFetchPreflightDecision(input: MessageFetchPrefligh
}
export function createMessageFetchExecutionSnapshot(input: MessageFetchExecutionInput): MessageFetchExecutionSnapshot {
return getInitialSnapshot(messageFetchExecutionMachine, input);
return initialTransition(messageFetchExecutionMachine, input)[0];
}
export function transitionMessageFetchExecutionSnapshot(
@@ -5,7 +5,7 @@ import {
calculateAroundPaginationState,
} from '@app/features/messaging/utils/MessagePaginationUtils';
import {compare as compareSnowflakes} from '@fluxer/snowflake/src/SnowflakeUtils';
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export interface MessagePageStateInput {
before: string | null;
@@ -158,7 +158,7 @@ export const messagePageStateMachine = setup({
export type MessagePageStateSnapshot = SnapshotFrom<typeof messagePageStateMachine>;
export function createMessagePageStateSnapshot(input: MessagePageStateInput): MessagePageStateSnapshot {
return getInitialSnapshot(messagePageStateMachine, input);
return initialTransition(messagePageStateMachine, input)[0];
}
export function transitionMessagePageStateSnapshot(
@@ -184,7 +184,7 @@ export const useMarkdownFormattingShortcut = ({
value: string;
setValue: React.Dispatch<React.SetStateAction<string>>;
handleTextChange: (newValue: string, oldValue: string) => void;
previousValueRef: React.MutableRefObject<string>;
previousValueRef: React.RefObject<string>;
}): ((event: KeyboardEvent<HTMLTextAreaElement>) => void) => {
return useCallback(
(event: KeyboardEvent<HTMLTextAreaElement>) => {
@@ -103,8 +103,8 @@ interface UseTextareaAutocompleteParams {
value: string;
setValue: React.Dispatch<React.SetStateAction<string>>;
textareaRef: React.RefObject<HTMLTextAreaElement | null>;
segmentManagerRef: React.MutableRefObject<TextareaSegmentManager>;
previousValueRef: React.MutableRefObject<string>;
segmentManagerRef: React.RefObject<TextareaSegmentManager>;
previousValueRef: React.RefObject<string>;
prepareTextChange: PrepareTextareaTextChange;
allowedTriggers?: Array<TriggerType>;
maxActualLength?: number;
@@ -12,8 +12,8 @@ interface UseTextareaDraftAndTypingOptions {
setValue: React.Dispatch<React.SetStateAction<string>>;
draft: string | null;
draftSegments?: ReadonlyArray<MentionSegment> | null;
previousValueRef: React.MutableRefObject<string>;
segmentManagerRef?: React.MutableRefObject<TextareaSegmentManager>;
previousValueRef: React.RefObject<string>;
segmentManagerRef?: React.RefObject<TextareaSegmentManager>;
isAutocompleteAttached: boolean;
enabled: boolean;
typingEnabled?: boolean;
@@ -13,7 +13,7 @@ interface UseTextareaEditingOptions {
value: string;
setValue: React.Dispatch<React.SetStateAction<string>>;
textareaRef: React.RefObject<HTMLTextAreaElement | null>;
previousValueRef: React.MutableRefObject<string>;
previousValueRef: React.RefObject<string>;
}
export const useTextareaEditing = ({
@@ -18,8 +18,8 @@ interface UseTextareaEmojiPickerReturn {
interface UseTextareaEmojiPickerParams {
setValue: React.Dispatch<React.SetStateAction<string>>;
textareaRef: React.RefObject<HTMLTextAreaElement | null>;
segmentManagerRef: React.MutableRefObject<TextareaSegmentManager>;
previousValueRef: React.MutableRefObject<string>;
segmentManagerRef: React.RefObject<TextareaSegmentManager>;
previousValueRef: React.RefObject<string>;
prepareTextChange: PrepareTextareaTextChange;
channelId?: string;
maxActualLength?: number;
@@ -31,9 +31,9 @@ interface UseTextareaExpressionHandlersOptions {
newText: string;
newSegments: Array<MentionSegment>;
};
previousValueRef: React.MutableRefObject<string>;
previousValueRef: React.RefObject<string>;
prepareTextChange: PrepareTextareaTextChange;
segmentManagerRef: React.MutableRefObject<TextareaSegmentManager>;
segmentManagerRef: React.RefObject<TextareaSegmentManager>;
sendOptimisticMessage: (
messageData: {
content: string;
@@ -21,7 +21,7 @@ interface UseTextareaKeyboardOptions {
value: string;
setValue: React.Dispatch<React.SetStateAction<string>>;
handleTextChange: (newValue: string, previousValue: string) => void;
previousValueRef: React.MutableRefObject<string>;
previousValueRef: React.RefObject<string>;
clearSegments: () => void;
replyingMessage: {
messageId: string;
@@ -21,9 +21,9 @@ import {useCallback, useEffect} from 'react';
interface UseTextareaPasteParams {
channel?: Channel | null;
textareaRef: React.RefObject<HTMLTextAreaElement | null>;
segmentManagerRef: React.MutableRefObject<TextareaSegmentManager>;
segmentManagerRef: React.RefObject<TextareaSegmentManager>;
setValue: React.Dispatch<React.SetStateAction<string>>;
previousValueRef: React.MutableRefObject<string>;
previousValueRef: React.RefObject<string>;
prepareTextChange: PrepareTextareaTextChange;
maxMessageLength?: number;
onPasteExceedsLimit?: (pastedText: string) => void;
@@ -5,8 +5,8 @@ import {TextareaSegmentManager} from '@app/features/messaging/utils/TextareaSegm
import {useCallback, useRef} from 'react';
interface UseTextareaSegmentsReturn {
segmentManagerRef: React.MutableRefObject<TextareaSegmentManager>;
previousValueRef: React.MutableRefObject<string>;
segmentManagerRef: React.RefObject<TextareaSegmentManager>;
previousValueRef: React.RefObject<string>;
displayToActual: (displayText: string) => string;
rememberSegmentsForValue: (value: string, segments?: ReadonlyArray<MentionSegment>) => void;
prepareTextChange: (nextValue: string, nextSegments: ReadonlyArray<MentionSegment>) => void;
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export interface ChannelIncomingMessageInput {
hasNonceMatch: boolean;
@@ -111,7 +111,7 @@ export type ChannelIncomingMessageSnapshot = SnapshotFrom<typeof channelIncoming
export function createChannelIncomingMessageSnapshot(
input: ChannelIncomingMessageInput,
): ChannelIncomingMessageSnapshot {
return getInitialSnapshot(channelIncomingMessageMachine, input);
return initialTransition(channelIncomingMessageMachine, input)[0];
}
export function transitionChannelIncomingMessageSnapshot(
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {compare as compareSnowflakes} from '@fluxer/snowflake/src/SnowflakeUtils';
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export interface ChannelMessagesLoadInput {
isBefore: boolean;
@@ -124,7 +124,7 @@ export const channelMessagesLoadMachine = setup({
export type ChannelMessagesLoadSnapshot = SnapshotFrom<typeof channelMessagesLoadMachine>;
export function createChannelMessagesLoadSnapshot(input: ChannelMessagesLoadInput): ChannelMessagesLoadSnapshot {
return getInitialSnapshot(channelMessagesLoadMachine, input);
return initialTransition(channelMessagesLoadMachine, input)[0];
}
export function transitionChannelMessagesLoadSnapshot(
@@ -247,7 +247,7 @@ export const channelMessagesWindowMachine = setup({
export type ChannelMessagesWindowSnapshot = SnapshotFrom<typeof channelMessagesWindowMachine>;
export function createChannelMessagesWindowSnapshot(input: ChannelMessagesWindowInput): ChannelMessagesWindowSnapshot {
return getInitialSnapshot(channelMessagesWindowMachine, input);
return initialTransition(channelMessagesWindowMachine, input)[0];
}
export function transitionChannelMessagesWindowSnapshot(
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export type MentionType = '@everyone' | '@here' | 'role';
@@ -121,7 +121,7 @@ export const mentionConfirmationStateMachine = setup({
});
export function createMentionConfirmationSnapshot(): MentionConfirmationSnapshot {
return getInitialSnapshot(mentionConfirmationStateMachine);
return initialTransition(mentionConfirmationStateMachine)[0];
}
export function transitionMentionConfirmationSnapshot(
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export interface MessageQueuePayloadRouteInput {
payloadType?: string;
@@ -275,7 +275,7 @@ export type MessageLocalSendRateLimitSnapshot = SnapshotFrom<typeof messageLocal
export function createMessageQueuePayloadRouteSnapshot(
input: MessageQueuePayloadRouteInput,
): MessageQueuePayloadRouteSnapshot {
return getInitialSnapshot(messageQueuePayloadRouteMachine, input);
return initialTransition(messageQueuePayloadRouteMachine, input)[0];
}
export function transitionMessageQueuePayloadRouteSnapshot(
@@ -307,7 +307,7 @@ export function resolveMessageQueuePayloadRouteDecision(
export function createMessageQueueSendExecutionSnapshot(
input: MessageQueueSendExecutionInput,
): MessageQueueSendExecutionSnapshot {
return getInitialSnapshot(messageQueueSendExecutionMachine, input);
return initialTransition(messageQueueSendExecutionMachine, input)[0];
}
export function transitionMessageQueueSendExecutionSnapshot(
@@ -337,7 +337,7 @@ export function resolveMessageQueueSendExecutionDecision(
export function createMessageQueueRequestOutcomeSnapshot(
input: MessageQueueRequestOutcomeInput,
): MessageQueueRequestOutcomeSnapshot {
return getInitialSnapshot(messageQueueRequestOutcomeMachine, input);
return initialTransition(messageQueueRequestOutcomeMachine, input)[0];
}
export function transitionMessageQueueRequestOutcomeSnapshot(
@@ -369,7 +369,7 @@ export function resolveMessageQueueRequestOutcomeDecision(
export function createMessageLocalSendRateLimitSnapshot(
input: MessageLocalSendRateLimitInput,
): MessageLocalSendRateLimitSnapshot {
return getInitialSnapshot(messageLocalSendRateLimitMachine, input);
return initialTransition(messageLocalSendRateLimitMachine, input)[0];
}
export function transitionMessageLocalSendRateLimitSnapshot(
@@ -2,7 +2,7 @@
import type {ReactionEmoji} from '@app/features/messaging/utils/ReactionUtils';
import type {MessageReaction} from '@fluxer/schema/src/domains/message/MessageResponseSchemas';
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export interface ReactionRecord {
emoji: ReactionEmoji;
@@ -366,7 +366,7 @@ export function createReactionMachineSnapshot(
map: ReactionMap = emptyMap(),
currentUserId?: string | null,
): ReactionMachineSnapshot {
return getInitialSnapshot(reactionStateMachine, {map, currentUserId});
return initialTransition(reactionStateMachine, {map, currentUserId})[0];
}
export function transitionReactionSnapshot(
@@ -2,7 +2,7 @@
import {User} from '@app/features/user/models/User';
import type {UserPartial} from '@fluxer/schema/src/domains/user/UserResponseSchemas';
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export type FetchStatus = 'idle' | 'pending' | 'success' | 'error';
@@ -336,7 +336,7 @@ export const reactionUsersStateMachine = setup({
export type ReactionUsersMachineSnapshot = SnapshotFrom<typeof reactionUsersStateMachine>;
export function createReactionUsersSnapshot(requestSerial = 0): ReactionUsersMachineSnapshot {
return getInitialSnapshot(reactionUsersStateMachine, {requestSerial});
return initialTransition(reactionUsersStateMachine, {requestSerial})[0];
}
export function transitionReactionUsersSnapshot(
@@ -9,8 +9,8 @@ export type PrepareTextareaTextChange = (nextValue: string, nextSegments: Readon
export interface ApplyTextareaTextChangeOptions {
textareaRef: React.RefObject<HTMLTextAreaElement | null>;
setValue: React.Dispatch<React.SetStateAction<string>>;
segmentManagerRef: React.MutableRefObject<TextareaSegmentManager>;
previousValueRef: React.MutableRefObject<string>;
segmentManagerRef: React.RefObject<TextareaSegmentManager>;
previousValueRef: React.RefObject<string>;
prepareTextChange: PrepareTextareaTextChange;
nextValue: string;
nextSegments?: ReadonlyArray<MentionSegment>;
@@ -7,7 +7,7 @@ import {
INITIAL_FORM_VALUES,
type State,
} from '@app/features/moderation/components/report/ReportTypes';
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
type ReportMachineContext = Omit<State, 'flowStep'>;
@@ -162,7 +162,7 @@ export const reportStateMachine = setup({
export type ReportMachineSnapshot = SnapshotFrom<typeof reportStateMachine>;
export function createReportSnapshot(): ReportMachineSnapshot {
return getInitialSnapshot(reportStateMachine);
return initialTransition(reportStateMachine)[0];
}
export function transitionReportSnapshot(snapshot: ReportMachineSnapshot, event: Action): ReportMachineSnapshot {
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export interface AutoAckWindowConditions {
channelId: string | null;
@@ -111,7 +111,7 @@ export const autoAckWindowStateMachine = setup({
export type AutoAckWindowSnapshot = SnapshotFrom<typeof autoAckWindowStateMachine>;
export function createAutoAckWindowSnapshot(input: AutoAckWindowInput = {}): AutoAckWindowSnapshot {
return getInitialSnapshot(autoAckWindowStateMachine, input);
return initialTransition(autoAckWindowStateMachine, input)[0];
}
export function transitionAutoAckWindowSnapshot(
@@ -3,7 +3,7 @@
import type {RuntimeConfigSnapshot} from '@app/features/app/state/RuntimeConfig';
import type {AccountPresenceIntent, UserData} from '@app/features/auth/state/AccountStorage';
import type {ValueOf} from '@fluxer/constants/src/ValueOf';
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export const SessionState = {
Idle: 'idle',
@@ -285,7 +285,7 @@ export const authSessionStateMachine = setup({
export type AuthSessionSnapshot = SnapshotFrom<typeof authSessionStateMachine>;
export function createAuthSessionSnapshot(): AuthSessionSnapshot {
return getInitialSnapshot(authSessionStateMachine);
return initialTransition(authSessionStateMachine)[0];
}
export function transitionAuthSessionSnapshot(
@@ -123,7 +123,7 @@ export const TextareaAutosize = React.forwardRef<HTMLTextAreaElement, TextareaAu
(node: HTMLTextAreaElement | null) => {
elRef.current = node;
if (typeof forwardedRef === 'function') forwardedRef(node);
else if (forwardedRef) (forwardedRef as React.MutableRefObject<HTMLTextAreaElement | null>).current = node;
else if (forwardedRef) forwardedRef.current = node;
},
[forwardedRef],
);
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
import {compareMessageIds} from './shared';
export interface ReadStateAckInput {
@@ -128,7 +128,7 @@ export const readStateAckMachine = setup({
export type ReadStateAckSnapshot = SnapshotFrom<typeof readStateAckMachine>;
export function createReadStateAckSnapshot(input: ReadStateAckInput): ReadStateAckSnapshot {
return getInitialSnapshot(readStateAckMachine, input);
return initialTransition(readStateAckMachine, input)[0];
}
export function transitionReadStateAckSnapshot(
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
import {compareMessageIds} from './shared';
export interface ReadStateEntryStatusInput {
@@ -127,7 +127,7 @@ export const readStateEntryStatusMachine = setup({
export type ReadStateEntryStatusSnapshot = SnapshotFrom<typeof readStateEntryStatusMachine>;
export function createReadStateEntryStatusSnapshot(input: ReadStateEntryStatusInput): ReadStateEntryStatusSnapshot {
return getInitialSnapshot(readStateEntryStatusMachine, input);
return initialTransition(readStateEntryStatusMachine, input)[0];
}
export function transitionReadStateEntryStatusSnapshot(
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
import {compareMessageIds} from './shared';
export interface ReadStateIncomingMessageInput {
@@ -136,7 +136,7 @@ export type ReadStateIncomingMessageSnapshot = SnapshotFrom<typeof readStateInco
export function createReadStateIncomingMessageSnapshot(
input: ReadStateIncomingMessageInput,
): ReadStateIncomingMessageSnapshot {
return getInitialSnapshot(readStateIncomingMessageMachine, input);
return initialTransition(readStateIncomingMessageMachine, input)[0];
}
export function transitionReadStateIncomingMessageSnapshot(
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export interface ReadStateMentionInput {
authorBlocked: boolean;
@@ -98,7 +98,7 @@ export const readStateMentionMachine = setup({
export type ReadStateMentionSnapshot = SnapshotFrom<typeof readStateMentionMachine>;
export function createReadStateMentionSnapshot(input: ReadStateMentionInput): ReadStateMentionSnapshot {
return getInitialSnapshot(readStateMentionMachine, input);
return initialTransition(readStateMentionMachine, input)[0];
}
export function transitionReadStateMentionSnapshot(
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
import {compareMessageIds, compareReadStateVersions} from './shared';
export interface ReadStateServerAckInput {
@@ -107,7 +107,7 @@ export const readStateServerAckMachine = setup({
export type ReadStateServerAckSnapshot = SnapshotFrom<typeof readStateServerAckMachine>;
export function createReadStateServerAckSnapshot(input: ReadStateServerAckInput): ReadStateServerAckSnapshot {
return getInitialSnapshot(readStateServerAckMachine, input);
return initialTransition(readStateServerAckMachine, input)[0];
}
export function transitionReadStateServerAckSnapshot(
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export const LOCAL_TYPING_REMOTE_SEND_DELAY_MS = 1500;
export const LOCAL_TYPING_REMOTE_REFRESH_MS = 8000;
@@ -198,7 +198,7 @@ export const localTypingStateMachine = setup({
});
export function createLocalTypingSnapshot(input: LocalTypingMachineInput = {}): LocalTypingSnapshot {
return getInitialSnapshot(localTypingStateMachine, input);
return initialTransition(localTypingStateMachine, input)[0];
}
export function transitionLocalTypingSnapshot(
@@ -82,7 +82,7 @@ export const Checkbox: React.FC<CheckboxProps> = observer(
'aria-describedby': ariaDescribedBy,
'aria-hidden': ariaHidden,
}) => {
const rootRef = useRef<React.ElementRef<typeof CheckboxPrimitive.Root>>(null);
const rootRef = useRef<React.ComponentRef<typeof CheckboxPrimitive.Root>>(null);
const checkboxRef = useRef<HTMLLabelElement>(null);
const labelRef = useRef<HTMLDivElement>(null);
const actualSize = size === 'small' ? 18 : size;
@@ -113,7 +113,7 @@ export const InlineEdit: React.FC<InlineEditProps> = observer((props) => {
setMode('editing');
}
};
const handleEditableInput: React.FormEventHandler<HTMLDivElement> = (e) => {
const handleEditableInput: React.InputEventHandler<HTMLDivElement> = (e) => {
const el = e.currentTarget;
const raw = el.textContent ?? '';
let next = sanitizeDraft(raw);
@@ -73,7 +73,7 @@ const assignRef = <T,>(ref: React.Ref<T> | undefined, value: T | null): void =>
if (typeof ref === 'function') {
ref(value);
} else if (ref && typeof ref === 'object') {
(ref as React.MutableRefObject<T | null>).current = value;
ref.current = value;
}
};
@@ -42,7 +42,7 @@ export const Switch = observer(
);
const resolvedLabelledBy = hasLabel ? labelId : ariaLabelledBy;
const resolvedDescribedBy = [hasDescription ? descriptionId : null, ariaDescribedBy].filter(Boolean).join(' ');
const rootRef = useRef<React.ElementRef<typeof SwitchPrimitive.Root>>(null);
const rootRef = useRef<React.ComponentRef<typeof SwitchPrimitive.Root>>(null);
const valueChange = useCallback(
(next: boolean) => {
if (disabled) return;
@@ -67,7 +67,7 @@ function keepCurrentArrayIfEqual<T>(currentItems: ReadonlyArray<T>, nextItems: R
interface ResolveHoverDeferredItemsRequest<T> {
readonly currentItems: ReadonlyArray<T>;
readonly getKey: (item: T) => string;
readonly hasDeferredOrderRef: React.MutableRefObject<boolean>;
readonly hasDeferredOrderRef: React.RefObject<boolean>;
readonly isHoveringDynamicItem: boolean;
readonly items: ReadonlyArray<T>;
readonly sourceChanged: boolean;
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export type SteppedCarouselHeight = number | 'auto';
@@ -136,7 +136,7 @@ export type SteppedCarouselSnapshot = SnapshotFrom<typeof steppedCarouselStateMa
export type SteppedCarouselStateValue = 'unmeasured' | 'ready';
export function createSteppedCarouselSnapshot(input: SteppedCarouselMachineInput): SteppedCarouselSnapshot {
return getInitialSnapshot(steppedCarouselStateMachine, input);
return initialTransition(steppedCarouselStateMachine, input)[0];
}
export function transitionSteppedCarouselSnapshot(
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export interface TooltipEnvironment {
disabled: boolean;
@@ -243,7 +243,7 @@ export const tooltipStateMachine = setup({
export type TooltipSnapshot = SnapshotFrom<typeof tooltipStateMachine>;
export function createTooltipSnapshot(input: TooltipMachineInput = {}): TooltipSnapshot {
return getInitialSnapshot(tooltipStateMachine, input);
return initialTransition(tooltipStateMachine, input)[0];
}
export function transitionTooltipSnapshot(snapshot: TooltipSnapshot, event: TooltipMachineEvent): TooltipSnapshot {
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {UpdaterDownloadOption} from '@app/features/platform/types/Electron';
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export type UpdaterState = 'idle' | 'checking' | 'available';
export type UpdateType = 'native' | 'web' | 'both' | null;
@@ -389,7 +389,7 @@ export const updaterStateMachine = setup({
export type UpdaterMachineSnapshot = SnapshotFrom<typeof updaterStateMachine>;
export function createUpdaterMachineSnapshot(): UpdaterMachineSnapshot {
return getInitialSnapshot(updaterStateMachine);
return initialTransition(updaterStateMachine)[0];
}
export function transitionUpdaterMachineSnapshot(
@@ -207,7 +207,7 @@ const EmbedDebuggerTab: React.FC = observer(() => {
() => (previewEmbeds ? createPreviewMessage(submittedUrl, previewEmbeds) : null),
[previewEmbeds, submittedUrl],
);
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
const handleSubmit = async (event: React.SubmitEvent<HTMLFormElement>) => {
event.preventDefault();
if (!trimmedUrl || isSubmitting) return;
setIsSubmitting(true);
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {TransientUploadFieldValue} from '@app/lib/forms/TransientUploadFields';
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export type ProfileAssetMode = 'inherit' | 'custom' | 'unset';
@@ -244,7 +244,7 @@ export const profileAssetCustomizationStateMachine = setup({
export type ProfileAssetCustomizationSnapshot = SnapshotFrom<typeof profileAssetCustomizationStateMachine>;
export function createProfileAssetCustomizationSnapshot(): ProfileAssetCustomizationSnapshot {
return getInitialSnapshot(profileAssetCustomizationStateMachine);
return initialTransition(profileAssetCustomizationStateMachine)[0];
}
export function transitionProfileAssetCustomizationSnapshot(
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export type SyncedFieldFailureReason =
| 'build-local'
@@ -242,7 +242,7 @@ export const syncedFieldStateMachine = setup({
});
export function createSyncedFieldMachineSnapshot(): SyncedFieldMachineSnapshot {
return getInitialSnapshot(syncedFieldStateMachine);
return initialTransition(syncedFieldStateMachine)[0];
}
export function transitionSyncedFieldMachineSnapshot(
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {areOrderedStringArraysEqual} from '@app/features/voice/utils/StringArrayUtils';
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export interface IncomingCallManagerSignals {
incomingCallIds: Array<string>;
@@ -85,7 +85,7 @@ export const incomingCallManagerStateMachine = setup({
export type IncomingCallManagerSnapshot = SnapshotFrom<typeof incomingCallManagerStateMachine>;
export function createIncomingCallManagerSnapshot(): IncomingCallManagerSnapshot {
return getInitialSnapshot(incomingCallManagerStateMachine);
return initialTransition(incomingCallManagerStateMachine)[0];
}
export function transitionIncomingCallManagerSnapshot(
@@ -15,7 +15,7 @@ import {
type WindowShareAudioScope,
} from '@app/features/voice/utils/StreamSettingsUpdatePolicy';
import type {NativeAudioAvailability} from '@app/types/electron.d';
import {getInitialSnapshot, setup, transition} from 'xstate';
import {initialTransition, setup, transition} from 'xstate';
export type StreamSettingsAudioControlStateValue =
| 'hidden'
@@ -146,7 +146,7 @@ export function selectStreamSettingsAudioControlState(
): StreamSettingsAudioControlStateValue {
const [snapshot] = transition(
streamSettingsAudioControlStateMachine,
getInitialSnapshot(streamSettingsAudioControlStateMachine),
initialTransition(streamSettingsAudioControlStateMachine)[0],
{
type: 'audio.evaluate',
signals,
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export type VoiceCallLayoutPresentationMode = 'grid' | 'focus';
@@ -185,7 +185,7 @@ export type VoiceCallLayoutPresentationMachineSnapshot = SnapshotFrom<typeof voi
export function createVoiceCallLayoutPresentationSnapshot(
input: VoiceCallLayoutPresentationInput,
): VoiceCallLayoutPresentationMachineSnapshot {
return getInitialSnapshot(voiceCallLayoutPresentationStateMachine, input);
return initialTransition(voiceCallLayoutPresentationStateMachine, input)[0];
}
export function transitionVoiceCallLayoutPresentationSnapshot(
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {assign, getInitialSnapshot, setup, transition} from 'xstate';
import {assign, initialTransition, setup, transition} from 'xstate';
export type VoiceControlBarMuteLabel =
| 'moderatorDeafened'
@@ -152,7 +152,7 @@ export const voiceControlBarStateMachine = setup({
});
export function transitionVoiceControlBarState(signals: VoiceControlBarSignals): VoiceControlBarState {
const [snapshot] = transition(voiceControlBarStateMachine, getInitialSnapshot(voiceControlBarStateMachine), {
const [snapshot] = transition(voiceControlBarStateMachine, initialTransition(voiceControlBarStateMachine)[0], {
type: 'controls.evaluate',
signals,
});
@@ -2,7 +2,7 @@
import type {VoiceParticipantTilePresentation} from '@app/features/voice/components/voice_participant_tile/shared';
import type {VoiceMediaGraphStreamTileState} from '@app/features/voice/engine/VoiceMediaGraphTileState';
import {assign, getInitialSnapshot, setup, transition} from 'xstate';
import {assign, initialTransition, setup, transition} from 'xstate';
export type VoiceParticipantTileScreenShareStateValue =
| 'idle'
@@ -221,7 +221,7 @@ export function selectVoiceParticipantTileScreenShareState(
): VoiceParticipantTileScreenShareStateValue {
const [snapshot] = transition(
voiceParticipantTileStateMachine,
getInitialSnapshot(voiceParticipantTileStateMachine),
initialTransition(voiceParticipantTileStateMachine)[0],
{
type: 'tile.evaluateScreenShare',
signals,
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export interface CompactVoiceCallPresentationInput {
readonly audioOnly?: boolean;
@@ -113,7 +113,7 @@ export type CompactVoiceCallPresentationMachineSnapshot = SnapshotFrom<typeof co
export function createCompactVoiceCallPresentationSnapshot(
input: CompactVoiceCallPresentationInput,
): CompactVoiceCallPresentationMachineSnapshot {
return getInitialSnapshot(compactVoiceCallPresentationStateMachine, input);
return initialTransition(compactVoiceCallPresentationStateMachine, input)[0];
}
export function transitionCompactVoiceCallPresentationSnapshot(
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export interface MediaControlsVisibilitySignals {
disabled: boolean;
@@ -58,7 +58,7 @@ export const mediaControlsVisibilityStateMachine = setup({
export type MediaControlsVisibilitySnapshot = SnapshotFrom<typeof mediaControlsVisibilityStateMachine>;
export function createMediaControlsVisibilitySnapshot(): MediaControlsVisibilitySnapshot {
return getInitialSnapshot(mediaControlsVisibilityStateMachine);
return initialTransition(mediaControlsVisibilityStateMachine)[0];
}
export function transitionMediaControlsVisibilitySnapshot(
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
export type VideoPlayerRenderState = 'poster' | 'playing' | 'paused' | 'ended' | 'error';
export type VideoPlayerPlayPauseIndicator = 'play' | 'pause';
@@ -127,7 +127,7 @@ export const videoPlayerRenderStateMachine = setup({
export type VideoPlayerRenderSnapshot = SnapshotFrom<typeof videoPlayerRenderStateMachine>;
export function createVideoPlayerRenderSnapshot(): VideoPlayerRenderSnapshot {
return getInitialSnapshot(videoPlayerRenderStateMachine);
return initialTransition(videoPlayerRenderStateMachine)[0];
}
export function transitionVideoPlayerRenderSnapshot(
@@ -256,8 +256,8 @@ interface CameraPreviewTrackSetupArgs {
cameraResolution: 'low' | 'medium' | 'high';
videoFrameRate: number;
isCurrentInitialization: () => boolean;
trackRef: React.MutableRefObject<LocalVideoTrack | null>;
processorRef: React.MutableRefObject<CameraPreviewProcessor | null>;
trackRef: React.RefObject<LocalVideoTrack | null>;
processorRef: React.RefObject<CameraPreviewProcessor | null>;
onResolutionNegotiated: (resolution: {width: number; height: number} | null) => void;
}
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {NativePermissionResult} from '@app/features/permissions/system/utils/NativePermissions';
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
import {assign, initialTransition, type SnapshotFrom, setup, transition} from 'xstate';
interface ScreenSharePickerDisplayPermissionContext {
settingsOpened: boolean;
@@ -107,7 +107,7 @@ export type ScreenSharePickerDisplayPermissionSnapshot = SnapshotFrom<
>;
export function createScreenSharePickerDisplayPermissionSnapshot(): ScreenSharePickerDisplayPermissionSnapshot {
return getInitialSnapshot(screenSharePickerDisplayPermissionStateMachine);
return initialTransition(screenSharePickerDisplayPermissionStateMachine)[0];
}
export function transitionScreenSharePickerDisplayPermissionSnapshot(
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {getInitialSnapshot, setup, transition} from 'xstate';
import {initialTransition, setup, transition} from 'xstate';
export type LocalParticipantMediaControl = 'camera' | 'screenShare';
export type LocalParticipantMediaControlStateValue =
@@ -120,7 +120,7 @@ export function selectLocalParticipantMediaControlState(
): LocalParticipantMediaControlStateValue {
const [snapshot] = transition(
localParticipantMediaControlStateMachine,
getInitialSnapshot(localParticipantMediaControlStateMachine),
initialTransition(localParticipantMediaControlStateMachine)[0],
{
type: 'controls.evaluate',
control,
@@ -174,7 +174,7 @@ export function useTileContextMenuActive(tileElRef: React.RefObject<HTMLElement
}
function unsubscribeManagedVideoPublication(
managedPublicationRef: React.MutableRefObject<RemoteTrackPublication | null>,
managedPublicationRef: React.RefObject<RemoteTrackPublication | null>,
publication: RemoteTrackPublication | null,
): void {
if (!publication) return;
Loaded 100 of 510 files, more files were not shown because too many files have changed in this diff. Show more