@morscherlab/mld-sdk 0.9.8 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/composables/useAppExperiment.test.d.ts +1 -0
- package/dist/components/AppTopBar.vue.js +61 -28
- package/dist/components/AppTopBar.vue.js.map +1 -1
- package/dist/components/AuditTrail.vue.d.ts +1 -1
- package/dist/components/ExperimentSelectorModal.vue.d.ts +1 -1
- package/dist/components/ScientificNumber.vue.d.ts +1 -1
- package/dist/composables/index.d.ts +1 -0
- package/dist/composables/index.js +3 -0
- package/dist/composables/index.js.map +1 -1
- package/dist/composables/useAppExperiment.d.ts +34 -0
- package/dist/composables/useAppExperiment.js +91 -0
- package/dist/composables/useAppExperiment.js.map +1 -0
- package/dist/composables/usePlatformContext.js +8 -1
- package/dist/composables/usePlatformContext.js.map +1 -1
- package/dist/composables/useTheme.js +23 -25
- package/dist/composables/useTheme.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/stores/auth.d.ts +1 -1
- package/dist/stores/settings.d.ts +1 -1
- package/dist/styles.css +6926 -6926
- package/package.json +1 -1
- package/src/__tests__/composables/useAppExperiment.test.ts +560 -0
- package/src/components/AppTopBar.vue +38 -2
- package/src/composables/index.ts +7 -0
- package/src/composables/useAppExperiment.ts +143 -0
- package/src/composables/usePlatformContext.ts +7 -1
- package/src/composables/useTheme.ts +33 -28
- package/src/index.ts +5 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePlatformContext.js","sources":["../../src/composables/usePlatformContext.ts"],"sourcesContent":["import { ref, computed, onMounted, onUnmounted } from 'vue'\nimport type { PlatformContext, PlatformContextOptions, PlatformEvent } from '../types'\n\nconst platformContext = ref<PlatformContext>({\n isIntegrated: false,\n theme: 'system',\n})\n\n// Track allowed origins for postMessage security\nlet allowedOrigins: Set<string> = new Set()\nlet allowAnyOrigin = false\nlet initialized = false\nlet listenerCount = 0\nlet currentHandler: ((event: MessageEvent) => void) | null = null\n\n/**\n * Derive origin from URL (protocol + host)\n */\nfunction getOriginFromUrl(url: string): string | null {\n try {\n const parsed = new URL(url)\n return parsed.origin\n } catch {\n return null\n }\n}\n\n/**\n * Check if an origin is allowed for postMessage communication\n */\nfunction isOriginAllowed(origin: string): boolean {\n // Development mode: allow any origin (must be explicitly enabled)\n if (allowAnyOrigin) {\n console.warn('[MLD SDK] postMessage origin validation disabled - only use in development')\n return true\n }\n\n // Same origin is always allowed\n if (origin === window.location.origin) {\n return true\n }\n\n // Check against allowed origins list\n return allowedOrigins.has(origin)\n}\n\n/**\n * Platform context composable for plugin integration with MLD Platform.\n *\n * Provides secure communication with the parent platform via postMessage.\n *\n * @param options - Configuration options\n * @param options.allowedOrigins - List of allowed origins for postMessage\n * @param options.allowAnyOrigin - Allow any origin (UNSAFE, development only)\n *\n * @example\n * ```typescript\n * // Basic usage - derives origin from platform injection\n * const { isIntegrated, user, theme } = usePlatformContext()\n *\n * // With explicit allowed origins\n * const { isIntegrated } = usePlatformContext({\n * allowedOrigins: ['https://mld.example.com']\n * })\n *\n * // Development mode (UNSAFE)\n * const { isIntegrated } = usePlatformContext({\n * allowAnyOrigin: import.meta.env.DEV\n * })\n * ```\n */\nexport function usePlatformContext(options: PlatformContextOptions = {}) {\n function detectPlatform(): void {\n // Check if running under MLD Platform by looking for platform-injected global\n const platformData = (window as unknown as { __MLD_PLATFORM__?: PlatformContext }).__MLD_PLATFORM__\n\n if (platformData) {\n platformContext.value = {\n ...platformData,\n isIntegrated: true,\n }\n\n // Derive platform origin from injected data\n if (platformData.platformOrigin) {\n allowedOrigins.add(platformData.platformOrigin)\n } else if (platformData.platformApiUrl) {\n const origin = getOriginFromUrl(platformData.platformApiUrl)\n if (origin) {\n allowedOrigins.add(origin)\n }\n }\n } else {\n // Check for platform indicator in URL or localStorage\n const urlParams = new URLSearchParams(window.location.search)\n const hasPluginParam = urlParams.has('mld-plugin')\n\n // Try to get platform origin from URL parameter\n const platformOrigin = urlParams.get('mld-origin')\n if (platformOrigin) {\n const origin = getOriginFromUrl(platformOrigin)\n if (origin) {\n allowedOrigins.add(origin)\n }\n }\n\n platformContext.value = {\n isIntegrated: hasPluginParam,\n theme: (localStorage.getItem('mld-theme') as 'light' | 'dark' | 'system') || 'system',\n platformOrigin: platformOrigin || undefined,\n }\n }\n\n // Add user-provided allowed origins\n if (options.allowedOrigins) {\n for (const origin of options.allowedOrigins) {\n const normalized = getOriginFromUrl(origin) || origin\n allowedOrigins.add(normalized)\n }\n }\n\n // Set development mode flag\n if (options.allowAnyOrigin) {\n allowAnyOrigin = true\n }\n }\n\n function handlePlatformMessage(event: MessageEvent): void {\n // Only accept messages from parent window (platform)\n if (event.source !== window.parent) return\n\n // Validate origin for security\n if (!isOriginAllowed(event.origin)) {\n console.warn(`[MLD SDK] Rejected postMessage from untrusted origin: ${event.origin}`)\n return\n }\n\n try {\n const platformEvent = event.data as PlatformEvent\n if (!platformEvent.type?.startsWith('mld:')) return\n\n switch (platformEvent.type) {\n case 'mld:theme-changed':\n platformContext.value.theme = platformEvent.payload as 'light' | 'dark' | 'system'\n break\n case 'mld:user-changed':\n platformContext.value.user = platformEvent.payload as PlatformContext['user']\n break\n }\n } catch {\n // Ignore invalid messages\n }\n }\n\n /**\n * Send a message to the parent platform.\n * Uses validated target origin for security.\n */\n function sendToPlatform(event: PlatformEvent): void {\n if (!platformContext.value.isIntegrated || window.parent === window) {\n return\n }\n\n // Determine target origin\n let targetOrigin: string\n\n if (platformContext.value.platformOrigin) {\n // Use explicitly configured platform origin\n targetOrigin = platformContext.value.platformOrigin\n } else if (allowedOrigins.size > 0) {\n // Use first allowed origin (typically the platform)\n targetOrigin = allowedOrigins.values().next().value as string\n } else if (allowAnyOrigin) {\n // Development mode fallback\n targetOrigin = '*'\n console.warn('[MLD SDK] Using wildcard origin for postMessage - only use in development')\n } else {\n // Safety: if no origin is configured, log warning and don't send\n console.warn('[MLD SDK] Cannot send postMessage: no platform origin configured')\n return\n }\n\n window.parent.postMessage(event, targetOrigin)\n }\n\n /**\n * Request navigation to a path in the platform.\n */\n function navigate(path: string): void {\n sendToPlatform({\n type: 'mld:navigate',\n payload: path,\n })\n }\n\n /**\n * Show a notification in the platform.\n */\n function notify(message: string, type: 'success' | 'error' | 'warning' | 'info' = 'info'): void {\n sendToPlatform({\n type: 'mld:notification',\n payload: { message, type },\n })\n }\n\n onMounted(() => {\n if (!initialized) {\n detectPlatform()\n currentHandler = handlePlatformMessage\n window.addEventListener('message', handlePlatformMessage)\n initialized = true\n }\n listenerCount++\n })\n\n onUnmounted(() => {\n listenerCount--\n if (listenerCount <= 0 && currentHandler) {\n window.removeEventListener('message', currentHandler)\n currentHandler = null\n initialized = false\n listenerCount = 0\n }\n })\n\n const isIntegrated = computed(() => platformContext.value.isIntegrated)\n const plugin = computed(() => platformContext.value.plugin)\n const user = computed(() => platformContext.value.user)\n const theme = computed(() => platformContext.value.theme)\n const features = computed(() => platformContext.value.features)\n\n return {\n context: platformContext,\n isIntegrated,\n plugin,\n user,\n theme,\n features,\n navigate,\n notify,\n sendToPlatform,\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"usePlatformContext.js","sources":["../../src/composables/usePlatformContext.ts"],"sourcesContent":["import { ref, computed, onMounted, onUnmounted } from 'vue'\nimport type { PlatformContext, PlatformContextOptions, PlatformEvent } from '../types'\n\nconst platformContext = ref<PlatformContext>({\n isIntegrated: false,\n theme: 'system',\n})\n\n// Track allowed origins for postMessage security\nlet allowedOrigins: Set<string> = new Set()\nlet allowAnyOrigin = false\nlet initialized = false\nlet listenerCount = 0\nlet currentHandler: ((event: MessageEvent) => void) | null = null\n\n/**\n * Derive origin from URL (protocol + host)\n */\nfunction getOriginFromUrl(url: string): string | null {\n try {\n const parsed = new URL(url)\n return parsed.origin\n } catch {\n return null\n }\n}\n\n/**\n * Check if an origin is allowed for postMessage communication\n */\nfunction isOriginAllowed(origin: string): boolean {\n // Development mode: allow any origin (must be explicitly enabled)\n if (allowAnyOrigin) {\n console.warn('[MLD SDK] postMessage origin validation disabled - only use in development')\n return true\n }\n\n // Same origin is always allowed\n if (origin === window.location.origin) {\n return true\n }\n\n // Check against allowed origins list\n return allowedOrigins.has(origin)\n}\n\n/**\n * Platform context composable for plugin integration with MLD Platform.\n *\n * Provides secure communication with the parent platform via postMessage.\n *\n * @param options - Configuration options\n * @param options.allowedOrigins - List of allowed origins for postMessage\n * @param options.allowAnyOrigin - Allow any origin (UNSAFE, development only)\n *\n * @example\n * ```typescript\n * // Basic usage - derives origin from platform injection\n * const { isIntegrated, user, theme } = usePlatformContext()\n *\n * // With explicit allowed origins\n * const { isIntegrated } = usePlatformContext({\n * allowedOrigins: ['https://mld.example.com']\n * })\n *\n * // Development mode (UNSAFE)\n * const { isIntegrated } = usePlatformContext({\n * allowAnyOrigin: import.meta.env.DEV\n * })\n * ```\n */\nexport function usePlatformContext(options: PlatformContextOptions = {}) {\n function detectPlatform(): void {\n // Check if running under MLD Platform by looking for platform-injected global\n const platformData = (window as unknown as { __MLD_PLATFORM__?: PlatformContext }).__MLD_PLATFORM__\n\n if (platformData) {\n platformContext.value = {\n ...platformData,\n isIntegrated: true,\n }\n\n // Derive platform origin from injected data\n if (platformData.platformOrigin) {\n allowedOrigins.add(platformData.platformOrigin)\n } else if (platformData.platformApiUrl) {\n const origin = getOriginFromUrl(platformData.platformApiUrl)\n if (origin) {\n allowedOrigins.add(origin)\n }\n }\n } else {\n // Check for platform indicator in URL or localStorage\n const urlParams = new URLSearchParams(window.location.search)\n const hasPluginParam = urlParams.has('mld-plugin')\n\n // Try to get platform origin from URL parameter\n const platformOrigin = urlParams.get('mld-origin')\n if (platformOrigin) {\n const origin = getOriginFromUrl(platformOrigin)\n if (origin) {\n allowedOrigins.add(origin)\n }\n }\n\n platformContext.value = {\n isIntegrated: hasPluginParam,\n theme: (() => {\n try {\n const s = localStorage.getItem('mld-settings')\n if (s) return (JSON.parse(s).theme as 'light' | 'dark' | 'system') || 'system'\n } catch { /* ignore */ }\n return 'system'\n })(),\n platformOrigin: platformOrigin || undefined,\n }\n }\n\n // Add user-provided allowed origins\n if (options.allowedOrigins) {\n for (const origin of options.allowedOrigins) {\n const normalized = getOriginFromUrl(origin) || origin\n allowedOrigins.add(normalized)\n }\n }\n\n // Set development mode flag\n if (options.allowAnyOrigin) {\n allowAnyOrigin = true\n }\n }\n\n function handlePlatformMessage(event: MessageEvent): void {\n // Only accept messages from parent window (platform)\n if (event.source !== window.parent) return\n\n // Validate origin for security\n if (!isOriginAllowed(event.origin)) {\n console.warn(`[MLD SDK] Rejected postMessage from untrusted origin: ${event.origin}`)\n return\n }\n\n try {\n const platformEvent = event.data as PlatformEvent\n if (!platformEvent.type?.startsWith('mld:')) return\n\n switch (platformEvent.type) {\n case 'mld:theme-changed':\n platformContext.value.theme = platformEvent.payload as 'light' | 'dark' | 'system'\n break\n case 'mld:user-changed':\n platformContext.value.user = platformEvent.payload as PlatformContext['user']\n break\n }\n } catch {\n // Ignore invalid messages\n }\n }\n\n /**\n * Send a message to the parent platform.\n * Uses validated target origin for security.\n */\n function sendToPlatform(event: PlatformEvent): void {\n if (!platformContext.value.isIntegrated || window.parent === window) {\n return\n }\n\n // Determine target origin\n let targetOrigin: string\n\n if (platformContext.value.platformOrigin) {\n // Use explicitly configured platform origin\n targetOrigin = platformContext.value.platformOrigin\n } else if (allowedOrigins.size > 0) {\n // Use first allowed origin (typically the platform)\n targetOrigin = allowedOrigins.values().next().value as string\n } else if (allowAnyOrigin) {\n // Development mode fallback\n targetOrigin = '*'\n console.warn('[MLD SDK] Using wildcard origin for postMessage - only use in development')\n } else {\n // Safety: if no origin is configured, log warning and don't send\n console.warn('[MLD SDK] Cannot send postMessage: no platform origin configured')\n return\n }\n\n window.parent.postMessage(event, targetOrigin)\n }\n\n /**\n * Request navigation to a path in the platform.\n */\n function navigate(path: string): void {\n sendToPlatform({\n type: 'mld:navigate',\n payload: path,\n })\n }\n\n /**\n * Show a notification in the platform.\n */\n function notify(message: string, type: 'success' | 'error' | 'warning' | 'info' = 'info'): void {\n sendToPlatform({\n type: 'mld:notification',\n payload: { message, type },\n })\n }\n\n onMounted(() => {\n if (!initialized) {\n detectPlatform()\n currentHandler = handlePlatformMessage\n window.addEventListener('message', handlePlatformMessage)\n initialized = true\n }\n listenerCount++\n })\n\n onUnmounted(() => {\n listenerCount--\n if (listenerCount <= 0 && currentHandler) {\n window.removeEventListener('message', currentHandler)\n currentHandler = null\n initialized = false\n listenerCount = 0\n }\n })\n\n const isIntegrated = computed(() => platformContext.value.isIntegrated)\n const plugin = computed(() => platformContext.value.plugin)\n const user = computed(() => platformContext.value.user)\n const theme = computed(() => platformContext.value.theme)\n const features = computed(() => platformContext.value.features)\n\n return {\n context: platformContext,\n isIntegrated,\n plugin,\n user,\n theme,\n features,\n navigate,\n notify,\n sendToPlatform,\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,33 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
let initialized = false;
|
|
1
|
+
import { computed, getCurrentInstance, onUnmounted } from "vue";
|
|
2
|
+
import { useSettingsStore } from "../stores/settings.js";
|
|
4
3
|
function useTheme() {
|
|
4
|
+
const settings = useSettingsStore();
|
|
5
|
+
const mql = typeof window !== "undefined" ? window.matchMedia("(prefers-color-scheme: dark)") : null;
|
|
6
|
+
const isDark = computed(() => {
|
|
7
|
+
if (settings.theme === "system") {
|
|
8
|
+
return (mql == null ? void 0 : mql.matches) ?? false;
|
|
9
|
+
}
|
|
10
|
+
return settings.theme === "dark";
|
|
11
|
+
});
|
|
12
|
+
function onSystemChange() {
|
|
13
|
+
if (settings.theme === "system") {
|
|
14
|
+
settings.theme = "system";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
mql == null ? void 0 : mql.addEventListener("change", onSystemChange);
|
|
18
|
+
if (getCurrentInstance()) {
|
|
19
|
+
onUnmounted(() => {
|
|
20
|
+
mql == null ? void 0 : mql.removeEventListener("change", onSystemChange);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
5
23
|
function toggleTheme() {
|
|
6
|
-
|
|
24
|
+
settings.theme = isDark.value ? "light" : "dark";
|
|
7
25
|
}
|
|
8
26
|
function setTheme(theme) {
|
|
9
|
-
|
|
27
|
+
settings.theme = theme;
|
|
10
28
|
}
|
|
11
|
-
watch(isDark, (dark) => {
|
|
12
|
-
if (dark) {
|
|
13
|
-
document.documentElement.classList.add("dark");
|
|
14
|
-
} else {
|
|
15
|
-
document.documentElement.classList.remove("dark");
|
|
16
|
-
}
|
|
17
|
-
localStorage.setItem("mld-theme", dark ? "dark" : "light");
|
|
18
|
-
});
|
|
19
|
-
onMounted(() => {
|
|
20
|
-
if (initialized) return;
|
|
21
|
-
initialized = true;
|
|
22
|
-
const savedTheme = localStorage.getItem("mld-theme");
|
|
23
|
-
if (savedTheme === "dark") {
|
|
24
|
-
isDark.value = true;
|
|
25
|
-
} else if (savedTheme === "light") {
|
|
26
|
-
isDark.value = false;
|
|
27
|
-
} else {
|
|
28
|
-
isDark.value = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
29
|
return {
|
|
32
30
|
isDark,
|
|
33
31
|
toggleTheme,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTheme.js","sources":["../../src/composables/useTheme.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"useTheme.js","sources":["../../src/composables/useTheme.ts"],"sourcesContent":["import { computed, onUnmounted, getCurrentInstance, type Ref } from 'vue'\nimport { useSettingsStore } from '../stores/settings'\n\nexport interface UseThemeReturn {\n isDark: Ref<boolean>\n toggleTheme: () => void\n setTheme: (theme: 'light' | 'dark') => void\n}\n\nexport function useTheme(): UseThemeReturn {\n const settings = useSettingsStore()\n\n const mql = typeof window !== 'undefined'\n ? window.matchMedia('(prefers-color-scheme: dark)')\n : null\n\n const isDark = computed(() => {\n if (settings.theme === 'system') {\n return mql?.matches ?? false\n }\n return settings.theme === 'dark'\n })\n\n // React to system preference changes when in 'system' mode\n function onSystemChange() {\n if (settings.theme === 'system') {\n // Force reactivity by toggling theme to itself — the computed re-evaluates\n // because mql.matches changed, but Vue needs a trigger. We nudge the store.\n settings.theme = 'system'\n }\n }\n\n mql?.addEventListener('change', onSystemChange)\n\n if (getCurrentInstance()) {\n onUnmounted(() => {\n mql?.removeEventListener('change', onSystemChange)\n })\n }\n\n function toggleTheme() {\n settings.theme = isDark.value ? 'light' : 'dark'\n }\n\n function setTheme(theme: 'light' | 'dark') {\n settings.theme = theme\n }\n\n return {\n isDark,\n toggleTheme,\n setTheme,\n }\n}\n"],"names":[],"mappings":";;AASO,SAAS,WAA2B;AACzC,QAAM,WAAW,iBAAA;AAEjB,QAAM,MAAM,OAAO,WAAW,cAC1B,OAAO,WAAW,8BAA8B,IAChD;AAEJ,QAAM,SAAS,SAAS,MAAM;AAC5B,QAAI,SAAS,UAAU,UAAU;AAC/B,cAAO,2BAAK,YAAW;AAAA,IACzB;AACA,WAAO,SAAS,UAAU;AAAA,EAC5B,CAAC;AAGD,WAAS,iBAAiB;AACxB,QAAI,SAAS,UAAU,UAAU;AAG/B,eAAS,QAAQ;AAAA,IACnB;AAAA,EACF;AAEA,6BAAK,iBAAiB,UAAU;AAEhC,MAAI,sBAAsB;AACxB,gBAAY,MAAM;AAChB,iCAAK,oBAAoB,UAAU;AAAA,IACrC,CAAC;AAAA,EACH;AAEA,WAAS,cAAc;AACrB,aAAS,QAAQ,OAAO,QAAQ,UAAU;AAAA,EAC5C;AAEA,WAAS,SAAS,OAAyB;AACzC,aAAS,QAAQ;AAAA,EACnB;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { MLDSdk, default } from './install';
|
|
2
2
|
export { BaseButton, BaseInput, BaseTextarea, BaseSelect, BaseCheckbox, BaseToggle, BaseRadioGroup, BaseSlider, ColorSlider, BaseTabs, BaseModal, FormField, DatePicker, TimePicker, TagsInput, NumberInput, FileUploader, AlertBox, ToastNotification, IconButton, ThemeToggle, SettingsButton, CollapsibleCard, AppTopBar, AppSidebar, AppLayout, AppContainer, Skeleton, WellPlate, RackEditor, SampleLegend, PlateMapEditor, ExperimentTimeline, SampleSelector, GroupingModal, AutoGroupModal, GroupAssigner, MoleculeInput, ConcentrationInput, DoseCalculator, ReagentList, SampleHierarchyTree, ProtocolStepEditor, SegmentedControl, MultiSelect, BasePill, DropdownButton, Calendar, DataFrame, LoadingSpinner, Divider, StatusIndicator, ProgressBar, Avatar, EmptyState, Breadcrumb, Tooltip, ConfirmDialog, ChartContainer, SettingsModal, ScientificNumber, ChemicalFormula, FormulaInput, SequenceInput, UnitInput, StepWizard, AuditTrail, BatchProgressList, ExperimentDataViewer, ExperimentCodeBadge, DateTimePicker, TimeRangeInput, ScheduleCalendar, ResourceCard, ExperimentSelectorModal, ExperimentPopover, FitPanel, } from './components';
|
|
3
|
-
export { useApi, useAuth, usePasskey, useTheme, useToast, usePlatformContext, useWellPlateEditor, useConcentrationUnits, useDoseCalculator, useProtocolTemplates, useRackEditor, useChemicalFormula, ATOMIC_WEIGHTS, useSequenceUtils, type ApiClientOptions, type UseWellPlateEditorOptions, type UseWellPlateEditorReturn, type UseRackEditorOptions, type UseRackEditorReturn, type ConcentrationValue, type ConcentrationUnit, type VolumeValue, type VolumeUnit, type StepTemplate, type FormulaParseResult, type FormulaPart, type SequenceType, type SequenceStats, parseTime, formatTime, generateTimeSlots, rangesOverlap, durationMinutes, formatDuration, isTimeInRange, findAvailableSlots, snapToSlot, addMinutes, compareTime, useScheduleDrag, usePluginConfig, type UsePluginConfigReturn, useAutoGroup, DEFAULT_COLORS, useExperimentSelector, type UseExperimentSelectorOptions, type UseExperimentSelectorReturn, formatExperimentDate, datePresetToISO, EXPERIMENT_STATUS_OPTIONS, EXPERIMENT_STATUS_VARIANT_MAP, EXPERIMENT_STATUS_LABELS, DATE_PRESET_OPTIONS, SORT_OPTIONS, useExperimentData, type UseExperimentDataOptions, type UseExperimentDataReturn, } from './composables';
|
|
3
|
+
export { useApi, useAuth, usePasskey, useTheme, useToast, usePlatformContext, useWellPlateEditor, useConcentrationUnits, useDoseCalculator, useProtocolTemplates, useRackEditor, useChemicalFormula, ATOMIC_WEIGHTS, useSequenceUtils, type ApiClientOptions, type UseWellPlateEditorOptions, type UseWellPlateEditorReturn, type UseRackEditorOptions, type UseRackEditorReturn, type ConcentrationValue, type ConcentrationUnit, type VolumeValue, type VolumeUnit, type StepTemplate, type FormulaParseResult, type FormulaPart, type SequenceType, type SequenceStats, parseTime, formatTime, generateTimeSlots, rangesOverlap, durationMinutes, formatDuration, isTimeInRange, findAvailableSlots, snapToSlot, addMinutes, compareTime, useScheduleDrag, usePluginConfig, type UsePluginConfigReturn, useAutoGroup, DEFAULT_COLORS, useExperimentSelector, type UseExperimentSelectorOptions, type UseExperimentSelectorReturn, formatExperimentDate, datePresetToISO, EXPERIMENT_STATUS_OPTIONS, EXPERIMENT_STATUS_VARIANT_MAP, EXPERIMENT_STATUS_LABELS, DATE_PRESET_OPTIONS, SORT_OPTIONS, useExperimentData, type UseExperimentDataOptions, type UseExperimentDataReturn, useAppExperiment, APP_EXPERIMENT_KEY, type UseAppExperimentOptions, type UseAppExperimentReturn, } from './composables';
|
|
4
4
|
export { useAuthStore, useSettingsStore, colorPalettes, type SettingsState, } from './stores';
|
|
5
5
|
export type { ContainerDirection, ButtonVariant, ButtonSize, InputType, ModalSize, AlertType, Toast, TabItem, SelectOption, RadioOption, FormFieldProps, SidebarToolSection, CollapsibleState, TopBarVariant, TopBarPage, TopBarTab, TopBarTabOption, TopBarSettingsConfig, WellPlateFormat, WellState, WellPlateSelectionMode, Well, HeatmapColorScale, HeatmapConfig, SlotPosition, WellExtendedData, WellEditData, WellEditField, WellLegendItem, Rack, SampleType, PlateMap, PlateMapEditorState, ProtocolStepType, ProtocolStepStatus, ProtocolStep, SampleGroup, GroupItem, OutlierAction, InputMode, OutlierInfo, ColumnInfo, MetadataRow, AutoGroupResult, ParsedCsvData, FileUploaderMode, SegmentedOption, SegmentedControlVariant, SegmentedControlSize, MultiSelectOption, MultiSelectSize, PillVariant, PillSize, CalendarSelectionMode, CalendarMarker, CalendarDayContext, SortDirection, SortState, DataFrameColumn, PaginationState, SpinnerSize, SpinnerVariant, DividerSpacing, StatusType, ProgressVariant, ProgressSize, AvatarSize, EmptyStateColor, EmptyStateSize, BreadcrumbItem, TooltipPosition, ConfirmVariant, SettingsTab, NumberNotation, TimePickerFormat, TimeRange, ScheduleView, ScheduleEventStatus, ScheduleEvent, ScheduleBlockedSlot, ScheduleSlotContext, ScheduleEventCreateContext, ScheduleEventUpdateContext, ResourceStatus, ResourceSpec, ExperimentStatus, ExperimentSummary, ExperimentListResponse, ExperimentFilters, FitState, FitResultSummary, UnitOption, WizardStep, WizardStepState, AuditEntryType, AuditEntry, BatchItemStatus, BatchItem, BatchSummary, AuthConfig, UserInfo, LoginResponse, TokenVerifyResponse, RegisterRequest, UpdateProfileRequest, CredentialInfo, SummaryData, SummarySection, SummarySectionItem, TreeNode, TreeNodeType, PluginInfo, PluginNavItem, PluginSettings, PluginSettingField, PlatformContext, PlatformEventType, PlatformEvent, ThemeMode, ColorPalette, TableDensity, } from './types';
|
package/dist/index.js
CHANGED
|
@@ -173,9 +173,11 @@ import { usePluginConfig } from "./composables/usePluginConfig.js";
|
|
|
173
173
|
import { useExperimentSelector } from "./composables/useExperimentSelector.js";
|
|
174
174
|
import { DATE_PRESET_OPTIONS, EXPERIMENT_STATUS_LABELS, EXPERIMENT_STATUS_OPTIONS, EXPERIMENT_STATUS_VARIANT_MAP, SORT_OPTIONS, datePresetToISO, formatExperimentDate } from "./composables/experiment-utils.js";
|
|
175
175
|
import { useExperimentData } from "./composables/useExperimentData.js";
|
|
176
|
+
import { APP_EXPERIMENT_KEY, useAppExperiment } from "./composables/useAppExperiment.js";
|
|
176
177
|
import { useAuthStore } from "./stores/auth.js";
|
|
177
178
|
import { colorPalettes, useSettingsStore } from "./stores/settings.js";
|
|
178
179
|
export {
|
|
180
|
+
APP_EXPERIMENT_KEY,
|
|
179
181
|
ATOMIC_WEIGHTS,
|
|
180
182
|
default25 as AlertBox,
|
|
181
183
|
default34 as AppContainer,
|
|
@@ -277,6 +279,7 @@ export {
|
|
|
277
279
|
rangesOverlap,
|
|
278
280
|
snapToSlot,
|
|
279
281
|
useApi,
|
|
282
|
+
useAppExperiment,
|
|
280
283
|
useAuth,
|
|
281
284
|
useAuthStore,
|
|
282
285
|
useAutoGroup,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/stores/auth.d.ts
CHANGED
|
@@ -47,7 +47,7 @@ export declare const useAuthStore: import('pinia').StoreDefinition<"mld-auth", P
|
|
|
47
47
|
setError: (message: string | null) => void;
|
|
48
48
|
setLoading: (loading: boolean) => void;
|
|
49
49
|
logout: () => void;
|
|
50
|
-
}, "error" | "username" | "
|
|
50
|
+
}, "error" | "username" | "token" | "tokenExpires" | "userInfo" | "authConfig" | "isInitialized" | "isLoading">, Pick<{
|
|
51
51
|
token: import('vue').Ref<string | null, string | null>;
|
|
52
52
|
tokenExpires: import('vue').Ref<Date | null, Date | null>;
|
|
53
53
|
username: import('vue').Ref<string | null, string | null>;
|
|
@@ -72,4 +72,4 @@ export declare const useSettingsStore: import('pinia').StoreDefinition<"mld-sett
|
|
|
72
72
|
isDark: () => boolean;
|
|
73
73
|
getApiBaseUrl: () => string;
|
|
74
74
|
getWsBaseUrl: () => string;
|
|
75
|
-
}, "
|
|
75
|
+
}, "initialize" | "applyTheme" | "persistSettings" | "resetToDefaults" | "getPaletteHues" | "isDark" | "getApiBaseUrl" | "getWsBaseUrl">>;
|