infra: add linting and formatting for js projects (#230)
* wip * wip * wip * format * wip(mobile): lint and format * cleanup * nits * nits * idiomatic react * nit * format all root files
This commit was merged in pull request #230.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { usePusherClient } from "@/lib/pusher-provider";
|
||||
import type { ChannelMessage } from "@/lib/pusher-client";
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { usePusherClient } from '@/lib/pusher-provider';
|
||||
import type { ChannelMessage } from '@/lib/pusher-client';
|
||||
|
||||
interface UseChannelResult {
|
||||
/** Current set of humanIds present in the channel */
|
||||
@@ -23,11 +23,7 @@ export function useChannel(channelId: string | null): UseChannelResult {
|
||||
const [messages, setMessages] = useState<ChannelMessage[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!client || !channelId) {
|
||||
setPresence([]);
|
||||
setMessages([]);
|
||||
return;
|
||||
}
|
||||
if (!client || !channelId) return;
|
||||
|
||||
client.subscribe(channelId);
|
||||
|
||||
@@ -58,17 +54,19 @@ export function useChannel(channelId: string | null): UseChannelResult {
|
||||
}
|
||||
};
|
||||
|
||||
client.on(channelId, "subscribed", onSubscribed);
|
||||
client.on(channelId, "join", onJoin);
|
||||
client.on(channelId, "leave", onLeave);
|
||||
client.on(channelId, "message", onMessage);
|
||||
client.on(channelId, 'subscribed', onSubscribed);
|
||||
client.on(channelId, 'join', onJoin);
|
||||
client.on(channelId, 'leave', onLeave);
|
||||
client.on(channelId, 'message', onMessage);
|
||||
|
||||
return () => {
|
||||
client.off(channelId, "subscribed", onSubscribed);
|
||||
client.off(channelId, "join", onJoin);
|
||||
client.off(channelId, "leave", onLeave);
|
||||
client.off(channelId, "message", onMessage);
|
||||
client.off(channelId, 'subscribed', onSubscribed);
|
||||
client.off(channelId, 'join', onJoin);
|
||||
client.off(channelId, 'leave', onLeave);
|
||||
client.off(channelId, 'message', onMessage);
|
||||
client.unsubscribe(channelId);
|
||||
setPresence([]);
|
||||
setMessages([]);
|
||||
};
|
||||
}, [client, channelId]);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useLayoutEffect, useRef } from "react";
|
||||
import { useCallback, useLayoutEffect, useRef } from 'react';
|
||||
|
||||
// Polyfill for React's `useEffectEvent` (canary). The returned function has a
|
||||
// stable identity but always sees the latest closure — exactly what
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { apiClient } from "@/api/client";
|
||||
import { useAuthStore } from "@/stores/auth-store";
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { apiClient } from '@/api/client';
|
||||
import { useAuthStore } from '@/stores/auth-store';
|
||||
|
||||
export function useNetworks() {
|
||||
return useQuery({
|
||||
queryKey: ["networks"],
|
||||
queryKey: ['networks'],
|
||||
queryFn: () => apiClient.listNetworks(),
|
||||
meta: { toastOnError: true },
|
||||
});
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import type { QueryFieldFilterConstraint } from "firebase/firestore";
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { QueryFieldFilterConstraint } from 'firebase/firestore';
|
||||
import {
|
||||
subscribeToParticle,
|
||||
subscribeToParticleChildren,
|
||||
subscribeToLatestChild,
|
||||
getParticle,
|
||||
getParticleChildren,
|
||||
} from "@/lib/firestore-particles";
|
||||
import type { Particle } from "@/api/types";
|
||||
} from '@/lib/firestore-particles';
|
||||
import type { Particle } from '@/api/types';
|
||||
import {
|
||||
type ParticlePath,
|
||||
toFirestoreDocPath,
|
||||
toFirestoreChildrenPath,
|
||||
} from "@/lib/particle-path";
|
||||
import { logError } from "@/lib/errors";
|
||||
} from '@/lib/particle-path';
|
||||
import { logError } from '@/lib/errors';
|
||||
|
||||
interface UseLiveParticleResult {
|
||||
particle: Particle | null;
|
||||
@@ -28,10 +28,6 @@ export function useLiveParticle(path: ParticlePath): UseLiveParticleResult {
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
setParticle(null);
|
||||
|
||||
const docPath = toFirestoreDocPath(path);
|
||||
const unsubscribe = subscribeToParticle(
|
||||
docPath,
|
||||
@@ -45,7 +41,12 @@ export function useLiveParticle(path: ParticlePath): UseLiveParticleResult {
|
||||
},
|
||||
);
|
||||
|
||||
return unsubscribe;
|
||||
return () => {
|
||||
unsubscribe();
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
setParticle(null);
|
||||
};
|
||||
}, [path]);
|
||||
|
||||
return { particle, isLoading, error };
|
||||
@@ -59,7 +60,7 @@ interface UseLiveParticleChildrenResult {
|
||||
|
||||
interface UseLiveParticleChildrenParams {
|
||||
orderByField?: string;
|
||||
orderDirection?: "asc" | "desc";
|
||||
orderDirection?: 'asc' | 'desc';
|
||||
visibilityScopes?: string[];
|
||||
onAdded?: (child: Particle) => void;
|
||||
onRemoved?: (child: Particle, updatedChildren: Particle[]) => void;
|
||||
@@ -71,8 +72,8 @@ interface UseLiveParticleChildrenParams {
|
||||
export function useLiveParticleChildren(
|
||||
path: ParticlePath | undefined,
|
||||
{
|
||||
orderByField = "created_at",
|
||||
orderDirection = "desc",
|
||||
orderByField = 'created_at',
|
||||
orderDirection = 'desc',
|
||||
visibilityScopes,
|
||||
onAdded,
|
||||
onRemoved,
|
||||
@@ -85,15 +86,7 @@ export function useLiveParticleChildren(
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!path) {
|
||||
setChildren([]);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
setChildren([]);
|
||||
if (!path) return;
|
||||
|
||||
const collectionPath = toFirestoreChildrenPath(path);
|
||||
|
||||
@@ -103,7 +96,7 @@ export function useLiveParticleChildren(
|
||||
setIsLoading(false);
|
||||
},
|
||||
onError: (err) => {
|
||||
logError(err, { scope: "firestore.particle-children", path });
|
||||
logError(err, { scope: 'firestore.particle-children', path });
|
||||
setError(err);
|
||||
setIsLoading(false);
|
||||
},
|
||||
@@ -116,13 +109,23 @@ export function useLiveParticleChildren(
|
||||
limit,
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
return () => {
|
||||
unsubscribe();
|
||||
setChildren([]);
|
||||
setError(null);
|
||||
setIsLoading(true);
|
||||
};
|
||||
// The hook intentionally keys only on path/whereFilter/limit — desktop
|
||||
// does the same. Visibility scope changes are absorbed by the active
|
||||
// listener; reordering causes a re-subscription.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [path, whereFilter, limit]);
|
||||
|
||||
// No path: nothing to load, so report an empty non-loading state.
|
||||
if (!path) {
|
||||
return { children: [], isLoading: false, error: null };
|
||||
}
|
||||
|
||||
return { children, isLoading, error };
|
||||
}
|
||||
|
||||
@@ -138,9 +141,6 @@ export function useLiveLatestChild(
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
setLatestChild(null);
|
||||
|
||||
const unsubscribe = subscribeToLatestChild(
|
||||
toFirestoreChildrenPath(path),
|
||||
(data) => {
|
||||
@@ -148,12 +148,16 @@ export function useLiveLatestChild(
|
||||
setIsLoading(false);
|
||||
},
|
||||
(err) => {
|
||||
logError(err, { scope: "firestore.latest-child", path });
|
||||
logError(err, { scope: 'firestore.latest-child', path });
|
||||
setIsLoading(false);
|
||||
},
|
||||
);
|
||||
|
||||
return unsubscribe;
|
||||
return () => {
|
||||
unsubscribe();
|
||||
setIsLoading(true);
|
||||
setLatestChild(null);
|
||||
};
|
||||
}, [path]);
|
||||
|
||||
return { latestChild, isLoading };
|
||||
@@ -161,7 +165,7 @@ export function useLiveLatestChild(
|
||||
|
||||
export function useParticle(path?: ParticlePath) {
|
||||
return useQuery({
|
||||
queryKey: ["particle", path],
|
||||
queryKey: ['particle', path],
|
||||
queryFn: async () => {
|
||||
if (!path) return null;
|
||||
const docPath = toFirestoreDocPath(path);
|
||||
@@ -174,7 +178,7 @@ export function useParticle(path?: ParticlePath) {
|
||||
|
||||
export function useParticleChildren(path?: ParticlePath) {
|
||||
return useQuery({
|
||||
queryKey: ["particle-children", path],
|
||||
queryKey: ['particle-children', path],
|
||||
queryFn: async () => {
|
||||
if (!path) return [];
|
||||
const collectionPath = toFirestoreChildrenPath(path);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { where, type QueryFieldFilterConstraint } from "firebase/firestore";
|
||||
import { useLiveParticleChildren } from "@/hooks/use-particle";
|
||||
import { useAuthStore } from "@/stores/auth-store";
|
||||
import { parseParticlePath, type ParticlePath } from "@/lib/particle-path";
|
||||
import type { Particle, StreamProperties } from "@/api/types";
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { where, type QueryFieldFilterConstraint } from 'firebase/firestore';
|
||||
import { useLiveParticleChildren } from '@/hooks/use-particle';
|
||||
import { useAuthStore } from '@/stores/auth-store';
|
||||
import { parseParticlePath, type ParticlePath } from '@/lib/particle-path';
|
||||
import type { Particle, StreamProperties } from '@/api/types';
|
||||
|
||||
export type StreamParticle = Particle & {
|
||||
type: "stream";
|
||||
type: 'stream';
|
||||
properties: StreamProperties;
|
||||
};
|
||||
|
||||
@@ -15,8 +15,8 @@ const CLOSED_PAGE_INCREMENT = 50;
|
||||
|
||||
// Stable where-constraint references so the Firestore subscription only
|
||||
// re-attaches when the tab actually changes, not on every render.
|
||||
const OPEN_STATUS_FILTER = where("status", "==", "open");
|
||||
const CLOSED_STATUS_FILTER = where("status", "==", "closed");
|
||||
const OPEN_STATUS_FILTER = where('status', '==', 'open');
|
||||
const CLOSED_STATUS_FILTER = where('status', '==', 'closed');
|
||||
|
||||
function useVisibilityScopes(userId?: string, networkId?: string) {
|
||||
return useMemo(() => {
|
||||
@@ -33,7 +33,7 @@ interface UseStreamParticlesOptions {
|
||||
* by active work — full realtime coverage is needed for autoplay/huddles).
|
||||
* Closed streams are paginated via `loadMore`.
|
||||
*/
|
||||
status: "open" | "closed";
|
||||
status: 'open' | 'closed';
|
||||
}
|
||||
|
||||
interface UseStreamParticlesResult {
|
||||
@@ -56,38 +56,40 @@ export function useStreamParticles(
|
||||
const visibilityScopes = useVisibilityScopes(user?.id, networkId);
|
||||
|
||||
const [closedLimit, setClosedLimit] = useState(CLOSED_INITIAL_PAGE_SIZE);
|
||||
const [prevStatus, setPrevStatus] = useState(status);
|
||||
|
||||
// Every time the user switches back to the closed tab, start with a fresh
|
||||
// window. Avoids an ever-growing subscription across a long session.
|
||||
useEffect(() => {
|
||||
if (status === "closed") {
|
||||
// Switching back to the closed tab starts a fresh window, avoiding an
|
||||
// ever-growing subscription across a long session.
|
||||
if (status !== prevStatus) {
|
||||
setPrevStatus(status);
|
||||
if (status === 'closed') {
|
||||
setClosedLimit(CLOSED_INITIAL_PAGE_SIZE);
|
||||
}
|
||||
}, [status]);
|
||||
}
|
||||
|
||||
const whereFilter: QueryFieldFilterConstraint =
|
||||
status === "open" ? OPEN_STATUS_FILTER : CLOSED_STATUS_FILTER;
|
||||
const limit = status === "closed" ? closedLimit : undefined;
|
||||
status === 'open' ? OPEN_STATUS_FILTER : CLOSED_STATUS_FILTER;
|
||||
const limit = status === 'closed' ? closedLimit : undefined;
|
||||
|
||||
const { children, isLoading, error } = useLiveParticleChildren(path, {
|
||||
orderByField: "last_child_created_at",
|
||||
orderDirection: "desc",
|
||||
orderByField: 'last_child_created_at',
|
||||
orderDirection: 'desc',
|
||||
visibilityScopes,
|
||||
whereFilter,
|
||||
limit,
|
||||
});
|
||||
|
||||
const streams = useMemo(
|
||||
() => children.filter((c): c is StreamParticle => c.type === "stream"),
|
||||
() => children.filter((c): c is StreamParticle => c.type === 'stream'),
|
||||
[children],
|
||||
);
|
||||
|
||||
// Heuristic: if we got back as many items as we asked for, assume there
|
||||
// might be more. Clicking load-more when there are no more is a no-op.
|
||||
const canLoadMore = status === "closed" && streams.length >= closedLimit;
|
||||
const canLoadMore = status === 'closed' && streams.length >= closedLimit;
|
||||
|
||||
const loadMore = useCallback(() => {
|
||||
if (status !== "closed") return;
|
||||
if (status !== 'closed') return;
|
||||
setClosedLimit((prev) => prev + CLOSED_PAGE_INCREMENT);
|
||||
}, [status]);
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { useCallback, useEffect, useMemo, useReducer, useRef } from "react";
|
||||
import { useAuthStore } from "@/stores/auth-store";
|
||||
import type { Particle } from "@/api/types";
|
||||
import { useLiveParticleChildren } from "@/hooks/use-particle";
|
||||
import { toFirestoreDocPath, type ParticlePath } from "@/lib/particle-path";
|
||||
import { updateStreamPlaybackMarker } from "@/lib/firestore-particles";
|
||||
import { logError } from "@/lib/errors";
|
||||
import { useEvent } from "@/hooks/use-event";
|
||||
import { useCallback, useEffect, useMemo, useReducer, useRef } from 'react';
|
||||
import { useAuthStore } from '@/stores/auth-store';
|
||||
import type { Particle } from '@/api/types';
|
||||
import { useLiveParticleChildren } from '@/hooks/use-particle';
|
||||
import { toFirestoreDocPath, type ParticlePath } from '@/lib/particle-path';
|
||||
import { updateStreamPlaybackMarker } from '@/lib/firestore-particles';
|
||||
import { logError } from '@/lib/errors';
|
||||
import { useEvent } from '@/hooks/use-event';
|
||||
|
||||
// --- Playback reducer (ID-based) ---
|
||||
|
||||
type PlaybackStatus = "idle" | "playing" | "ended";
|
||||
type PlaybackStatus = 'idle' | 'playing' | 'ended';
|
||||
|
||||
interface PlaybackState {
|
||||
currentParticleId: string | null;
|
||||
@@ -18,19 +18,19 @@ interface PlaybackState {
|
||||
}
|
||||
|
||||
type PlaybackAction =
|
||||
| { type: "INIT"; particleId: string }
|
||||
| { type: "SET_PARTICLE"; particleId: string }
|
||||
| { type: "END" }
|
||||
| { type: "PARTICLE_ADDED"; particleId: string }
|
||||
| { type: 'INIT'; particleId: string }
|
||||
| { type: 'SET_PARTICLE'; particleId: string }
|
||||
| { type: 'END' }
|
||||
| { type: 'PARTICLE_ADDED'; particleId: string }
|
||||
| {
|
||||
type: "PARTICLE_REMOVED";
|
||||
type: 'PARTICLE_REMOVED';
|
||||
removedParticleId: string;
|
||||
fallbackParticleId: string | null;
|
||||
};
|
||||
|
||||
const initialState: PlaybackState = {
|
||||
currentParticleId: null,
|
||||
status: "idle",
|
||||
status: 'idle',
|
||||
initialized: false,
|
||||
};
|
||||
|
||||
@@ -39,39 +39,39 @@ function playbackReducer(
|
||||
action: PlaybackAction,
|
||||
): PlaybackState {
|
||||
switch (action.type) {
|
||||
case "INIT":
|
||||
case 'INIT':
|
||||
return {
|
||||
currentParticleId: action.particleId,
|
||||
status: "playing",
|
||||
status: 'playing',
|
||||
initialized: true,
|
||||
};
|
||||
case "SET_PARTICLE":
|
||||
case 'SET_PARTICLE':
|
||||
return {
|
||||
...state,
|
||||
currentParticleId: action.particleId,
|
||||
status: "playing",
|
||||
status: 'playing',
|
||||
};
|
||||
case "END":
|
||||
return { ...state, status: "ended" };
|
||||
case "PARTICLE_ADDED":
|
||||
if (state.status === "ended") {
|
||||
case 'END':
|
||||
return { ...state, status: 'ended' };
|
||||
case 'PARTICLE_ADDED':
|
||||
if (state.status === 'ended') {
|
||||
return {
|
||||
...state,
|
||||
currentParticleId: action.particleId,
|
||||
status: "playing",
|
||||
status: 'playing',
|
||||
};
|
||||
}
|
||||
return state;
|
||||
case "PARTICLE_REMOVED":
|
||||
case 'PARTICLE_REMOVED':
|
||||
if (action.removedParticleId !== state.currentParticleId) return state;
|
||||
if (action.fallbackParticleId) {
|
||||
return {
|
||||
...state,
|
||||
currentParticleId: action.fallbackParticleId,
|
||||
status: "playing",
|
||||
status: 'playing',
|
||||
};
|
||||
}
|
||||
return { ...state, currentParticleId: null, status: "idle" };
|
||||
return { ...state, currentParticleId: null, status: 'idle' };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,33 +90,40 @@ interface UseStreamPlaybackResult {
|
||||
}
|
||||
|
||||
export function useStreamPlayback(
|
||||
streamParticle: Particle & { type: "stream" },
|
||||
streamParticle: Particle & { type: 'stream' },
|
||||
path: ParticlePath,
|
||||
): UseStreamPlaybackResult {
|
||||
const userId = useAuthStore((s) => s.user?.id);
|
||||
const [state, dispatch] = useReducer(playbackReducer, initialState);
|
||||
// Track which stream we initialized for, so navigating to a sibling resets cleanly.
|
||||
const initializedForRef = useRef<string | null>(null);
|
||||
// Latest currentIndex for onParticleRemoved, so it reads the current value
|
||||
// without recreating the callback (which would re-subscribe the listener).
|
||||
const currentIndexRef = useRef(0);
|
||||
|
||||
const onParticleAdded = useCallback((particle: Particle) => {
|
||||
dispatch({ type: "PARTICLE_ADDED", particleId: particle.id });
|
||||
dispatch({ type: 'PARTICLE_ADDED', particleId: particle.id });
|
||||
}, []);
|
||||
|
||||
const onParticleRemoved = useEvent(
|
||||
const onParticleRemoved = useCallback(
|
||||
(removed: Particle, updatedChildren: Particle[]) => {
|
||||
const fallbackIndex = Math.min(currentIndex, updatedChildren.length - 1);
|
||||
const fallbackIndex = Math.min(
|
||||
currentIndexRef.current,
|
||||
updatedChildren.length - 1,
|
||||
);
|
||||
const fallback = updatedChildren[Math.max(0, fallbackIndex)];
|
||||
dispatch({
|
||||
type: "PARTICLE_REMOVED",
|
||||
type: 'PARTICLE_REMOVED',
|
||||
removedParticleId: removed.id,
|
||||
fallbackParticleId: fallback?.id ?? null,
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const { children } = useLiveParticleChildren(path, {
|
||||
orderByField: "created_at",
|
||||
orderDirection: "asc",
|
||||
orderByField: 'created_at',
|
||||
orderDirection: 'asc',
|
||||
onAdded: onParticleAdded,
|
||||
onRemoved: onParticleRemoved,
|
||||
});
|
||||
@@ -129,10 +136,15 @@ export function useStreamPlayback(
|
||||
|
||||
const currentParticle = currentIndex !== -1 ? children[currentIndex] : null;
|
||||
|
||||
// Keep the latest-index ref in sync for onParticleRemoved (above).
|
||||
useEffect(() => {
|
||||
currentIndexRef.current = currentIndex;
|
||||
}, [currentIndex]);
|
||||
|
||||
const initFallback = useEvent(() => {
|
||||
if (state.initialized || children.length === 0) return;
|
||||
initializedForRef.current = streamParticle.id;
|
||||
dispatch({ type: "INIT", particleId: children[0].id });
|
||||
dispatch({ type: 'INIT', particleId: children[0].id });
|
||||
});
|
||||
|
||||
// --- Init logic: runs on every children change until initialized ---
|
||||
@@ -149,11 +161,11 @@ export function useStreamPlayback(
|
||||
|
||||
if (children.length === 0) return;
|
||||
|
||||
const playbackPosition = streamParticle.playback_markers?.[userId ?? ""];
|
||||
const playbackPosition = streamParticle.playback_markers?.[userId ?? ''];
|
||||
|
||||
if (!playbackPosition) {
|
||||
initializedForRef.current = streamParticle.id;
|
||||
dispatch({ type: "INIT", particleId: children[0].id });
|
||||
dispatch({ type: 'INIT', particleId: children[0].id });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -163,12 +175,12 @@ export function useStreamPlayback(
|
||||
|
||||
if (found) {
|
||||
initializedForRef.current = streamParticle.id;
|
||||
dispatch({ type: "INIT", particleId: found.id });
|
||||
dispatch({ type: 'INIT', particleId: found.id });
|
||||
return;
|
||||
} else {
|
||||
initializedForRef.current = streamParticle.id;
|
||||
dispatch({
|
||||
type: "INIT",
|
||||
type: 'INIT',
|
||||
particleId: children[children.length - 1].id,
|
||||
});
|
||||
}
|
||||
@@ -201,7 +213,7 @@ export function useStreamPlayback(
|
||||
lastPersistedMarkerRef.current = currentTime;
|
||||
const streamDocPath = toFirestoreDocPath(path);
|
||||
updateStreamPlaybackMarker(streamDocPath, userId, currentTime).catch(
|
||||
(err) => logError(err, { scope: "playback.marker", path }),
|
||||
(err) => logError(err, { scope: 'playback.marker', path }),
|
||||
);
|
||||
// streamParticle.playback_markers is read at effect time; not in deps to
|
||||
// avoid double-writes when the snapshot we just persisted echoes back.
|
||||
@@ -213,18 +225,18 @@ export function useStreamPlayback(
|
||||
if (currentIndex === -1) return;
|
||||
if (currentIndex < children.length - 1) {
|
||||
dispatch({
|
||||
type: "SET_PARTICLE",
|
||||
type: 'SET_PARTICLE',
|
||||
particleId: children[currentIndex + 1].id,
|
||||
});
|
||||
} else {
|
||||
dispatch({ type: "END" });
|
||||
dispatch({ type: 'END' });
|
||||
}
|
||||
}, [children, currentIndex]);
|
||||
|
||||
const prev = useCallback(() => {
|
||||
if (currentIndex <= 0) return;
|
||||
dispatch({
|
||||
type: "SET_PARTICLE",
|
||||
type: 'SET_PARTICLE',
|
||||
particleId: children[currentIndex - 1].id,
|
||||
});
|
||||
}, [children, currentIndex]);
|
||||
@@ -232,7 +244,7 @@ export function useStreamPlayback(
|
||||
const goTo = useCallback(
|
||||
(index: number) => {
|
||||
if (index >= 0 && index < children.length) {
|
||||
dispatch({ type: "SET_PARTICLE", particleId: children[index].id });
|
||||
dispatch({ type: 'SET_PARTICLE', particleId: children[index].id });
|
||||
}
|
||||
},
|
||||
[children],
|
||||
@@ -241,7 +253,7 @@ export function useStreamPlayback(
|
||||
// If the particle isn't in `children` yet (e.g. just-created), the live
|
||||
// query will resolve it shortly and the derived index/particle will catch up.
|
||||
const goToParticle = useCallback((particleId: string) => {
|
||||
dispatch({ type: "SET_PARTICLE", particleId });
|
||||
dispatch({ type: 'SET_PARTICLE', particleId });
|
||||
}, []);
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useId } from "react";
|
||||
import { usePlaybackPauseStore } from "@/stores/playback-pause-store";
|
||||
import { useEffect, useId } from 'react';
|
||||
import { usePlaybackPauseStore } from '@/stores/playback-pause-store';
|
||||
|
||||
/**
|
||||
* Suspend stream playback while `active` is true. The hook owns its own
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useMemo } from "react";
|
||||
import type { Transcript } from "@/api/types";
|
||||
import { useMemo } from 'react';
|
||||
import type { Transcript } from '@/api/types';
|
||||
|
||||
type Sentence = Transcript["paragraphs"][number]["sentences"][number];
|
||||
type Sentence = Transcript['paragraphs'][number]['sentences'][number];
|
||||
|
||||
interface TranscriptPlaybackState {
|
||||
/** The sentence currently being spoken, or null if between sentences */
|
||||
|
||||
Reference in New Issue
Block a user