import { omit } from '@mtyk/dash' import { push, register } from '@mtyk/dev-client' import { EventRouter } from '@mtyk/events' import React, { ComponentType, useEffect } from 'react' import { BaseActionSpec } from '@mtyk/dev-core/src/actions/BaseAction' import createMobxStore from '@mtyk/frontend/mobx/helpers/createMobxStore' import coerceAsync from '@mtyk/frontend/timing/coerceAsync' import { observable } from 'mobx' import { v4 } from 'uuid' import ActionBrowser from './ActionBrowser/ActionBrowser' export { LocalActionSpec } from './LocalActionSpec' export { MTYKDev } from './MTYKDev' export { useDevActions } from './useDevActions' export { ActionBrowser } export interface ReactActionSpec extends BaseActionSpec { reactid: string } export type ReactLocalActionFn = (() => void) | (() => Promise) export const store = new (createMobxStore( class extends EventRouter<{ 'action-triggered': { id: string } 'action-completed': { id: string } 'action-added': { id: string } 'action-removed': { id: string } 'action-failed': { id: string; error: any } }> { actionsById = observable.map() wrapAction(info: { label?: string id: string action: ReactLocalActionFn }) { // if (isDev()) { // return info.action // } return async () => { const { action, id } = info this.emit('action-triggered', { id }) push({ type: 'react', name: info.label ?? id, reactid: id, id: v4(), date: new Date(), } as any) try { const result = await action() this.emit('action-completed', { id }) return result } catch (e) { this.emit('action-failed', { id, error: e }) throw e } } } addAction = (id: string, action: ReactLocalActionFn) => { this.actionsById.set(id, action) this.emit('action-added', { id }) } removeAction = (id: string) => { this.actionsById.delete(id) this.emit('action-removed', { id }) } } ))() type ExtraProps = { id: string; label?: string; action: ReactLocalActionFn } export function makeActionComponent

( Component: ComponentType

, options: { actionProps?: string[] } ): ComponentType

{ type PropsType = P & ExtraProps return function DevComponent(props: PropsType) { const actionProp = (options.actionProps ?? []).find((prop) => prop in props) ?? 'action' const action = (props as any)[actionProp] useEffect(() => { store.actionsById.set(props.id, action) return () => { store.actionsById.delete(props.id) } }, []) return ( ) } } export function useComponentId(componentName: string) { return (strings: TemplateStringsArray, ...rest: any) => { return ( componentName + '/' + strings.map((str, i) => str + (rest[i] ?? '')).join('') ) } } // Register a handler for component specific actions register({ type: 'react', prepare: async (action, context) => { return !!store.actionsById.get(action.reactid) }, runner: async (action, context) => { const fn = store.actionsById.get(action.reactid)! await coerceAsync(fn(...context.arguments)) }, }) // Register a handler for wildcard actions as long as the id matches register({ type: '*', prepare: async (action, context) => { return !!store.actionsById.get(action.id) }, runner: async (action, context) => { const fn = store.actionsById.get(action.id) await coerceAsync(fn(...context.arguments)) }, })