@morscherlab/mint-sdk 1.0.0-rc.1 → 1.0.0-rc.2
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/README.md +9 -1
- package/dist/__tests__/components/LcmsSequenceTable.test.d.ts +1 -0
- package/dist/__tests__/components/ProgressBar.test.d.ts +1 -0
- package/dist/__tests__/components/RackEditor.test.d.ts +1 -0
- package/dist/__tests__/components/SequenceProgressBar.test.d.ts +1 -0
- package/dist/__tests__/composables/useExperimentSamples.test.d.ts +1 -0
- package/dist/__tests__/utils/instrument.test.d.ts +1 -0
- package/dist/__tests__/utils/lcms.test.d.ts +1 -0
- package/dist/__tests__/utils/permissions.test.d.ts +1 -0
- package/dist/__tests__/utils/rack.test.d.ts +1 -0
- package/dist/{auth-CBG3bWEc.js → auth-B7g4J4ZF.js} +99 -5
- package/dist/auth-B7g4J4ZF.js.map +1 -0
- package/dist/components/AutoGroupModal.vue.d.ts +1 -1
- package/dist/components/BaseCheckbox.vue.d.ts +1 -1
- package/dist/components/BaseToggle.vue.d.ts +2 -2
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +1 -1
- package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +1 -1
- package/dist/components/BioTemplatePresetWorkspaceView.vue.d.ts +1 -1
- package/dist/components/DoseDesignWorkspaceView.vue.d.ts +1 -1
- package/dist/components/FormulaInput.vue.d.ts +1 -1
- package/dist/components/InstrumentAlertLog.vue.d.ts +22 -0
- package/dist/components/InstrumentStateBadge.vue.d.ts +11 -0
- package/dist/components/InstrumentStatusCard.vue.d.ts +13 -0
- package/dist/components/LcmsSequenceTable.vue.d.ts +26 -0
- package/dist/components/ProgressBar.vue.d.ts +1 -0
- package/dist/components/RackEditor.vue.d.ts +41 -3
- package/dist/components/ReagentList.vue.d.ts +1 -1
- package/dist/components/SampleSelector.vue.d.ts +5 -2
- package/dist/components/SegmentedControl.vue.d.ts +2 -0
- package/dist/components/SequenceInput.vue.d.ts +1 -1
- package/dist/components/SequenceProgressBar.vue.d.ts +15 -0
- package/dist/components/SettingsModal.vue.d.ts +3 -1
- package/dist/components/TagsInput.vue.d.ts +1 -1
- package/dist/components/WellPlate.vue.d.ts +42 -3
- package/dist/components/index.d.ts +5 -0
- package/dist/components/index.js +3 -3
- package/dist/{components-5KSfsVqf.js → components-BhK-dW99.js} +2091 -1051
- package/dist/components-BhK-dW99.js.map +1 -0
- package/dist/composables/experimentDesignData.d.ts +17 -0
- package/dist/composables/index.d.ts +2 -0
- package/dist/composables/index.js +4 -4
- package/dist/composables/useControlSchema.d.ts +11 -0
- package/dist/composables/useExperimentData.d.ts +11 -3
- package/dist/composables/useExperimentSamples.d.ts +42 -0
- package/dist/composables/usePlatformContext.d.ts +54 -0
- package/dist/{composables-D4Myb30a.js → composables-Bg7CFuNz.js} +5 -3
- package/dist/composables-Bg7CFuNz.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +168 -6
- package/dist/index.js.map +1 -0
- package/dist/install.js +2 -2
- package/dist/instrument.d.ts +7 -0
- package/dist/lcms.d.ts +27 -0
- package/dist/permissions.d.ts +46 -0
- package/dist/stores/auth.d.ts +74 -2
- package/dist/stores/index.js +1 -1
- package/dist/styles.css +3316 -1216
- package/dist/templates/builders.d.ts +7 -3
- package/dist/templates/index.d.ts +2 -2
- package/dist/templates/index.js +2 -2
- package/dist/templates/presets.d.ts +12 -0
- package/dist/templates/types.d.ts +16 -1
- package/dist/{templates-BSlxwV2c.js → templates-BorLR_7p.js} +313 -3
- package/dist/templates-BorLR_7p.js.map +1 -0
- package/dist/types/auth.d.ts +2 -0
- package/dist/types/components.d.ts +32 -3
- package/dist/types/form-builder.d.ts +2 -1
- package/dist/types/index.d.ts +4 -1
- package/dist/types/instrument.d.ts +56 -0
- package/dist/types/platform.d.ts +3 -0
- package/dist/{useExperimentData-BbbdI5xT.js → useProtocolTemplates-n6AJqSqv.js} +534 -359
- package/dist/useProtocolTemplates-n6AJqSqv.js.map +1 -0
- package/dist/utils/rack.d.ts +47 -0
- package/package.json +1 -1
- package/src/__tests__/components/AppTopBar.test.ts +15 -0
- package/src/__tests__/components/BaseTabs.test.ts +15 -0
- package/src/__tests__/components/LcmsSequenceTable.test.ts +57 -0
- package/src/__tests__/components/ProgressBar.test.ts +18 -0
- package/src/__tests__/components/RackEditor.test.ts +125 -0
- package/src/__tests__/components/SampleSelector.test.ts +25 -0
- package/src/__tests__/components/SegmentedControl.test.ts +45 -0
- package/src/__tests__/components/SequenceProgressBar.test.ts +39 -0
- package/src/__tests__/components/SettingsModal.test.ts +83 -2
- package/src/__tests__/composables/useControlSchema.test.ts +4 -0
- package/src/__tests__/composables/useExperimentData.test.ts +23 -0
- package/src/__tests__/composables/useExperimentSamples.test.ts +91 -0
- package/src/__tests__/templates/templates.test.ts +86 -0
- package/src/__tests__/utils/instrument.test.ts +47 -0
- package/src/__tests__/utils/lcms.test.ts +73 -0
- package/src/__tests__/utils/permissions.test.ts +50 -0
- package/src/__tests__/utils/rack.test.ts +120 -0
- package/src/components/AppTopBar.vue +1 -0
- package/src/components/BaseTabs.vue +22 -1
- package/src/components/InstrumentAlertLog.vue +191 -0
- package/src/components/InstrumentStateBadge.vue +50 -0
- package/src/components/InstrumentStatusCard.vue +188 -0
- package/src/components/LcmsSequenceTable.vue +191 -0
- package/src/components/ProgressBar.vue +3 -0
- package/src/components/RackEditor.vue +73 -2
- package/src/components/SampleSelector.vue +28 -9
- package/src/components/SegmentedControl.story.vue +17 -0
- package/src/components/SegmentedControl.vue +14 -3
- package/src/components/SequenceProgressBar.vue +71 -0
- package/src/components/SettingsModal.vue +42 -2
- package/src/components/WellPlate.vue +142 -21
- package/src/components/index.ts +5 -0
- package/src/components/internal/WellEditPopupInternal.vue +1 -0
- package/src/composables/experimentDesignData.ts +182 -0
- package/src/composables/index.ts +14 -0
- package/src/composables/useAuth.ts +4 -0
- package/src/composables/useAutoGroup.ts +5 -1
- package/src/composables/useControlSchema.ts +21 -0
- package/src/composables/useExperimentData.ts +57 -16
- package/src/composables/useExperimentSamples.ts +142 -0
- package/src/index.ts +27 -0
- package/src/instrument.ts +90 -0
- package/src/lcms.ts +108 -0
- package/src/permissions.ts +143 -0
- package/src/stores/auth.ts +31 -3
- package/src/styles/components/instrument-monitor.css +478 -0
- package/src/styles/components/lcms-sequence-table.css +189 -0
- package/src/styles/components/sequence-progress-bar.css +63 -0
- package/src/styles/components/tabs.css +9 -0
- package/src/styles/components/well-edit-popup.css +7 -1
- package/src/styles/components/well-plate.css +5 -0
- package/src/styles/index.css +3 -0
- package/src/templates/builders.ts +201 -0
- package/src/templates/controlSchemas.ts +68 -0
- package/src/templates/index.ts +2 -0
- package/src/templates/presets.ts +23 -0
- package/src/templates/types.ts +17 -0
- package/src/types/auth.ts +3 -0
- package/src/types/components.ts +45 -3
- package/src/types/form-builder.ts +2 -1
- package/src/types/index.ts +35 -0
- package/src/types/instrument.ts +61 -0
- package/src/types/platform.ts +4 -0
- package/src/utils/rack.ts +209 -0
- package/dist/auth-CBG3bWEc.js.map +0 -1
- package/dist/components-5KSfsVqf.js.map +0 -1
- package/dist/composables-D4Myb30a.js.map +0 -1
- package/dist/templates-BSlxwV2c.js.map +0 -1
- package/dist/useExperimentData-BbbdI5xT.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { computed, effectScope, getCurrentScope, onMounted, onScopeDispose, onUnmounted, provide, reactive, readonly, ref, shallowRef, toRaw, toValue, watch } from "vue";
|
|
1
|
+
import { Bt as ensureTemplateFromCollection, In as useControlWorkspace, Ln as getFieldRegistryEntry, Pt as createTemplateCollection, Rn as getTypeDefault, Vn as useConcentrationUnits, Vt as extractTemplateCollection, _ as toBioTemplateComponentPropsByComponent$1, _t as createBioTemplatePresetCollectionFromControls, b as toBioTemplateComponentUsage, d as getBioTemplateComponentProps$1, g as toBioTemplateComponentProps, gt as createBioTemplatePresetCollection, h as toBioTemplateComponentImports, ht as createBioTemplatePackCollection, i as createBioTemplateControlToolkit, m as toBioTemplateComponentBindingsById, nn as getBioTemplatePresetInfo, on as getBioTemplatePackInfo, p as toBioTemplateComponentBindings, u as getBioTemplateComponentBindings, v as toBioTemplateComponentPropsById, y as toBioTemplateComponentSnippets } from "./templates-BorLR_7p.js";
|
|
2
|
+
import { g as useSettingsStore, t as useAuthStore } from "./auth-B7g4J4ZF.js";
|
|
3
|
+
import { computed, effectScope, getCurrentScope, inject, onMounted, onScopeDispose, onUnmounted, provide, reactive, readonly, ref, shallowRef, toRaw, toValue, watch } from "vue";
|
|
4
4
|
import axios from "axios";
|
|
5
5
|
//#region src/composables/useSortedItems.ts
|
|
6
6
|
/** Shared sorting for SDK tables and lists with stable empty-value handling. */
|
|
@@ -673,6 +673,223 @@ function useFormBuilder(schema, initialData, enhancements) {
|
|
|
673
673
|
};
|
|
674
674
|
}
|
|
675
675
|
//#endregion
|
|
676
|
+
//#region src/composables/usePlatformContext.ts
|
|
677
|
+
var DEFAULT_CONTEXT = {
|
|
678
|
+
isIntegrated: false,
|
|
679
|
+
theme: "system"
|
|
680
|
+
};
|
|
681
|
+
var platformContext = ref({ ...DEFAULT_CONTEXT });
|
|
682
|
+
var inferredOrigins = /* @__PURE__ */ new Set();
|
|
683
|
+
var allowedOrigins = /* @__PURE__ */ new Set();
|
|
684
|
+
var allowAnyOrigin = false;
|
|
685
|
+
var initialized = false;
|
|
686
|
+
var listenerCount = 0;
|
|
687
|
+
var nextConsumerId = 0;
|
|
688
|
+
var currentHandler = null;
|
|
689
|
+
var consumerOrigins = /* @__PURE__ */ new Map();
|
|
690
|
+
var allowAnyOriginConsumers = /* @__PURE__ */ new Set();
|
|
691
|
+
/**
|
|
692
|
+
* Derive origin from URL (protocol + host)
|
|
693
|
+
*/
|
|
694
|
+
function getOriginFromUrl(url) {
|
|
695
|
+
try {
|
|
696
|
+
return new URL(url).origin;
|
|
697
|
+
} catch {
|
|
698
|
+
return null;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
function normalizeAllowedOrigins(origins) {
|
|
702
|
+
const normalized = /* @__PURE__ */ new Set();
|
|
703
|
+
if (!origins) return normalized;
|
|
704
|
+
for (const origin of origins) normalized.add(getOriginFromUrl(origin) || origin);
|
|
705
|
+
return normalized;
|
|
706
|
+
}
|
|
707
|
+
function recomputeOriginPolicy() {
|
|
708
|
+
allowedOrigins = new Set(inferredOrigins);
|
|
709
|
+
for (const origins of consumerOrigins.values()) for (const origin of origins) allowedOrigins.add(origin);
|
|
710
|
+
allowAnyOrigin = allowAnyOriginConsumers.size > 0;
|
|
711
|
+
}
|
|
712
|
+
function resetPlatformContextState() {
|
|
713
|
+
inferredOrigins = /* @__PURE__ */ new Set();
|
|
714
|
+
allowedOrigins = /* @__PURE__ */ new Set();
|
|
715
|
+
allowAnyOrigin = false;
|
|
716
|
+
initialized = false;
|
|
717
|
+
listenerCount = 0;
|
|
718
|
+
currentHandler = null;
|
|
719
|
+
consumerOrigins.clear();
|
|
720
|
+
allowAnyOriginConsumers.clear();
|
|
721
|
+
platformContext.value = { ...DEFAULT_CONTEXT };
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Check if an origin is allowed for postMessage communication
|
|
725
|
+
*/
|
|
726
|
+
function isOriginAllowed(origin) {
|
|
727
|
+
if (allowAnyOrigin) {
|
|
728
|
+
console.warn("[MINT SDK] postMessage origin validation disabled - only use in development");
|
|
729
|
+
return true;
|
|
730
|
+
}
|
|
731
|
+
if (origin === window.location.origin) return true;
|
|
732
|
+
return allowedOrigins.has(origin);
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Platform context composable for plugin integration with MINT Platform.
|
|
736
|
+
*
|
|
737
|
+
* Provides secure communication with the parent platform via postMessage.
|
|
738
|
+
*
|
|
739
|
+
* @param options - Configuration options
|
|
740
|
+
* @param options.allowedOrigins - List of allowed origins for postMessage
|
|
741
|
+
* @param options.allowAnyOrigin - Allow any origin (UNSAFE, development only)
|
|
742
|
+
*
|
|
743
|
+
* @example
|
|
744
|
+
* ```typescript
|
|
745
|
+
* // Basic usage - derives origin from platform injection
|
|
746
|
+
* const { isIntegrated, user, theme } = usePlatformContext()
|
|
747
|
+
*
|
|
748
|
+
* // With explicit allowed origins
|
|
749
|
+
* const { isIntegrated } = usePlatformContext({
|
|
750
|
+
* allowedOrigins: ['https://mint.example.com']
|
|
751
|
+
* })
|
|
752
|
+
*
|
|
753
|
+
* // Development mode (UNSAFE)
|
|
754
|
+
* const { isIntegrated } = usePlatformContext({
|
|
755
|
+
* allowAnyOrigin: import.meta.env.DEV
|
|
756
|
+
* })
|
|
757
|
+
* ```
|
|
758
|
+
*/
|
|
759
|
+
/** Connects a plugin to the MINT platform via postMessage, exposing user, theme, and experiment context. */
|
|
760
|
+
function usePlatformContext(options = {}) {
|
|
761
|
+
const consumerId = ++nextConsumerId;
|
|
762
|
+
const instanceOrigins = normalizeAllowedOrigins(options.allowedOrigins);
|
|
763
|
+
const instanceAllowAnyOrigin = options.allowAnyOrigin === true;
|
|
764
|
+
function detectPlatform() {
|
|
765
|
+
const detectedOrigins = /* @__PURE__ */ new Set();
|
|
766
|
+
const platformData = window.__MINT_PLATFORM__;
|
|
767
|
+
if (platformData) {
|
|
768
|
+
platformContext.value = {
|
|
769
|
+
...platformData,
|
|
770
|
+
isIntegrated: true
|
|
771
|
+
};
|
|
772
|
+
if (platformData.platformOrigin) detectedOrigins.add(platformData.platformOrigin);
|
|
773
|
+
else if (platformData.platformApiUrl) {
|
|
774
|
+
const origin = getOriginFromUrl(platformData.platformApiUrl);
|
|
775
|
+
if (origin) detectedOrigins.add(origin);
|
|
776
|
+
}
|
|
777
|
+
} else {
|
|
778
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
779
|
+
const hasPluginParam = urlParams.has("mint-plugin");
|
|
780
|
+
const platformOrigin = urlParams.get("mint-origin");
|
|
781
|
+
if (platformOrigin) {
|
|
782
|
+
const origin = getOriginFromUrl(platformOrigin);
|
|
783
|
+
if (origin) detectedOrigins.add(origin);
|
|
784
|
+
}
|
|
785
|
+
platformContext.value = {
|
|
786
|
+
isIntegrated: hasPluginParam,
|
|
787
|
+
theme: (() => {
|
|
788
|
+
try {
|
|
789
|
+
const s = localStorage.getItem("mint-settings");
|
|
790
|
+
if (s) return JSON.parse(s).theme || "system";
|
|
791
|
+
} catch {}
|
|
792
|
+
return "system";
|
|
793
|
+
})(),
|
|
794
|
+
platformOrigin: platformOrigin || void 0
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
inferredOrigins = detectedOrigins;
|
|
798
|
+
}
|
|
799
|
+
function handlePlatformMessage(event) {
|
|
800
|
+
if (event.source !== window.parent) return;
|
|
801
|
+
if (!isOriginAllowed(event.origin)) {
|
|
802
|
+
console.warn(`[MINT SDK] Rejected postMessage from untrusted origin: ${event.origin}`);
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
try {
|
|
806
|
+
const platformEvent = event.data;
|
|
807
|
+
if (!platformEvent.type?.startsWith("mint:")) return;
|
|
808
|
+
switch (platformEvent.type) {
|
|
809
|
+
case "mint:theme-changed":
|
|
810
|
+
platformContext.value.theme = platformEvent.payload;
|
|
811
|
+
break;
|
|
812
|
+
case "mint:user-changed":
|
|
813
|
+
platformContext.value.user = platformEvent.payload;
|
|
814
|
+
break;
|
|
815
|
+
}
|
|
816
|
+
} catch {}
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Send a message to the parent platform.
|
|
820
|
+
* Uses validated target origin for security.
|
|
821
|
+
*/
|
|
822
|
+
function sendToPlatform(event) {
|
|
823
|
+
if (!platformContext.value.isIntegrated || window.parent === window) return;
|
|
824
|
+
let targetOrigin;
|
|
825
|
+
if (platformContext.value.platformOrigin) targetOrigin = platformContext.value.platformOrigin;
|
|
826
|
+
else if (allowedOrigins.size > 0) targetOrigin = allowedOrigins.values().next().value;
|
|
827
|
+
else if (allowAnyOrigin) {
|
|
828
|
+
targetOrigin = "*";
|
|
829
|
+
console.warn("[MINT SDK] Using wildcard origin for postMessage - only use in development");
|
|
830
|
+
} else {
|
|
831
|
+
console.warn("[MINT SDK] Cannot send postMessage: no platform origin configured");
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
window.parent.postMessage(event, targetOrigin);
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Request navigation to a path in the platform.
|
|
838
|
+
*/
|
|
839
|
+
function navigate(path) {
|
|
840
|
+
sendToPlatform({
|
|
841
|
+
type: "mint:navigate",
|
|
842
|
+
payload: path
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Show a notification in the platform.
|
|
847
|
+
*/
|
|
848
|
+
function notify(message, type = "info") {
|
|
849
|
+
sendToPlatform({
|
|
850
|
+
type: "mint:notification",
|
|
851
|
+
payload: {
|
|
852
|
+
message,
|
|
853
|
+
type
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
onMounted(() => {
|
|
858
|
+
consumerOrigins.set(consumerId, instanceOrigins);
|
|
859
|
+
if (instanceAllowAnyOrigin) allowAnyOriginConsumers.add(consumerId);
|
|
860
|
+
if (!initialized) {
|
|
861
|
+
detectPlatform();
|
|
862
|
+
currentHandler = handlePlatformMessage;
|
|
863
|
+
window.addEventListener("message", handlePlatformMessage);
|
|
864
|
+
initialized = true;
|
|
865
|
+
}
|
|
866
|
+
listenerCount++;
|
|
867
|
+
recomputeOriginPolicy();
|
|
868
|
+
});
|
|
869
|
+
onUnmounted(() => {
|
|
870
|
+
consumerOrigins.delete(consumerId);
|
|
871
|
+
allowAnyOriginConsumers.delete(consumerId);
|
|
872
|
+
listenerCount = Math.max(0, listenerCount - 1);
|
|
873
|
+
if (listenerCount === 0) {
|
|
874
|
+
if (currentHandler) window.removeEventListener("message", currentHandler);
|
|
875
|
+
resetPlatformContextState();
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
recomputeOriginPolicy();
|
|
879
|
+
});
|
|
880
|
+
return {
|
|
881
|
+
context: platformContext,
|
|
882
|
+
isIntegrated: computed(() => platformContext.value.isIntegrated),
|
|
883
|
+
plugin: computed(() => platformContext.value.plugin),
|
|
884
|
+
user: computed(() => platformContext.value.user),
|
|
885
|
+
theme: computed(() => platformContext.value.theme),
|
|
886
|
+
features: computed(() => platformContext.value.features),
|
|
887
|
+
navigate,
|
|
888
|
+
notify,
|
|
889
|
+
sendToPlatform
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
//#endregion
|
|
676
893
|
//#region src/composables/experiment-utils.ts
|
|
677
894
|
function formatExperimentDate(dateStr) {
|
|
678
895
|
try {
|
|
@@ -1191,270 +1408,53 @@ function useExperimentSelector(options = {}) {
|
|
|
1191
1408
|
const groupedByProject = computed(() => {
|
|
1192
1409
|
const groups = /* @__PURE__ */ new Map();
|
|
1193
1410
|
for (const exp of experiments.value) {
|
|
1194
|
-
const key = exp.project_name ?? exp.project ?? "No project";
|
|
1195
|
-
const list = groups.get(key);
|
|
1196
|
-
if (list) list.push(exp);
|
|
1197
|
-
else groups.set(key, [exp]);
|
|
1198
|
-
}
|
|
1199
|
-
return [...groups.entries()].sort(([a], [b]) => {
|
|
1200
|
-
if (a === "No project") return 1;
|
|
1201
|
-
if (b === "No project") return -1;
|
|
1202
|
-
return a.localeCompare(b);
|
|
1203
|
-
});
|
|
1204
|
-
});
|
|
1205
|
-
const searchWatch = useDebouncedWatch(() => filters.search, () => {
|
|
1206
|
-
page.value = 0;
|
|
1207
|
-
fetchExperiments();
|
|
1208
|
-
}, { delay: 300 });
|
|
1209
|
-
watch(() => [
|
|
1210
|
-
filters.status,
|
|
1211
|
-
filters.project,
|
|
1212
|
-
filters.experimentType,
|
|
1213
|
-
filters.datePreset,
|
|
1214
|
-
sortKey.value
|
|
1215
|
-
], () => {
|
|
1216
|
-
searchWatch.cancel();
|
|
1217
|
-
page.value = 0;
|
|
1218
|
-
fetchExperiments();
|
|
1219
|
-
});
|
|
1220
|
-
if (immediate) fetchExperiments();
|
|
1221
|
-
return {
|
|
1222
|
-
experiments,
|
|
1223
|
-
total,
|
|
1224
|
-
selectedExperiment,
|
|
1225
|
-
filters,
|
|
1226
|
-
isLoading,
|
|
1227
|
-
error,
|
|
1228
|
-
lastLoadedAt,
|
|
1229
|
-
page,
|
|
1230
|
-
hasMore,
|
|
1231
|
-
sortKey,
|
|
1232
|
-
experimentTypes,
|
|
1233
|
-
projects,
|
|
1234
|
-
groupedByProject,
|
|
1235
|
-
fetch: fetchExperiments,
|
|
1236
|
-
loadMore,
|
|
1237
|
-
reset,
|
|
1238
|
-
select,
|
|
1239
|
-
clear,
|
|
1240
|
-
fetchFilterOptions
|
|
1241
|
-
};
|
|
1242
|
-
}
|
|
1243
|
-
//#endregion
|
|
1244
|
-
//#region src/composables/usePlatformContext.ts
|
|
1245
|
-
var DEFAULT_CONTEXT = {
|
|
1246
|
-
isIntegrated: false,
|
|
1247
|
-
theme: "system"
|
|
1248
|
-
};
|
|
1249
|
-
var platformContext = ref({ ...DEFAULT_CONTEXT });
|
|
1250
|
-
var inferredOrigins = /* @__PURE__ */ new Set();
|
|
1251
|
-
var allowedOrigins = /* @__PURE__ */ new Set();
|
|
1252
|
-
var allowAnyOrigin = false;
|
|
1253
|
-
var initialized = false;
|
|
1254
|
-
var listenerCount = 0;
|
|
1255
|
-
var nextConsumerId = 0;
|
|
1256
|
-
var currentHandler = null;
|
|
1257
|
-
var consumerOrigins = /* @__PURE__ */ new Map();
|
|
1258
|
-
var allowAnyOriginConsumers = /* @__PURE__ */ new Set();
|
|
1259
|
-
/**
|
|
1260
|
-
* Derive origin from URL (protocol + host)
|
|
1261
|
-
*/
|
|
1262
|
-
function getOriginFromUrl(url) {
|
|
1263
|
-
try {
|
|
1264
|
-
return new URL(url).origin;
|
|
1265
|
-
} catch {
|
|
1266
|
-
return null;
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
function normalizeAllowedOrigins(origins) {
|
|
1270
|
-
const normalized = /* @__PURE__ */ new Set();
|
|
1271
|
-
if (!origins) return normalized;
|
|
1272
|
-
for (const origin of origins) normalized.add(getOriginFromUrl(origin) || origin);
|
|
1273
|
-
return normalized;
|
|
1274
|
-
}
|
|
1275
|
-
function recomputeOriginPolicy() {
|
|
1276
|
-
allowedOrigins = new Set(inferredOrigins);
|
|
1277
|
-
for (const origins of consumerOrigins.values()) for (const origin of origins) allowedOrigins.add(origin);
|
|
1278
|
-
allowAnyOrigin = allowAnyOriginConsumers.size > 0;
|
|
1279
|
-
}
|
|
1280
|
-
function resetPlatformContextState() {
|
|
1281
|
-
inferredOrigins = /* @__PURE__ */ new Set();
|
|
1282
|
-
allowedOrigins = /* @__PURE__ */ new Set();
|
|
1283
|
-
allowAnyOrigin = false;
|
|
1284
|
-
initialized = false;
|
|
1285
|
-
listenerCount = 0;
|
|
1286
|
-
currentHandler = null;
|
|
1287
|
-
consumerOrigins.clear();
|
|
1288
|
-
allowAnyOriginConsumers.clear();
|
|
1289
|
-
platformContext.value = { ...DEFAULT_CONTEXT };
|
|
1290
|
-
}
|
|
1291
|
-
/**
|
|
1292
|
-
* Check if an origin is allowed for postMessage communication
|
|
1293
|
-
*/
|
|
1294
|
-
function isOriginAllowed(origin) {
|
|
1295
|
-
if (allowAnyOrigin) {
|
|
1296
|
-
console.warn("[MINT SDK] postMessage origin validation disabled - only use in development");
|
|
1297
|
-
return true;
|
|
1298
|
-
}
|
|
1299
|
-
if (origin === window.location.origin) return true;
|
|
1300
|
-
return allowedOrigins.has(origin);
|
|
1301
|
-
}
|
|
1302
|
-
/**
|
|
1303
|
-
* Platform context composable for plugin integration with MINT Platform.
|
|
1304
|
-
*
|
|
1305
|
-
* Provides secure communication with the parent platform via postMessage.
|
|
1306
|
-
*
|
|
1307
|
-
* @param options - Configuration options
|
|
1308
|
-
* @param options.allowedOrigins - List of allowed origins for postMessage
|
|
1309
|
-
* @param options.allowAnyOrigin - Allow any origin (UNSAFE, development only)
|
|
1310
|
-
*
|
|
1311
|
-
* @example
|
|
1312
|
-
* ```typescript
|
|
1313
|
-
* // Basic usage - derives origin from platform injection
|
|
1314
|
-
* const { isIntegrated, user, theme } = usePlatformContext()
|
|
1315
|
-
*
|
|
1316
|
-
* // With explicit allowed origins
|
|
1317
|
-
* const { isIntegrated } = usePlatformContext({
|
|
1318
|
-
* allowedOrigins: ['https://mint.example.com']
|
|
1319
|
-
* })
|
|
1320
|
-
*
|
|
1321
|
-
* // Development mode (UNSAFE)
|
|
1322
|
-
* const { isIntegrated } = usePlatformContext({
|
|
1323
|
-
* allowAnyOrigin: import.meta.env.DEV
|
|
1324
|
-
* })
|
|
1325
|
-
* ```
|
|
1326
|
-
*/
|
|
1327
|
-
/** Connects a plugin to the MINT platform via postMessage, exposing user, theme, and experiment context. */
|
|
1328
|
-
function usePlatformContext(options = {}) {
|
|
1329
|
-
const consumerId = ++nextConsumerId;
|
|
1330
|
-
const instanceOrigins = normalizeAllowedOrigins(options.allowedOrigins);
|
|
1331
|
-
const instanceAllowAnyOrigin = options.allowAnyOrigin === true;
|
|
1332
|
-
function detectPlatform() {
|
|
1333
|
-
const detectedOrigins = /* @__PURE__ */ new Set();
|
|
1334
|
-
const platformData = window.__MINT_PLATFORM__;
|
|
1335
|
-
if (platformData) {
|
|
1336
|
-
platformContext.value = {
|
|
1337
|
-
...platformData,
|
|
1338
|
-
isIntegrated: true
|
|
1339
|
-
};
|
|
1340
|
-
if (platformData.platformOrigin) detectedOrigins.add(platformData.platformOrigin);
|
|
1341
|
-
else if (platformData.platformApiUrl) {
|
|
1342
|
-
const origin = getOriginFromUrl(platformData.platformApiUrl);
|
|
1343
|
-
if (origin) detectedOrigins.add(origin);
|
|
1344
|
-
}
|
|
1345
|
-
} else {
|
|
1346
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
1347
|
-
const hasPluginParam = urlParams.has("mint-plugin");
|
|
1348
|
-
const platformOrigin = urlParams.get("mint-origin");
|
|
1349
|
-
if (platformOrigin) {
|
|
1350
|
-
const origin = getOriginFromUrl(platformOrigin);
|
|
1351
|
-
if (origin) detectedOrigins.add(origin);
|
|
1352
|
-
}
|
|
1353
|
-
platformContext.value = {
|
|
1354
|
-
isIntegrated: hasPluginParam,
|
|
1355
|
-
theme: (() => {
|
|
1356
|
-
try {
|
|
1357
|
-
const s = localStorage.getItem("mint-settings");
|
|
1358
|
-
if (s) return JSON.parse(s).theme || "system";
|
|
1359
|
-
} catch {}
|
|
1360
|
-
return "system";
|
|
1361
|
-
})(),
|
|
1362
|
-
platformOrigin: platformOrigin || void 0
|
|
1363
|
-
};
|
|
1364
|
-
}
|
|
1365
|
-
inferredOrigins = detectedOrigins;
|
|
1366
|
-
}
|
|
1367
|
-
function handlePlatformMessage(event) {
|
|
1368
|
-
if (event.source !== window.parent) return;
|
|
1369
|
-
if (!isOriginAllowed(event.origin)) {
|
|
1370
|
-
console.warn(`[MINT SDK] Rejected postMessage from untrusted origin: ${event.origin}`);
|
|
1371
|
-
return;
|
|
1372
|
-
}
|
|
1373
|
-
try {
|
|
1374
|
-
const platformEvent = event.data;
|
|
1375
|
-
if (!platformEvent.type?.startsWith("mint:")) return;
|
|
1376
|
-
switch (platformEvent.type) {
|
|
1377
|
-
case "mint:theme-changed":
|
|
1378
|
-
platformContext.value.theme = platformEvent.payload;
|
|
1379
|
-
break;
|
|
1380
|
-
case "mint:user-changed":
|
|
1381
|
-
platformContext.value.user = platformEvent.payload;
|
|
1382
|
-
break;
|
|
1383
|
-
}
|
|
1384
|
-
} catch {}
|
|
1385
|
-
}
|
|
1386
|
-
/**
|
|
1387
|
-
* Send a message to the parent platform.
|
|
1388
|
-
* Uses validated target origin for security.
|
|
1389
|
-
*/
|
|
1390
|
-
function sendToPlatform(event) {
|
|
1391
|
-
if (!platformContext.value.isIntegrated || window.parent === window) return;
|
|
1392
|
-
let targetOrigin;
|
|
1393
|
-
if (platformContext.value.platformOrigin) targetOrigin = platformContext.value.platformOrigin;
|
|
1394
|
-
else if (allowedOrigins.size > 0) targetOrigin = allowedOrigins.values().next().value;
|
|
1395
|
-
else if (allowAnyOrigin) {
|
|
1396
|
-
targetOrigin = "*";
|
|
1397
|
-
console.warn("[MINT SDK] Using wildcard origin for postMessage - only use in development");
|
|
1398
|
-
} else {
|
|
1399
|
-
console.warn("[MINT SDK] Cannot send postMessage: no platform origin configured");
|
|
1400
|
-
return;
|
|
1401
|
-
}
|
|
1402
|
-
window.parent.postMessage(event, targetOrigin);
|
|
1403
|
-
}
|
|
1404
|
-
/**
|
|
1405
|
-
* Request navigation to a path in the platform.
|
|
1406
|
-
*/
|
|
1407
|
-
function navigate(path) {
|
|
1408
|
-
sendToPlatform({
|
|
1409
|
-
type: "mint:navigate",
|
|
1410
|
-
payload: path
|
|
1411
|
-
});
|
|
1412
|
-
}
|
|
1413
|
-
/**
|
|
1414
|
-
* Show a notification in the platform.
|
|
1415
|
-
*/
|
|
1416
|
-
function notify(message, type = "info") {
|
|
1417
|
-
sendToPlatform({
|
|
1418
|
-
type: "mint:notification",
|
|
1419
|
-
payload: {
|
|
1420
|
-
message,
|
|
1421
|
-
type
|
|
1422
|
-
}
|
|
1423
|
-
});
|
|
1424
|
-
}
|
|
1425
|
-
onMounted(() => {
|
|
1426
|
-
consumerOrigins.set(consumerId, instanceOrigins);
|
|
1427
|
-
if (instanceAllowAnyOrigin) allowAnyOriginConsumers.add(consumerId);
|
|
1428
|
-
if (!initialized) {
|
|
1429
|
-
detectPlatform();
|
|
1430
|
-
currentHandler = handlePlatformMessage;
|
|
1431
|
-
window.addEventListener("message", handlePlatformMessage);
|
|
1432
|
-
initialized = true;
|
|
1433
|
-
}
|
|
1434
|
-
listenerCount++;
|
|
1435
|
-
recomputeOriginPolicy();
|
|
1436
|
-
});
|
|
1437
|
-
onUnmounted(() => {
|
|
1438
|
-
consumerOrigins.delete(consumerId);
|
|
1439
|
-
allowAnyOriginConsumers.delete(consumerId);
|
|
1440
|
-
listenerCount = Math.max(0, listenerCount - 1);
|
|
1441
|
-
if (listenerCount === 0) {
|
|
1442
|
-
if (currentHandler) window.removeEventListener("message", currentHandler);
|
|
1443
|
-
resetPlatformContextState();
|
|
1444
|
-
return;
|
|
1411
|
+
const key = exp.project_name ?? exp.project ?? "No project";
|
|
1412
|
+
const list = groups.get(key);
|
|
1413
|
+
if (list) list.push(exp);
|
|
1414
|
+
else groups.set(key, [exp]);
|
|
1445
1415
|
}
|
|
1446
|
-
|
|
1416
|
+
return [...groups.entries()].sort(([a], [b]) => {
|
|
1417
|
+
if (a === "No project") return 1;
|
|
1418
|
+
if (b === "No project") return -1;
|
|
1419
|
+
return a.localeCompare(b);
|
|
1420
|
+
});
|
|
1421
|
+
});
|
|
1422
|
+
const searchWatch = useDebouncedWatch(() => filters.search, () => {
|
|
1423
|
+
page.value = 0;
|
|
1424
|
+
fetchExperiments();
|
|
1425
|
+
}, { delay: 300 });
|
|
1426
|
+
watch(() => [
|
|
1427
|
+
filters.status,
|
|
1428
|
+
filters.project,
|
|
1429
|
+
filters.experimentType,
|
|
1430
|
+
filters.datePreset,
|
|
1431
|
+
sortKey.value
|
|
1432
|
+
], () => {
|
|
1433
|
+
searchWatch.cancel();
|
|
1434
|
+
page.value = 0;
|
|
1435
|
+
fetchExperiments();
|
|
1447
1436
|
});
|
|
1437
|
+
if (immediate) fetchExperiments();
|
|
1448
1438
|
return {
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1439
|
+
experiments,
|
|
1440
|
+
total,
|
|
1441
|
+
selectedExperiment,
|
|
1442
|
+
filters,
|
|
1443
|
+
isLoading,
|
|
1444
|
+
error,
|
|
1445
|
+
lastLoadedAt,
|
|
1446
|
+
page,
|
|
1447
|
+
hasMore,
|
|
1448
|
+
sortKey,
|
|
1449
|
+
experimentTypes,
|
|
1450
|
+
projects,
|
|
1451
|
+
groupedByProject,
|
|
1452
|
+
fetch: fetchExperiments,
|
|
1453
|
+
loadMore,
|
|
1454
|
+
reset,
|
|
1455
|
+
select,
|
|
1456
|
+
clear,
|
|
1457
|
+
fetchFilterOptions
|
|
1458
1458
|
};
|
|
1459
1459
|
}
|
|
1460
1460
|
//#endregion
|
|
@@ -2098,6 +2098,108 @@ function useWellPlateEditor(initialState, options = {}) {
|
|
|
2098
2098
|
};
|
|
2099
2099
|
}
|
|
2100
2100
|
//#endregion
|
|
2101
|
+
//#region src/composables/experimentDesignData.ts
|
|
2102
|
+
function isRecord(value) {
|
|
2103
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2104
|
+
}
|
|
2105
|
+
function isPlatformDesignDataResponse(value) {
|
|
2106
|
+
return "data" in value && ("experiment_id" in value || "plugin_id" in value || "schema_version" in value || "updated_at" in value);
|
|
2107
|
+
}
|
|
2108
|
+
/** Return the plugin-defined design_data payload from common platform and plugin response shapes. */
|
|
2109
|
+
function unwrapExperimentDesignData(rawData) {
|
|
2110
|
+
if (!rawData) return null;
|
|
2111
|
+
if (isRecord(rawData.data) && isPlatformDesignDataResponse(rawData)) return rawData.data;
|
|
2112
|
+
if (isRecord(rawData.design_data)) return rawData.design_data;
|
|
2113
|
+
if (isRecord(rawData.designData)) return rawData.designData;
|
|
2114
|
+
return rawData;
|
|
2115
|
+
}
|
|
2116
|
+
function shouldIncludeSample(record, options) {
|
|
2117
|
+
if (options.includeControls) return true;
|
|
2118
|
+
const rawType = record.sample_type ?? record.sampleType ?? record.type ?? record.well_type ?? record.wellType;
|
|
2119
|
+
const type = typeof rawType === "string" ? rawType.toLowerCase() : "";
|
|
2120
|
+
return !["blank", "qc"].includes(type);
|
|
2121
|
+
}
|
|
2122
|
+
function readSampleValue(record) {
|
|
2123
|
+
const candidates = [
|
|
2124
|
+
record.sample_name,
|
|
2125
|
+
record.sampleName,
|
|
2126
|
+
record.sample_id,
|
|
2127
|
+
record.sampleId,
|
|
2128
|
+
record.name,
|
|
2129
|
+
record.id,
|
|
2130
|
+
record.label
|
|
2131
|
+
];
|
|
2132
|
+
for (const candidate of candidates) {
|
|
2133
|
+
if (typeof candidate === "string" && candidate.trim()) return candidate;
|
|
2134
|
+
if (typeof candidate === "number" && Number.isFinite(candidate)) return String(candidate);
|
|
2135
|
+
}
|
|
2136
|
+
return null;
|
|
2137
|
+
}
|
|
2138
|
+
function readSampleLabel(record, fallback) {
|
|
2139
|
+
const label = record.name ?? record.label ?? record.sample_name ?? record.sampleName;
|
|
2140
|
+
return typeof label === "string" && label.trim() ? label : fallback;
|
|
2141
|
+
}
|
|
2142
|
+
function readSampleDescription(record) {
|
|
2143
|
+
const parts = [
|
|
2144
|
+
record.group,
|
|
2145
|
+
record.batch,
|
|
2146
|
+
record.batch_id,
|
|
2147
|
+
record.batchId,
|
|
2148
|
+
record.plate_id,
|
|
2149
|
+
record.plateId,
|
|
2150
|
+
record.well_id,
|
|
2151
|
+
record.wellId
|
|
2152
|
+
].filter((value) => typeof value === "string" && value.trim().length > 0 || typeof value === "number");
|
|
2153
|
+
return parts.length > 0 ? parts.map(String).join(" / ") : void 0;
|
|
2154
|
+
}
|
|
2155
|
+
function addSampleRecord(options, seen, value, extractOptions) {
|
|
2156
|
+
if (typeof value === "string") {
|
|
2157
|
+
const trimmed = value.trim();
|
|
2158
|
+
if (trimmed && !seen.has(trimmed)) {
|
|
2159
|
+
seen.add(trimmed);
|
|
2160
|
+
options.push({
|
|
2161
|
+
value: trimmed,
|
|
2162
|
+
label: trimmed
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
return;
|
|
2166
|
+
}
|
|
2167
|
+
if (!isRecord(value) || !shouldIncludeSample(value, extractOptions)) return;
|
|
2168
|
+
const sampleValue = readSampleValue(value);
|
|
2169
|
+
if (!sampleValue || seen.has(sampleValue)) return;
|
|
2170
|
+
seen.add(sampleValue);
|
|
2171
|
+
options.push({
|
|
2172
|
+
value: sampleValue,
|
|
2173
|
+
label: readSampleLabel(value, sampleValue),
|
|
2174
|
+
description: readSampleDescription(value)
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
2177
|
+
function addSampleArray(options, seen, value, extractOptions) {
|
|
2178
|
+
if (!Array.isArray(value)) return;
|
|
2179
|
+
for (const sample of value) addSampleRecord(options, seen, sample, extractOptions);
|
|
2180
|
+
}
|
|
2181
|
+
/** Extract selectable sample options from raw or wrapped experiment design_data. */
|
|
2182
|
+
function extractSampleOptionsFromDesignData(rawData, options = {}) {
|
|
2183
|
+
const designData = unwrapExperimentDesignData(rawData);
|
|
2184
|
+
if (!designData) return [];
|
|
2185
|
+
const sampleOptions = [];
|
|
2186
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2187
|
+
addSampleArray(sampleOptions, seen, designData.samples, options);
|
|
2188
|
+
if (isRecord(designData.templates)) for (const template of Object.values(designData.templates)) {
|
|
2189
|
+
if (!isRecord(template)) continue;
|
|
2190
|
+
addSampleArray(sampleOptions, seen, (isRecord(template.data) ? template.data : template).samples, options);
|
|
2191
|
+
}
|
|
2192
|
+
if (Array.isArray(designData.plates)) for (const plate of designData.plates) {
|
|
2193
|
+
if (!isRecord(plate)) continue;
|
|
2194
|
+
addSampleArray(sampleOptions, seen, plate.samples, options);
|
|
2195
|
+
}
|
|
2196
|
+
return sampleOptions;
|
|
2197
|
+
}
|
|
2198
|
+
/** Extract SampleSelector-compatible string values from raw or wrapped experiment design_data. */
|
|
2199
|
+
function extractSampleNamesFromDesignData(rawData, options = {}) {
|
|
2200
|
+
return extractSampleOptionsFromDesignData(rawData, options).map((sample) => sample.value);
|
|
2201
|
+
}
|
|
2202
|
+
//#endregion
|
|
2101
2203
|
//#region src/composables/useAutoGroup.ts
|
|
2102
2204
|
var DEFAULT_COLORS = [
|
|
2103
2205
|
"#3B82F6",
|
|
@@ -2323,7 +2425,9 @@ function computeGroups(allSamples, columns, enabledFields, outlierActions, delim
|
|
|
2323
2425
|
* Returns null if no samples with conditions are found.
|
|
2324
2426
|
*/
|
|
2325
2427
|
function extractSamplesFromDesignData(rawData) {
|
|
2326
|
-
const
|
|
2428
|
+
const designData = unwrapExperimentDesignData(rawData);
|
|
2429
|
+
if (!designData) return null;
|
|
2430
|
+
const samples = designData.samples;
|
|
2327
2431
|
if (!Array.isArray(samples) || samples.length === 0) return null;
|
|
2328
2432
|
const allConditionKeys = [];
|
|
2329
2433
|
const keySet = /* @__PURE__ */ new Set();
|
|
@@ -2772,6 +2876,169 @@ function normalizeIds(ids) {
|
|
|
2772
2876
|
return [...new Set(ids.filter(Boolean))];
|
|
2773
2877
|
}
|
|
2774
2878
|
//#endregion
|
|
2879
|
+
//#region src/composables/platformContextHelpers.ts
|
|
2880
|
+
function getInjectedPlatformContext() {
|
|
2881
|
+
if (typeof window === "undefined") return void 0;
|
|
2882
|
+
return window.__MINT_PLATFORM__;
|
|
2883
|
+
}
|
|
2884
|
+
function getInjectedExperimentContext() {
|
|
2885
|
+
return getInjectedPlatformContext();
|
|
2886
|
+
}
|
|
2887
|
+
function currentExperimentFromContext(context = getInjectedExperimentContext()) {
|
|
2888
|
+
const candidate = context?.currentExperiment ?? context?.experiment;
|
|
2889
|
+
return candidate && typeof candidate === "object" ? candidate : void 0;
|
|
2890
|
+
}
|
|
2891
|
+
function currentExperimentIdFromContext(context = getInjectedExperimentContext()) {
|
|
2892
|
+
return parseExperimentId(context?.currentExperimentId ?? context?.experimentId ?? context?.currentExperiment ?? context?.experiment);
|
|
2893
|
+
}
|
|
2894
|
+
function currentExperimentIdFromUrl() {
|
|
2895
|
+
if (typeof window === "undefined") return void 0;
|
|
2896
|
+
const params = new URLSearchParams(window.location.search);
|
|
2897
|
+
return parseExperimentId(params.get("experimentId") ?? params.get("experiment_id")) ?? experimentIdFromPath(window.location.pathname) ?? experimentIdFromPath(window.location.hash.replace(/^#\/?/, "/"));
|
|
2898
|
+
}
|
|
2899
|
+
function resolveCurrentExperimentId(context = getInjectedExperimentContext()) {
|
|
2900
|
+
return currentExperimentIdFromContext(context) ?? currentExperimentIdFromUrl();
|
|
2901
|
+
}
|
|
2902
|
+
function parseExperimentId(value) {
|
|
2903
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
2904
|
+
if (typeof value === "string" && value.trim()) {
|
|
2905
|
+
const parsed = Number(value);
|
|
2906
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
2907
|
+
}
|
|
2908
|
+
if (value && typeof value === "object" && "id" in value) return parseExperimentId(value.id);
|
|
2909
|
+
}
|
|
2910
|
+
function experimentIdFromPath(path) {
|
|
2911
|
+
const segments = path.split("/").filter(Boolean);
|
|
2912
|
+
for (let index = 0; index < segments.length - 1; index += 1) {
|
|
2913
|
+
const segment = segments[index]?.toLowerCase();
|
|
2914
|
+
if (segment === "experiment" || segment === "experiments") return parseExperimentId(decodeURIComponent(segments[index + 1] ?? ""));
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
//#endregion
|
|
2918
|
+
//#region src/composables/useExperimentData.ts
|
|
2919
|
+
/** Fetches and normalises experiment output data (tree, table, summary) from the platform API. */
|
|
2920
|
+
function useExperimentData(options = {}) {
|
|
2921
|
+
const data = shallowRef(null);
|
|
2922
|
+
const request = useRequestSyncState("Failed to fetch experiment data");
|
|
2923
|
+
const isLoading = request.loading;
|
|
2924
|
+
const error = request.error;
|
|
2925
|
+
const lastLoadedAt = request.lastLoadedAt;
|
|
2926
|
+
let lastExperimentId = null;
|
|
2927
|
+
let api = null;
|
|
2928
|
+
function getApi() {
|
|
2929
|
+
api ??= useApi({ baseUrl: options.apiBaseUrl });
|
|
2930
|
+
return api;
|
|
2931
|
+
}
|
|
2932
|
+
const designData = computed(() => unwrapExperimentDesignData(data.value));
|
|
2933
|
+
const sampleNames = computed(() => extractSampleNamesFromDesignData(designData.value));
|
|
2934
|
+
const sampleOptions = computed(() => extractSampleOptionsFromDesignData(designData.value));
|
|
2935
|
+
const treeData = computed(() => {
|
|
2936
|
+
if (!designData.value) return [];
|
|
2937
|
+
const tree = designData.value.tree_data ?? designData.value.treeData;
|
|
2938
|
+
return Array.isArray(tree) ? tree : [];
|
|
2939
|
+
});
|
|
2940
|
+
const tableData = computed(() => {
|
|
2941
|
+
if (!designData.value) return [];
|
|
2942
|
+
const table = designData.value.table_data ?? designData.value.tableData;
|
|
2943
|
+
return Array.isArray(table) ? table : [];
|
|
2944
|
+
});
|
|
2945
|
+
const summaryData = computed(() => {
|
|
2946
|
+
if (!designData.value) return null;
|
|
2947
|
+
const summary = designData.value.summary_data ?? designData.value.summaryData;
|
|
2948
|
+
if (summary && typeof summary === "object" && "metadata" in summary) return summary;
|
|
2949
|
+
return null;
|
|
2950
|
+
});
|
|
2951
|
+
async function fetchData(experimentId) {
|
|
2952
|
+
lastExperimentId = experimentId;
|
|
2953
|
+
try {
|
|
2954
|
+
data.value = await request.run(() => getApi().get(`/experiments/${experimentId}/data`), {
|
|
2955
|
+
success: "load",
|
|
2956
|
+
errorMessage: "Failed to fetch experiment data"
|
|
2957
|
+
});
|
|
2958
|
+
return data.value;
|
|
2959
|
+
} catch {
|
|
2960
|
+
data.value = null;
|
|
2961
|
+
return null;
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
async function refresh() {
|
|
2965
|
+
if (lastExperimentId !== null) await fetchData(lastExperimentId);
|
|
2966
|
+
}
|
|
2967
|
+
function clear() {
|
|
2968
|
+
data.value = null;
|
|
2969
|
+
lastExperimentId = null;
|
|
2970
|
+
request.clearError();
|
|
2971
|
+
}
|
|
2972
|
+
return {
|
|
2973
|
+
data,
|
|
2974
|
+
designData,
|
|
2975
|
+
sampleNames,
|
|
2976
|
+
sampleOptions,
|
|
2977
|
+
treeData,
|
|
2978
|
+
tableData,
|
|
2979
|
+
summaryData,
|
|
2980
|
+
isLoading,
|
|
2981
|
+
error,
|
|
2982
|
+
lastLoadedAt,
|
|
2983
|
+
fetch: fetchData,
|
|
2984
|
+
refresh,
|
|
2985
|
+
clear
|
|
2986
|
+
};
|
|
2987
|
+
}
|
|
2988
|
+
//#endregion
|
|
2989
|
+
//#region src/composables/useExperimentSamples.ts
|
|
2990
|
+
/** Loads experiment design_data and derives samples for SampleSelector-style UIs. */
|
|
2991
|
+
function useExperimentSamples(options = {}) {
|
|
2992
|
+
const appExperiment = options.syncAppExperiment === false ? void 0 : inject(APP_EXPERIMENT_KEY, void 0);
|
|
2993
|
+
const experimentData = useExperimentData({ apiBaseUrl: options.apiBaseUrl });
|
|
2994
|
+
const experimentId = computed(() => {
|
|
2995
|
+
return parseExperimentId(toValue(options.experimentId)) ?? parseExperimentId(appExperiment?.experimentId.value);
|
|
2996
|
+
});
|
|
2997
|
+
const explicitDesignData = computed(() => unwrapExperimentDesignData(toValue(options.designData) ?? null));
|
|
2998
|
+
const designData = computed(() => explicitDesignData.value ?? experimentData.designData.value);
|
|
2999
|
+
const extractOptions = computed(() => ({ includeControls: options.includeControls }));
|
|
3000
|
+
const samples = computed(() => extractSampleNamesFromDesignData(designData.value, extractOptions.value));
|
|
3001
|
+
const sampleOptions = computed(() => extractSampleOptionsFromDesignData(designData.value, extractOptions.value));
|
|
3002
|
+
const enabled = computed(() => toValue(options.enabled) ?? true);
|
|
3003
|
+
async function fetchSamples(id = experimentId.value) {
|
|
3004
|
+
const parsedId = parseExperimentId(id);
|
|
3005
|
+
if (parsedId === void 0) {
|
|
3006
|
+
experimentData.clear();
|
|
3007
|
+
return null;
|
|
3008
|
+
}
|
|
3009
|
+
return experimentData.fetch(parsedId);
|
|
3010
|
+
}
|
|
3011
|
+
watch(() => ({
|
|
3012
|
+
designData: explicitDesignData.value,
|
|
3013
|
+
enabled: enabled.value,
|
|
3014
|
+
experimentId: experimentId.value
|
|
3015
|
+
}), ({ designData: currentDesignData, enabled: isEnabled, experimentId: currentExperimentId }) => {
|
|
3016
|
+
if (options.immediate === false || !isEnabled) return;
|
|
3017
|
+
if (currentDesignData) {
|
|
3018
|
+
experimentData.clear();
|
|
3019
|
+
return;
|
|
3020
|
+
}
|
|
3021
|
+
if (currentExperimentId === void 0) {
|
|
3022
|
+
experimentData.clear();
|
|
3023
|
+
return;
|
|
3024
|
+
}
|
|
3025
|
+
experimentData.fetch(currentExperimentId);
|
|
3026
|
+
}, { immediate: options.immediate !== false });
|
|
3027
|
+
return {
|
|
3028
|
+
experimentId,
|
|
3029
|
+
data: experimentData.data,
|
|
3030
|
+
designData,
|
|
3031
|
+
samples,
|
|
3032
|
+
sampleOptions,
|
|
3033
|
+
isLoading: experimentData.isLoading,
|
|
3034
|
+
error: experimentData.error,
|
|
3035
|
+
lastLoadedAt: experimentData.lastLoadedAt,
|
|
3036
|
+
fetch: fetchSamples,
|
|
3037
|
+
refresh: experimentData.refresh,
|
|
3038
|
+
clear: experimentData.clear
|
|
3039
|
+
};
|
|
3040
|
+
}
|
|
3041
|
+
//#endregion
|
|
2775
3042
|
//#region src/composables/useScheduleDrag.ts
|
|
2776
3043
|
/** Handles pointer-driven create, move, and resize drag interactions for the ScheduleCalendar grid. */
|
|
2777
3044
|
function useScheduleDrag(options) {
|
|
@@ -2952,45 +3219,6 @@ function clampRange(start, end, min, max) {
|
|
|
2952
3219
|
};
|
|
2953
3220
|
}
|
|
2954
3221
|
//#endregion
|
|
2955
|
-
//#region src/composables/platformContextHelpers.ts
|
|
2956
|
-
function getInjectedPlatformContext() {
|
|
2957
|
-
if (typeof window === "undefined") return void 0;
|
|
2958
|
-
return window.__MINT_PLATFORM__;
|
|
2959
|
-
}
|
|
2960
|
-
function getInjectedExperimentContext() {
|
|
2961
|
-
return getInjectedPlatformContext();
|
|
2962
|
-
}
|
|
2963
|
-
function currentExperimentFromContext(context = getInjectedExperimentContext()) {
|
|
2964
|
-
const candidate = context?.currentExperiment ?? context?.experiment;
|
|
2965
|
-
return candidate && typeof candidate === "object" ? candidate : void 0;
|
|
2966
|
-
}
|
|
2967
|
-
function currentExperimentIdFromContext(context = getInjectedExperimentContext()) {
|
|
2968
|
-
return parseExperimentId(context?.currentExperimentId ?? context?.experimentId ?? context?.currentExperiment ?? context?.experiment);
|
|
2969
|
-
}
|
|
2970
|
-
function currentExperimentIdFromUrl() {
|
|
2971
|
-
if (typeof window === "undefined") return void 0;
|
|
2972
|
-
const params = new URLSearchParams(window.location.search);
|
|
2973
|
-
return parseExperimentId(params.get("experimentId") ?? params.get("experiment_id")) ?? experimentIdFromPath(window.location.pathname) ?? experimentIdFromPath(window.location.hash.replace(/^#\/?/, "/"));
|
|
2974
|
-
}
|
|
2975
|
-
function resolveCurrentExperimentId(context = getInjectedExperimentContext()) {
|
|
2976
|
-
return currentExperimentIdFromContext(context) ?? currentExperimentIdFromUrl();
|
|
2977
|
-
}
|
|
2978
|
-
function parseExperimentId(value) {
|
|
2979
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
2980
|
-
if (typeof value === "string" && value.trim()) {
|
|
2981
|
-
const parsed = Number(value);
|
|
2982
|
-
return Number.isFinite(parsed) ? parsed : void 0;
|
|
2983
|
-
}
|
|
2984
|
-
if (value && typeof value === "object" && "id" in value) return parseExperimentId(value.id);
|
|
2985
|
-
}
|
|
2986
|
-
function experimentIdFromPath(path) {
|
|
2987
|
-
const segments = path.split("/").filter(Boolean);
|
|
2988
|
-
for (let index = 0; index < segments.length - 1; index += 1) {
|
|
2989
|
-
const segment = segments[index]?.toLowerCase();
|
|
2990
|
-
if (segment === "experiment" || segment === "experiments") return parseExperimentId(decodeURIComponent(segments[index + 1] ?? ""));
|
|
2991
|
-
}
|
|
2992
|
-
}
|
|
2993
|
-
//#endregion
|
|
2994
3222
|
//#region src/composables/useExperimentSave.ts
|
|
2995
3223
|
/** Persists experiment design, analysis, and built-in template preset data through the platform API. */
|
|
2996
3224
|
function useExperimentSave(options = {}) {
|
|
@@ -4428,59 +4656,6 @@ function useProtocolTemplates() {
|
|
|
4428
4656
|
};
|
|
4429
4657
|
}
|
|
4430
4658
|
//#endregion
|
|
4431
|
-
|
|
4432
|
-
/** Fetches and normalises experiment output data (tree, table, summary) from the platform API. */
|
|
4433
|
-
function useExperimentData(options = {}) {
|
|
4434
|
-
const api = useApi({ baseUrl: options.apiBaseUrl });
|
|
4435
|
-
const data = ref(null);
|
|
4436
|
-
const request = useRequestSyncState("Failed to fetch experiment data");
|
|
4437
|
-
const isLoading = request.loading;
|
|
4438
|
-
const error = request.error;
|
|
4439
|
-
const lastLoadedAt = request.lastLoadedAt;
|
|
4440
|
-
let lastExperimentId = null;
|
|
4441
|
-
const treeData = computed(() => {
|
|
4442
|
-
if (!data.value) return [];
|
|
4443
|
-
const tree = data.value.tree_data ?? data.value.treeData;
|
|
4444
|
-
return Array.isArray(tree) ? tree : [];
|
|
4445
|
-
});
|
|
4446
|
-
const tableData = computed(() => {
|
|
4447
|
-
if (!data.value) return [];
|
|
4448
|
-
const table = data.value.table_data ?? data.value.tableData;
|
|
4449
|
-
return Array.isArray(table) ? table : [];
|
|
4450
|
-
});
|
|
4451
|
-
const summaryData = computed(() => {
|
|
4452
|
-
if (!data.value) return null;
|
|
4453
|
-
const summary = data.value.summary_data ?? data.value.summaryData;
|
|
4454
|
-
if (summary && typeof summary === "object" && "metadata" in summary) return summary;
|
|
4455
|
-
return null;
|
|
4456
|
-
});
|
|
4457
|
-
async function fetchData(experimentId) {
|
|
4458
|
-
lastExperimentId = experimentId;
|
|
4459
|
-
try {
|
|
4460
|
-
data.value = await request.run(() => api.get(`/experiments/${experimentId}/data`), {
|
|
4461
|
-
success: "load",
|
|
4462
|
-
errorMessage: "Failed to fetch experiment data"
|
|
4463
|
-
});
|
|
4464
|
-
} catch {
|
|
4465
|
-
data.value = null;
|
|
4466
|
-
}
|
|
4467
|
-
}
|
|
4468
|
-
async function refresh() {
|
|
4469
|
-
if (lastExperimentId !== null) await fetchData(lastExperimentId);
|
|
4470
|
-
}
|
|
4471
|
-
return {
|
|
4472
|
-
data,
|
|
4473
|
-
treeData,
|
|
4474
|
-
tableData,
|
|
4475
|
-
summaryData,
|
|
4476
|
-
isLoading,
|
|
4477
|
-
error,
|
|
4478
|
-
lastLoadedAt,
|
|
4479
|
-
fetch: fetchData,
|
|
4480
|
-
refresh
|
|
4481
|
-
};
|
|
4482
|
-
}
|
|
4483
|
-
//#endregion
|
|
4484
|
-
export { useTheme as $, useAutoGroup as A, DATE_PRESET_OPTIONS as B, useSampleGroups as C, DEFAULT_COLORS as D, hslToHex as E, usePlatformContext as F, datePresetToISO as G, EXPERIMENT_STATUS_OPTIONS as H, useExperimentSelector as I, getExperimentStatusVariant as J, formatExperimentDate as K, useRequestSyncState as L, useDoseCalculator as M, APP_EXPERIMENT_KEY as N, extractSamplesFromDesignData as O, useAppExperiment as P, useForm as Q, useDebouncedWatch as R, useExpansionSet as S, hexToHsl as T, EXPERIMENT_STATUS_VARIANT_MAP as U, EXPERIMENT_STATUS_LABELS as V, SORT_OPTIONS as W, evaluateCondition as X, resolveExperimentCode as Y, useFormBuilder as Z, useExperimentSave as _, generateDilutionSeries as a, useSortedItems as at, resolveCurrentExperimentId as b, useRackEditor as c, useBioTemplateWorkspace as d, useToast as et, getBioTemplateComponentProps as f, useTemplateCollection as g, useBioTemplateControls as h, DEFAULT_UNITS as i, compareSortValues as it, useWellPlateEditor as j, parseCSV as k, useBioTemplatePresetWorkspace as l, useBioTemplateComponents as m, useProtocolTemplates as n, normalizeSearchQuery as nt, useReagentSeries as o, toBioTemplateComponentPropsByComponent as p, formatExperimentStatus as q, DEFAULT_PRESETS as r, useTextSearch as rt, useGroupAssignment as s, useExperimentData as t, candidateMatchesSearch as tt, useBioTemplatePackWorkspace as u, currentExperimentFromContext as v, deriveShade as w, useScheduleDrag as x, getInjectedPlatformContext as y, useApi as z };
|
|
4659
|
+
export { usePlatformContext as $, parseCSV as A, useRequestSyncState as B, useExpansionSet as C, hslToHex as D, hexToHsl as E, useWellPlateEditor as F, EXPERIMENT_STATUS_OPTIONS as G, useApi as H, useDoseCalculator as I, datePresetToISO as J, EXPERIMENT_STATUS_VARIANT_MAP as K, APP_EXPERIMENT_KEY as L, extractSampleNamesFromDesignData as M, extractSampleOptionsFromDesignData as N, DEFAULT_COLORS as O, unwrapExperimentDesignData as P, resolveExperimentCode as Q, useAppExperiment as R, resolveCurrentExperimentId as S, deriveShade as T, DATE_PRESET_OPTIONS as U, useDebouncedWatch as V, EXPERIMENT_STATUS_LABELS as W, formatExperimentStatus as X, formatExperimentDate as Y, getExperimentStatusVariant as Z, useScheduleDrag as _, useReagentSeries as a, candidateMatchesSearch as at, currentExperimentFromContext as b, useBioTemplatePresetWorkspace as c, compareSortValues as ct, getBioTemplateComponentProps as d, evaluateCondition as et, toBioTemplateComponentPropsByComponent as f, useExperimentSave as g, useTemplateCollection as h, generateDilutionSeries as i, useToast as it, useAutoGroup as j, extractSamplesFromDesignData as k, useBioTemplatePackWorkspace as l, useSortedItems as lt, useBioTemplateControls as m, DEFAULT_PRESETS as n, useForm as nt, useGroupAssignment as o, normalizeSearchQuery as ot, useBioTemplateComponents as p, SORT_OPTIONS as q, DEFAULT_UNITS as r, useTheme as rt, useRackEditor as s, useTextSearch as st, useProtocolTemplates as t, useFormBuilder as tt, useBioTemplateWorkspace as u, useExperimentSamples as v, useSampleGroups as w, getInjectedPlatformContext as x, useExperimentData as y, useExperimentSelector as z };
|
|
4485
4660
|
|
|
4486
|
-
//# sourceMappingURL=
|
|
4661
|
+
//# sourceMappingURL=useProtocolTemplates-n6AJqSqv.js.map
|