import { createMachine, assign, sendParent, actions } from 'xstate'; import type { IChoice } from '$lib/Models'; import { ChoiceModel } from '$lib/Models'; import { nanoid } from '$lib/@iioioo_utils/Services/nanoid'; import { fetchMachine } from '$lib/@iioioo_utils/State/fetch.machine'; const buildOptions = (data: any[] | string, dataRegistry?, dataKey?) => { if (typeof data === 'string' && dataRegistry) { data = dataRegistry[data]; } if (Array.isArray(data)) { return data.map( (d) => new ChoiceOption({ faceValue: !!dataKey ? d[dataKey] : d, richValue: d }) ); } else { return []; } // return Array.isArray(data) ? data.map(d => new ChoiceOption({ // faceValue: !!this.dataKey ? d[this.dataKey]: d, // richValue: d // })) : []; }; const { choose, raise } = actions; export class ChoiceOption { key = nanoid(); faceValue = ''; richValue = null; constructor(choiceOption: Partial) { this.key = choiceOption.key ?? this.key; this.faceValue = choiceOption.faceValue ?? ''; this.richValue = choiceOption.richValue ?? null; } } export interface ChoiceContext { field: IChoice; options: ChoiceOption[]; activeOptions: ChoiceOption[]; broadcastEvent: { type: string; key: string }; } export type ChoiceState = | { value: 'initializing'; context: ChoiceContext; } | { value: 'fetchingDataFromRemoteSource'; context: ChoiceContext; } | { value: 'ready'; context: ChoiceContext; }; export const choiceMachine = createMachine( { context: { field: new ChoiceModel({}), options: [], activeOptions: [], broadcastEvent: { type: 'CHANGE', key: 'value' } }, tsTypes: {} as import('./choiceMachine.typegen').Typegen0, schema: { context: {} as ChoiceContext, events: {} as | { type: 'ACTIVATE_MACHINE' } | { type: 'DEACTIVATE_MACHINE' } | { type: 'FETCH_DATAKEY'; machine: any } | { type: 'FETCH_DATA'; machine: any } | { type: 'FETCH_DATA_FROM_ROOT' } | { type: 'FETCH_DATA_FROM_REMOTE_URL' } | { type: 'LOAD_DATAKEY'; dataKey: string } | { type: 'LOAD_DATA'; data: any[] } | { type: 'LOAD_REMOTE_DATA'; data: { data: any[] } } | { type: 'TOGGLE_SELECT'; choice: ChoiceOption }, services: {} as { fetchDataFromRemoteUrl: { data: any[]; }; } }, id: '', initial: 'idle', states: { idle: { on: { ACTIVATE_MACHINE: 'initializing' } }, initializing: { initial: 'loadingDataKey', states: { loadingDataKey: { entry: ['determineDataKey'], on: { FETCH_DATAKEY: { actions: ['fetchDataKey'] } } }, loadingData: { entry: ['determineData'], on: { FETCH_DATA: { actions: ['fetchDataFromRoot'] } } } } }, fetchingDataFromRemoteSource: { invoke: { id: 'fetchDataFromRemoteUrl', src: fetchMachine, data: { ...fetchMachine.context, endpoint: (context, event) => context.field.dataSourceUrl, broadcastEvent: 'LOAD_REMOTE_DATA' }, onDone: { target: 'ready', actions: [(c, e) => console.log('DONE LOADING REMOTE DATA: ', c, e), 'loadData'] } } }, ready: { on: { TOGGLE_SELECT: { actions: ['assignChoices', 'notifyParent'] } } } }, on: { LOAD_DATAKEY: { target: 'initializing.loadingData', actions: ['loadDataKey'] }, LOAD_DATA: { target: 'ready', actions: ['loadData'] }, LOAD_REMOTE_DATA: { target: 'ready', actions: ['loadRemoteData'] }, FETCH_DATA_FROM_ROOT: { target: 'ready', actions: ['fetchDataFromRoot'] }, FETCH_DATA_FROM_REMOTE_URL: 'fetchingDataFromRemoteSource', DEACTIVATE_MACHINE: 'idle' } }, { actions: { determineDataKey: choose([ { // datakey needs to be fetched cond: (context, event) => !!context.field.dataKeySourceId, actions: raise('FETCH_DATAKEY') }, { actions: raise('LOAD_DATAKEY') } ]), fetchDataKey: sendParent((context, event) => ({ type: 'SOURCE_DYNAMIC_VALUE', dataKeySourceId: context.field.dataKeySourceId, returnAction: 'LOAD_DATAKEY', returnKey: 'dataKey' })), loadDataKey: assign({ field: (context, event) => { context.field.dataKey = event.dataKey || context.field.dataKey; return context.field; } }), determineData: choose([ { cond: (context, event) => !!context.field.dataSourceId, actions: raise('FETCH_DATA_FROM_ROOT') }, { cond: (context, event) => !!context.field.dataSourceUrl, actions: raise('FETCH_DATA_FROM_REMOTE_URL') }, { cond: (context, event) => !!context.field.data, actions: raise('LOAD_DATA') } ]), // loadData: assign({ // options: (context, event) => context.field.buildOptions( event.data || context.field.data ), // activeOptions: (context, event) => [] // }), // loadRemoteData: assign({ // options: (context, event) => { // const data = Array.isArray(event.data.data) ? event.data.data : [ event.data.data ]; // return context.field.buildOptions( data ); // }, // activeOptions: (context, event) => [] // }), loadData: assign({ options: (context, event) => buildOptions((event.data as any) || context.field.data), activeOptions: (context, event) => [] }), loadRemoteData: assign({ options: (context, event) => { const data = Array.isArray(event.data.data) ? event.data.data : [event.data.data]; return buildOptions(data); }, activeOptions: (context, event) => [] }), fetchDataFromRoot: sendParent((context, event) => ({ type: 'SOURCE_DYNAMIC_VALUE', dataKeySourceId: context.field.dataSourceId, returnAction: 'LOAD_DATA', returnKey: 'data' })), assignChoices: assign((context, event) => { const newActiveOptions = context.field.subType === 'radio' ? [event.choice] : context.activeOptions.includes(event.choice) ? context.activeOptions.filter((d) => d !== event.choice) : [...context.activeOptions, event.choice]; const newFieldValue = newActiveOptions.map((d) => !!context.field.dataKey ? d.richValue[context.field.dataKey] : d.richValue ); context.field.value = context.field.subType === 'radio' ? newFieldValue[0] : newFieldValue; context.activeOptions = newActiveOptions; return context; }), notifyParent: sendParent((context) => ({ type: context.broadcastEvent.type, [context.broadcastEvent.key]: context.field })) } } ); // import { createMachine, assign, sendParent, send, actions } from 'xstate'; // import type { IChoice } from '$lib/Models'; // import { ChoiceModel } from '$lib/Models'; // import { nanoid } from '$lib/@iioioo_utils/Services/nanoid'; // const { choose } = actions; // export class ChoiceOption { // key = nanoid(); // faceValue = ''; // richValue = null; // constructor( choiceOption: Partial ){ // this.key = choiceOption.key ?? this.key; // this.faceValue = choiceOption.faceValue ?? ''; // this.richValue = choiceOption.richValue ?? null; // } // } // export interface ChoiceContext { // field: IChoice; // linkedTo: string; // active: ChoiceOption; // action: { name: string, payload: string } // } // export const choiceMachine = createMachine( // { // context: { // field: null, // linkedTo: '', // active: null, // action: { name: 'CHANGE', payload: 'value'} // }, // tsTypes: {} as import("./choiceMachine.typegen").Typegen0, // schema: { // context: {} as ChoiceContext, // events: {} as // { type: 'LOAD_OPTIONS'; data: any[] } | // { type: 'SELECT'; choice: ChoiceOption } // }, // id: '', // initial: 'ready', // states: { // ready: { // entry: [ 'fetchData' ], // on: { // LOAD_OPTIONS: { // actions: 'loadNewData' // }, // SELECT: { // actions: [ // 'assignChoices', // 'notifyParent' // ] // }, // } // } // } // }, // { // actions: { // assignChoices: assign({ // field: (context, event) => { // return { // ...context.field, // value: context.field.subType === 'radio' ? // event.choice.richValue[ context.field.dataKey ] : // [ ...context.field.value, event.choice.richValue[ context.field.dataKey ] ] // } // }, // active: (context, event) => event.choice // }), // loadNewData: assign({ // field: (context, event) => { // const newChoices = { ...context.field, data: event.data }; // return new ChoiceModel(newChoices) // }, // active: (context, event) => null, // linkedTo: (context, event) => context.linkedTo // }), // fetchData: choose([ // { // cond: (context) => !!context.linkedTo, // actions: [ // send( // (context: ChoiceContext) => ({ type: 'GET_VALUE', returnEvent: 'LOAD_OPTIONS' }), // { to: (context) => context.linkedTo } // ) // ] // } // ]), // notifyParent: sendParent((context) => { // console.log("calling parent in choice machine: ", context); // return { type: context.action.name, [context.action.payload]: context.field }; // }) // } // } // )