/** * Shared helpers for story files that need experiment selector integration. * Provides mock data, API interceptors, and a provider wrapper component. */ import { defineComponent, onUnmounted, type PropType } from 'vue' import { useAppExperiment } from '../composables/useAppExperiment' import { useApi } from '../composables/useApi' import { useExperimentStore } from '../stores/experiment' import type { ExperimentSummary, PluginInfo } from '../types' export const mockExperiments: ExperimentSummary[] = [ { id: 1, experiment_code: 'DR-HeLa-DOX-001', name: 'Doxorubicin dose-response in HeLa cells', status: 'completed', experiment_type: 'dose_response', project: 'Oncology Screen Q1', project_name: 'Oncology Screen Q1', created_at: '2026-02-28T14:30:00Z', updated_at: '2026-03-01T09:15:00Z', has_design_data: true }, { id: 2, experiment_code: 'CV-MCF7-24H-002', name: 'MCF-7 cell viability 24h time course', status: 'completed', experiment_type: 'cell_viability', project: 'Oncology Screen Q1', project_name: 'Oncology Screen Q1', created_at: '2026-02-25T10:00:00Z', updated_at: '2026-02-26T16:45:00Z', has_design_data: true }, { id: 3, experiment_code: 'DR-PANC1-GEM-003', name: 'Gemcitabine IC50 in PANC-1', status: 'ongoing', experiment_type: 'dose_response', project: 'Pancreatic Cancer', project_name: 'Pancreatic Cancer', created_at: '2026-03-01T08:00:00Z', updated_at: '2026-03-02T11:30:00Z', has_design_data: true }, { id: 4, experiment_code: 'CV-A549-CIS-004', name: 'Cisplatin cytotoxicity A549 lung', status: 'ongoing', experiment_type: 'cell_viability', project: 'Lung Cancer Panel', project_name: 'Lung Cancer Panel', created_at: '2026-03-01T13:00:00Z', updated_at: '2026-03-02T08:00:00Z', has_design_data: false }, { id: 5, experiment_code: 'DR-HEK293-CTRL-005', name: 'HEK-293T control viability baseline', status: 'completed', experiment_type: 'dose_response', project: 'Toxicity Controls', project_name: 'Toxicity Controls', created_at: '2026-02-20T09:00:00Z', updated_at: '2026-02-22T17:00:00Z', has_design_data: true }, ] const mockExperimentTypes = [ { name: 'dose_response', color: '#3b82f6' }, { name: 'cell_viability', color: '#10b981' }, ] const mockProjects = [ { id: 1, name: 'Oncology Screen Q1' }, { id: 2, name: 'Pancreatic Cancer' }, { id: 3, name: 'Lung Cancer Panel' }, { id: 4, name: 'Toxicity Controls' }, ] export function installMockInterceptor() { const { client } = useApi() client.interceptors.request.use((config) => { const url = config.url ?? '' if (url.includes('/experiments/experiment-types')) { config.adapter = () => Promise.resolve({ data: mockExperimentTypes, status: 200, statusText: 'OK', headers: {}, config }) return config } if (url.includes('/projects')) { config.adapter = () => Promise.resolve({ data: mockProjects, status: 200, statusText: 'OK', headers: {}, config }) return config } if (url.startsWith('/api/experiments')) { const params = new URLSearchParams(url.split('?')[1] ?? '') const skip = Number(params.get('skip') ?? 0) const limit = Number(params.get('limit') ?? 100) let filtered = [...mockExperiments] const status = params.get('status') const search = params.get('search')?.toLowerCase() if (status) filtered = filtered.filter(e => e.status === status) if (search) filtered = filtered.filter(e => e.name.toLowerCase().includes(search) || e.experiment_code?.toLowerCase().includes(search)) config.adapter = () => Promise.resolve({ data: { experiments: filtered.slice(skip, skip + limit), total: filtered.length }, status: 200, statusText: 'OK', headers: {}, config }) } return config }) } /** * Wrapper component that fakes integrated mode and provides useAppExperiment context. * Use in story files to demonstrate experiment selector integration. * * The preselected experiment is committed to the shared store as a full * platform record — the same path the selector modal uses at runtime. * * Props: * preselect (boolean, default true) - pre-select the first mock experiment * plugin (PluginInfo, optional) - the integrated plugin, shown as AppTopBar's static identity */ export const ExperimentProvider = defineComponent({ props: { preselect: { type: Boolean, default: true }, plugin: { type: Object as PropType, default: undefined }, }, setup(props, { slots }) { ;(window as any).__MINT_PLATFORM__ = { isIntegrated: true, theme: 'system', plugin: props.plugin } installMockInterceptor() const experimentStore = useExperimentStore() useAppExperiment({ onSave: async () => 'Saved!', }) if (props.preselect && mockExperiments[0]) { experimentStore.select(mockExperiments[0]) } onUnmounted(() => { experimentStore.clear() delete (window as any).__MINT_PLATFORM__ }) return () => slots.default?.() }, })