Skip to content

Instantly share code, notes, and snippets.

@msyfls123
Last active January 28, 2026 07:35
Show Gist options
  • Select an option

  • Save msyfls123/0adcb79eff981c10f8d8996f69581c11 to your computer and use it in GitHub Desktop.

Select an option

Save msyfls123/0adcb79eff981c10f8d8996f69581c11 to your computer and use it in GitHub Desktop.
VoxDeck & Aholo usefull code snippets
import type { IPanoramaGenerationItem } from '@pages/holo-global-site-aholo/types/machines/panorama';
import { assign, enqueueActions, setup } from 'xstate';
import { v4 } from 'uuid';
import {
type GenerationGlobalEvents,
type IGenerationGlobalContext,
} from '@pages/holo-global-site-aholo/types/generation';
import { GenerateMode } from '@pages/holo-global-site-aholo/types/machines/base';
import { panoramaMachine } from './Panorama';
export const globalMachine = setup({
types: {
context: {} as IGenerationGlobalContext,
events: {} as GenerationGlobalEvents,
},
actors: {
panorama: panoramaMachine,
},
actions: {
restoreChildren: enqueueActions(({ context, enqueue, self }) => {
context.generationItems.forEach((item) => {
if (item.mode === GenerateMode.PANORAMA_GRAPHICS) {
if (item.phase !== 'done') {
enqueue.spawnChild('panorama', {
id: item.generationId,
input: {
...item,
parentRef: self,
},
});
}
}
});
}),
},
guards: {
hasProcessingItem: ({ context }) => context.generationItems.length > 0,
},
}).createMachine({
/** @xstate-layout N4IgpgJg5mDOIC5RQDYHsBGBDFBZLAxgBYCWAdmAMQCSActQCrUCCAMtQFoCiA+o17gDaABgC6iUAAc0sEgBcSaMhJAAPRACYAzADYAdFoAsOgBwBOYdoCsOjaZ0AaEAE9Nw4XoCM74Z8NajC09zAF8Qp1RMHHxicioAVQAFABFmBl5E5loAeQAlZlxmEXEkEGlZBSUVdQQNTzM9DTMtAHYdKwsdbxarEydXBG99YSsfFs8dFsNR5rCI9Gw8QlIKSlyBbIA1Xn4hMRVy+UVlUpqNQw09QxMezysrfx1pq37ETw05kEjFmJWwPQATnA5GgAeQoJRigcZEcqqdNKZGrYrBptJYrMEtK8EFoTB4dM0Wm0-FNPLiWp9vtFlnFAcDQeDIZ4SlIYZUTqAzoi7BoUWjeZjsf4GhMbnjdF1TCYwuEQGQ0BA4CoqUtYhRoRVjtVEABaTzYjQmTxeUaWO6WLQokyGSkLalq-5A2AgsFkKAa2EctSIQwtbG6Ex6Ey8yztQwXQzo21RVV-OlYCADVmauGcn1Gk22a1EsyeHqOFw+3R6Lq8rQjMz+Iw6aM-GkUPQYACusCTZTZWvhCGuxruWd9LVz+f9LUDZJ6dla90HU1r9rjsBbkjAZEVEA97O1CAsjSnI1RIxMfOxVi0lzMpt0vQxxk8c9jtIAZlg5DguACAaCN5209v9O1mjxB4NHGPshSmK4emEM8JS0XM7xlIA */
id: 'globalMachine',
context: {
generationItems: [],
},
initial: 'restoring',
states: {
restoring: {},
idle: {
always: [
{
guard: 'hasProcessingItem',
target: 'busy',
},
{
target: 'ready',
},
],
},
ready: {
always: {
guard: ({ context }) => context.generationItems.length !== 0,
target: 'busy',
},
},
busy: {
always: {
guard: ({ context }) => context.generationItems.length === 0,
target: 'ready',
},
},
suspended: {},
fatalError: {},
},
on: {
RESTORE: {
actions: 'restoreChildren',
target: '.idle',
},
INITIALIZE_ITEM: {
actions: enqueueActions(({ enqueue, event, self }) => {
const id = v4();
const panoramaItem: IPanoramaGenerationItem = {
mode: GenerateMode.PANORAMA_GRAPHICS,
uploadId: event.uploadId,
generationId: id,
phase: 'idle',
createConfig: event.createConfig,
items: event.resources.map((resource) => ({
resourceId: resource.resourceId,
resource,
})),
};
enqueue.spawnChild('panorama', {
id,
input: {
...panoramaItem,
parentRef: self,
},
});
enqueue.assign({
generationItems: ({ context }) => [...context.generationItems, panoramaItem],
});
}),
reenter: true,
},
UPDATE_PANORAMA: {
actions: assign({
generationItems: ({ context, event }) => {
return context.generationItems.map((item) =>
item.generationId === event.item.generationId ? { ...item, ...event.item } : item
);
},
}),
},
REMOVE_ITEM: {
actions: enqueueActions(({ enqueue, event }) => {
enqueue.stopChild(event.generationId);
enqueue.assign({
generationItems: ({ context, event }) =>
context.generationItems.filter((item) => item.generationId !== event.generationId),
});
}),
reenter: true,
},
},
});
import { enqueueActions, fromObservable, sendParent, setup } from 'xstate';
import { defer, map, from, tap } from 'rxjs';
import { omit } from 'lodash-es';
import { polling } from '@common/utils/polling';
import {
PANORAMA_NEED_POLLING_STATUSES,
PanoramaConvertStatus,
type IPanoramaContext,
type IPanoramaItem,
type PanoramaEvents,
} from '@pages/holo-global-site-aholo/types/machines/panorama';
import ArcTaskAPIService from '../../api/ArcTaskAPIService';
const convertTask = fromObservable<
{ status: 'success'; items: IPanoramaItem[] } | { status: 'processing'; items: IPanoramaItem[] },
{ items: IPanoramaItem[] }
>(({ input }) => {
const { items } = input;
const service = new ArcTaskAPIService();
const poller = (taskIds: string[]) => service.batchGet({ taskIds });
const taskIdItemMap: Record<string, IPanoramaItem | undefined> = {};
const existedTaskIds: string[] = [];
items.forEach((item) => {
if (item.task) {
existedTaskIds.push(item.task.taskId);
taskIdItemMap[item.task.taskId] = item;
}
});
const needSubmitItems = items.filter((item) => !item.task);
const allTaskIds$ = (
needSubmitItems.length > 0
? defer(() =>
service.submit({
inputSourceUrls: needSubmitItems.map((item) => item.resource.url).filter(Boolean) as string[],
})
).pipe(
tap(({ submitTaskResults }) => {
submitTaskResults.forEach(({ inputSourceUrl, taskId }) => {
taskIdItemMap[taskId] = items.find((item) => item.resource.url === inputSourceUrl);
});
}),
map(({ submitTaskResults }) => submitTaskResults.map((res) => res.taskId))
)
: from([[]])
).pipe(map((data) => [...data, ...existedTaskIds]));
return allTaskIds$.pipe(
map((taskIds) => ({
payload: taskIds,
})),
polling(poller, (data) => data.every(({ status }) => !PANORAMA_NEED_POLLING_STATUSES.includes(status)), {
interval: 3000,
}),
map((res) => {
const { data, err } = res;
if (err) {
throw new Error(err);
}
const combinedItems = items.map((item) => {
const task = data?.find((t) => taskIdItemMap[t.taskId]?.resourceId === item.resourceId);
return task ? { ...item, task } : item;
});
if (data && data.some(({ status }) => PANORAMA_NEED_POLLING_STATUSES.includes(status))) {
return { status: 'processing', items: combinedItems };
}
if (data && data.every((t) => t.status === PanoramaConvertStatus.SUCCEEDED)) {
return { status: 'success', items: combinedItems };
}
throw new Error('convert not success');
})
);
});
export const panoramaMachine = setup({
types: {
context: {} as IPanoramaContext,
events: {} as PanoramaEvents,
input: {} as IPanoramaContext,
},
actors: {
convertTask,
},
actions: {},
}).createMachine({
/** @xstate-layout N4IgpgJg5mDOIC5gF8A0IB2B7CdGgAoBbAQwGMALASwzAEp8QAHLWKgFyqw0YA9EAtADZ0AT0FDkaEMXLVadAHRUIAGzCMWbTtz6IALACYxiABwBGRUIDsAVgAMhw6aEBOa69e3JU5EA */
initial: 'idle',
context: ({ input }) => input,
states: {
idle: {
always: [
{
guard: ({ context }) => context.phase === 'done',
target: 'done',
},
{
guard: ({ context }) => context.phase === 'error',
target: 'error',
},
{
target: 'convert',
},
],
},
convert: {
invoke: {
src: 'convertTask',
input: ({ context }) => ({
items: context.items,
}),
onSnapshot: [
{
guard: ({ event }) => event.snapshot.context?.status === 'success',
actions: enqueueActions(({ enqueue }) => {
enqueue.assign({
items: ({ event, context }) => {
return event.snapshot.context?.items ?? context.items;
},
});
enqueue.sendParent(({ context }) => {
return {
type: 'UPDATE_PANORAMA',
item: omit(context, ['parentRef']),
};
});
}),
target: 'done',
},
{
actions: enqueueActions(({ enqueue }) => {
enqueue.assign({
phase: 'convert',
items: ({ event, context }) => {
return event.snapshot.context?.items ?? context.items;
},
});
enqueue.sendParent(({ context }) => {
return {
type: 'UPDATE_PANORAMA',
item: omit(context, ['parentRef']),
};
});
}),
},
],
onError: 'error',
},
},
done: {
entry: enqueueActions(({ enqueue }) => {
enqueue.assign({
phase: 'done',
});
enqueue.sendParent(({ context }) => {
return {
type: 'UPDATE_PANORAMA',
item: omit(context, ['parentRef']),
};
});
}),
},
error: {
entry: enqueueActions(({ enqueue }) => {
enqueue.assign({
phase: 'error',
});
enqueue.sendParent(({ context }) => {
return {
type: 'UPDATE_PANORAMA',
item: omit(context, ['parentRef']),
};
});
}),
},
},
on: {
REMOVE_SELF: {
actions: [
sendParent(({ context }) => ({
type: 'REMOVE_ITEM',
generationId: context.generationId,
})),
],
},
},
});
import type { OperatorFunction } from 'rxjs';
import { catchError, concatMap, defer, map, of, switchMap, takeWhile, timer } from 'rxjs';
export interface PollConfig {
/**
* 最大轮询次数,可以不设置
*/
maxCount?: number;
/**
* 轮询间隔
*/
interval?: number;
}
export type PollingState<Response> =
| {
data: Response;
count: number;
err: null;
pollIndex?: number;
}
| {
data: null;
count: number;
err: any;
pollIndex?: number;
};
export const polling = <Payload, Response>(
/**
* 轮询器
*/
poller: (p: Payload) => Promise<Response>,
/**
* 预测结束条件
*/
predicate: (res: Response) => boolean,
config: PollConfig = {}
): OperatorFunction<{ payload: Payload; pollIndex?: number }, PollingState<Response>> => {
const { maxCount, interval = 1000 } = config;
return (poll$) =>
poll$.pipe(
switchMap(({ payload, pollIndex }) => {
return timer(0, interval).pipe(
concatMap((count) => {
return defer(() => poller(payload)).pipe(
map((data) => {
return { data, count, err: null, pollIndex };
}),
catchError((err) => {
return of({ data: null, count, err, pollIndex });
})
);
}),
takeWhile(({ data, count }) => {
if (data && predicate(data)) return false;
if (maxCount) return count < maxCount;
return true;
}, true)
);
})
);
};
import type { PollingState, PollConfig } from '../utils/polling';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { filter, firstValueFrom, share, Subject } from 'rxjs';
import { polling } from '../utils/polling';
import useConstant from './useConstant';
import { useConstantCallback } from './useAdvanceRef';
const usePolling = <Payload, Response>(
poller: (p: Payload) => Promise<Response>,
predicate: (res: Response) => boolean,
config: PollConfig = {}
) => {
const constantPoller = useConstantCallback(poller);
const constantPredicate = useConstantCallback(predicate);
const poll$ = useConstant(() => new Subject<{ payload: Payload; pollIndex: number }>());
const result$ = useConstant(() => poll$.pipe(polling(constantPoller, constantPredicate, config), share()));
const pollIndexRef = useRef(0);
const [state, setState] = useState<PollingState<Response> | null>(null);
const data = useMemo(() => state?.data, [state]);
useEffect(() => {
const subscription = result$.subscribe((res) => {
setState(res);
});
return () => {
subscription.unsubscribe();
};
}, [result$]);
const startPolling = useCallback(
(p: Payload, newPredicate?: (res: Response) => boolean) => {
const newPollIndex = pollIndexRef.current + 1;
pollIndexRef.current = newPollIndex;
const res = firstValueFrom(
result$.pipe(
filter(({ pollIndex }) => {
return pollIndex === newPollIndex;
}),
filter(({ data }) => {
if (!data) return false;
return newPredicate ? newPredicate(data) : constantPredicate(data);
})
)
);
poll$.next({
payload: p,
pollIndex: newPollIndex,
});
return res;
},
[poll$, constantPredicate, result$]
);
return {
data,
startPolling,
state,
};
};
export default usePolling;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment