Last active
January 28, 2026 07:35
-
-
Save msyfls123/0adcb79eff981c10f8d8996f69581c11 to your computer and use it in GitHub Desktop.
VoxDeck & Aholo usefull code snippets
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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, | |
| }, | |
| }, | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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, | |
| })), | |
| ], | |
| }, | |
| }, | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) | |
| ); | |
| }) | |
| ); | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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