@morscherlab/mint-sdk 1.0.0-beta.7 → 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__/composables/useProtocolTemplates.test.d.ts +1 -0
- package/dist/__tests__/stores/settings.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-QQj2kkze.js → auth-B7g4J4ZF.js} +148 -24
- 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 +8 -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-DihbSJjU.js → components-BhK-dW99.js} +2135 -1075
- 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-BcgZ6diz.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 +3186 -1070
- 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-Cyt0Suwf.js → templates-BorLR_7p.js} +324 -10
- 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-CM6Y0u5L.js → useProtocolTemplates-n6AJqSqv.js} +627 -380
- 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/GroupAssigner.test.ts +18 -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/useApi.test.ts +45 -0
- package/src/__tests__/composables/useAuth.test.ts +20 -0
- 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__/composables/useProtocolTemplates.test.ts +64 -0
- package/src/__tests__/stores/settings.test.ts +78 -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/AppAvatarMenu.vue +6 -3
- package/src/components/AppTopBar.vue +16 -10
- package/src/components/AuditTrail.vue +1 -1
- package/src/components/BaseTabs.vue +22 -1
- package/src/components/Calendar.vue +6 -2
- package/src/components/ConcentrationInput.vue +3 -2
- package/src/components/GroupAssigner.vue +8 -3
- 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/NumberInput.vue +5 -3
- package/src/components/ProgressBar.vue +3 -0
- package/src/components/RackEditor.vue +73 -2
- package/src/components/SampleHierarchyTree.vue +3 -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 +49 -2
- package/src/components/UnitInput.vue +6 -2
- package/src/components/WellPlate.vue +145 -24
- 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/useApi.ts +113 -16
- package/src/composables/useAuth.ts +4 -0
- package/src/composables/useAutoGroup.ts +18 -9
- package/src/composables/useControlSchema.ts +21 -0
- package/src/composables/useExperimentData.ts +57 -16
- package/src/composables/useExperimentSamples.ts +142 -0
- package/src/composables/useProtocolTemplates.ts +13 -1
- package/src/composables/useRackEditor.ts +3 -2
- 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 +79 -26
- package/src/stores/settings.ts +10 -0
- 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/settings-modal.css +9 -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-QQj2kkze.js.map +0 -1
- package/dist/components-DihbSJjU.js.map +0 -1
- package/dist/composables-BcgZ6diz.js.map +0 -1
- package/dist/templates-Cyt0Suwf.js.map +0 -1
- package/dist/useExperimentData-CM6Y0u5L.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 {
|
|
@@ -784,10 +1001,56 @@ var apiClientInstance = null;
|
|
|
784
1001
|
var interceptorAttached = false;
|
|
785
1002
|
function joinUrlPath(baseUrl, path) {
|
|
786
1003
|
if (!path) return baseUrl;
|
|
1004
|
+
if (path.startsWith("?") || path.startsWith("#")) return `${baseUrl.replace(/\/+$/, "")}${path}`;
|
|
787
1005
|
const normalizedBase = baseUrl.replace(/\/+$/, "");
|
|
788
1006
|
const normalizedPath = path.replace(/^\/+/, "/");
|
|
789
1007
|
return `${normalizedBase}${normalizedPath.startsWith("/") ? normalizedPath : `/${normalizedPath}`}`;
|
|
790
1008
|
}
|
|
1009
|
+
function getBasePath(baseUrl) {
|
|
1010
|
+
if (!baseUrl) return "/";
|
|
1011
|
+
try {
|
|
1012
|
+
const origin = typeof window !== "undefined" ? window.location.origin : "http://localhost";
|
|
1013
|
+
return new URL(baseUrl, origin).pathname.replace(/\/+$/, "") || "/";
|
|
1014
|
+
} catch {
|
|
1015
|
+
return baseUrl.replace(/^https?:\/\/[^/]+/i, "").replace(/\/+$/, "") || "/";
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
function normalizeRequestUrl(baseUrl, url) {
|
|
1019
|
+
if (!url || /^https?:\/\//.test(url)) return url;
|
|
1020
|
+
const basePath = getBasePath(baseUrl);
|
|
1021
|
+
if (basePath === "/") return url;
|
|
1022
|
+
const normalizedUrl = url.startsWith("/") ? url : `/${url}`;
|
|
1023
|
+
if (normalizedUrl === basePath || normalizedUrl.startsWith(`${basePath}?`) || normalizedUrl.startsWith(`${basePath}#`)) return normalizedUrl.slice(basePath.length);
|
|
1024
|
+
if (normalizedUrl.startsWith(`${basePath}/`)) return normalizedUrl.slice(basePath.length) || "";
|
|
1025
|
+
return url;
|
|
1026
|
+
}
|
|
1027
|
+
function asMutableHeaders(headers) {
|
|
1028
|
+
return headers ? headers : null;
|
|
1029
|
+
}
|
|
1030
|
+
function hasAuthorizationHeader(headers) {
|
|
1031
|
+
const bag = asMutableHeaders(headers);
|
|
1032
|
+
if (!bag) return false;
|
|
1033
|
+
if (typeof bag.has === "function") return bag.has("Authorization");
|
|
1034
|
+
return Object.keys(bag).some((key) => key.toLowerCase() === "authorization");
|
|
1035
|
+
}
|
|
1036
|
+
function setAuthorizationHeader(headers, value) {
|
|
1037
|
+
const bag = asMutableHeaders(headers);
|
|
1038
|
+
if (!bag) return;
|
|
1039
|
+
if (typeof bag.set === "function") {
|
|
1040
|
+
bag.set("Authorization", value);
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
bag.Authorization = value;
|
|
1044
|
+
}
|
|
1045
|
+
function deleteAuthorizationHeader(headers) {
|
|
1046
|
+
const bag = asMutableHeaders(headers);
|
|
1047
|
+
if (!bag) return;
|
|
1048
|
+
if (typeof bag.delete === "function") {
|
|
1049
|
+
bag.delete("Authorization");
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
for (const key of Object.keys(bag)) if (key.toLowerCase() === "authorization") delete bag[key];
|
|
1053
|
+
}
|
|
791
1054
|
function getApiClient() {
|
|
792
1055
|
if (!apiClientInstance) apiClientInstance = axios.create({ headers: { "Content-Type": "application/json" } });
|
|
793
1056
|
return apiClientInstance;
|
|
@@ -800,37 +1063,50 @@ function useApi(options = {}) {
|
|
|
800
1063
|
if (!authStore.isInitialized) authStore.initialize();
|
|
801
1064
|
if (!interceptorAttached) {
|
|
802
1065
|
apiClient.interceptors.request.use((config) => {
|
|
803
|
-
|
|
1066
|
+
const request = config;
|
|
1067
|
+
if (request._mintSkipAuth) {
|
|
1068
|
+
delete request._mintSkipAuth;
|
|
1069
|
+
deleteAuthorizationHeader(request.headers);
|
|
1070
|
+
return request;
|
|
1071
|
+
}
|
|
1072
|
+
const currentAuthStore = useAuthStore();
|
|
1073
|
+
if (currentAuthStore.token && config.headers && !hasAuthorizationHeader(config.headers)) setAuthorizationHeader(config.headers, `Bearer ${currentAuthStore.token}`);
|
|
804
1074
|
return config;
|
|
805
1075
|
});
|
|
806
1076
|
interceptorAttached = true;
|
|
807
1077
|
}
|
|
1078
|
+
function getBaseUrl() {
|
|
1079
|
+
return options.baseUrl ?? settingsStore.getApiBaseUrl();
|
|
1080
|
+
}
|
|
1081
|
+
function normalizeUrl(url) {
|
|
1082
|
+
return normalizeRequestUrl(getBaseUrl(), url);
|
|
1083
|
+
}
|
|
808
1084
|
function requestConfig(config) {
|
|
809
1085
|
const base = {
|
|
810
|
-
baseURL:
|
|
1086
|
+
baseURL: getBaseUrl(),
|
|
811
1087
|
timeout: options.timeout ?? settingsStore.requestTimeout,
|
|
812
1088
|
...config
|
|
813
1089
|
};
|
|
814
|
-
if (options.withAuth === false)
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
}
|
|
1090
|
+
if (options.withAuth === false) {
|
|
1091
|
+
base._mintSkipAuth = true;
|
|
1092
|
+
deleteAuthorizationHeader(base.headers);
|
|
1093
|
+
}
|
|
818
1094
|
return base;
|
|
819
1095
|
}
|
|
820
1096
|
async function get(url, config) {
|
|
821
|
-
return (await apiClient.get(url, requestConfig(config))).data;
|
|
1097
|
+
return (await apiClient.get(normalizeUrl(url), requestConfig(config))).data;
|
|
822
1098
|
}
|
|
823
1099
|
async function post(url, data, config) {
|
|
824
|
-
return (await apiClient.post(url, data, requestConfig(config))).data;
|
|
1100
|
+
return (await apiClient.post(normalizeUrl(url), data, requestConfig(config))).data;
|
|
825
1101
|
}
|
|
826
1102
|
async function put(url, data, config) {
|
|
827
|
-
return (await apiClient.put(url, data, requestConfig(config))).data;
|
|
1103
|
+
return (await apiClient.put(normalizeUrl(url), data, requestConfig(config))).data;
|
|
828
1104
|
}
|
|
829
1105
|
async function patch(url, data, config) {
|
|
830
|
-
return (await apiClient.patch(url, data, requestConfig(config))).data;
|
|
1106
|
+
return (await apiClient.patch(normalizeUrl(url), data, requestConfig(config))).data;
|
|
831
1107
|
}
|
|
832
1108
|
async function del(url, config) {
|
|
833
|
-
return (await apiClient.delete(url, requestConfig(config))).data;
|
|
1109
|
+
return (await apiClient.delete(normalizeUrl(url), requestConfig(config))).data;
|
|
834
1110
|
}
|
|
835
1111
|
async function upload(url, file, fieldName = "file", additionalData) {
|
|
836
1112
|
const formData = new FormData();
|
|
@@ -838,10 +1114,10 @@ function useApi(options = {}) {
|
|
|
838
1114
|
if (additionalData) Object.entries(additionalData).forEach(([key, value]) => {
|
|
839
1115
|
if (value !== void 0 && value !== null) formData.append(key, typeof value === "object" ? JSON.stringify(value) : String(value));
|
|
840
1116
|
});
|
|
841
|
-
return (await apiClient.post(url, formData, requestConfig({ headers: { "Content-Type": void 0 } }))).data;
|
|
1117
|
+
return (await apiClient.post(normalizeUrl(url), formData, requestConfig({ headers: { "Content-Type": void 0 } }))).data;
|
|
842
1118
|
}
|
|
843
1119
|
async function download(url, filename) {
|
|
844
|
-
const response = await apiClient.get(url, requestConfig({ responseType: "blob" }));
|
|
1120
|
+
const response = await apiClient.get(normalizeUrl(url), requestConfig({ responseType: "blob" }));
|
|
845
1121
|
const blob = new Blob([response.data]);
|
|
846
1122
|
const blobUrl = URL.createObjectURL(blob);
|
|
847
1123
|
if (filename) {
|
|
@@ -857,7 +1133,8 @@ function useApi(options = {}) {
|
|
|
857
1133
|
return blobUrl;
|
|
858
1134
|
}
|
|
859
1135
|
function buildUrl(path) {
|
|
860
|
-
|
|
1136
|
+
const baseUrl = getBaseUrl();
|
|
1137
|
+
return joinUrlPath(baseUrl, normalizeRequestUrl(baseUrl, path));
|
|
861
1138
|
}
|
|
862
1139
|
function buildWsUrl(path) {
|
|
863
1140
|
return joinUrlPath(settingsStore.getWsBaseUrl(), path);
|
|
@@ -1132,269 +1409,52 @@ function useExperimentSelector(options = {}) {
|
|
|
1132
1409
|
const groups = /* @__PURE__ */ new Map();
|
|
1133
1410
|
for (const exp of experiments.value) {
|
|
1134
1411
|
const key = exp.project_name ?? exp.project ?? "No project";
|
|
1135
|
-
const list = groups.get(key);
|
|
1136
|
-
if (list) list.push(exp);
|
|
1137
|
-
else groups.set(key, [exp]);
|
|
1138
|
-
}
|
|
1139
|
-
return [...groups.entries()].sort(([a], [b]) => {
|
|
1140
|
-
if (a === "No project") return 1;
|
|
1141
|
-
if (b === "No project") return -1;
|
|
1142
|
-
return a.localeCompare(b);
|
|
1143
|
-
});
|
|
1144
|
-
});
|
|
1145
|
-
const searchWatch = useDebouncedWatch(() => filters.search, () => {
|
|
1146
|
-
page.value = 0;
|
|
1147
|
-
fetchExperiments();
|
|
1148
|
-
}, { delay: 300 });
|
|
1149
|
-
watch(() => [
|
|
1150
|
-
filters.status,
|
|
1151
|
-
filters.project,
|
|
1152
|
-
filters.experimentType,
|
|
1153
|
-
filters.datePreset,
|
|
1154
|
-
sortKey.value
|
|
1155
|
-
], () => {
|
|
1156
|
-
searchWatch.cancel();
|
|
1157
|
-
page.value = 0;
|
|
1158
|
-
fetchExperiments();
|
|
1159
|
-
});
|
|
1160
|
-
if (immediate) fetchExperiments();
|
|
1161
|
-
return {
|
|
1162
|
-
experiments,
|
|
1163
|
-
total,
|
|
1164
|
-
selectedExperiment,
|
|
1165
|
-
filters,
|
|
1166
|
-
isLoading,
|
|
1167
|
-
error,
|
|
1168
|
-
lastLoadedAt,
|
|
1169
|
-
page,
|
|
1170
|
-
hasMore,
|
|
1171
|
-
sortKey,
|
|
1172
|
-
experimentTypes,
|
|
1173
|
-
projects,
|
|
1174
|
-
groupedByProject,
|
|
1175
|
-
fetch: fetchExperiments,
|
|
1176
|
-
loadMore,
|
|
1177
|
-
reset,
|
|
1178
|
-
select,
|
|
1179
|
-
clear,
|
|
1180
|
-
fetchFilterOptions
|
|
1181
|
-
};
|
|
1182
|
-
}
|
|
1183
|
-
//#endregion
|
|
1184
|
-
//#region src/composables/usePlatformContext.ts
|
|
1185
|
-
var DEFAULT_CONTEXT = {
|
|
1186
|
-
isIntegrated: false,
|
|
1187
|
-
theme: "system"
|
|
1188
|
-
};
|
|
1189
|
-
var platformContext = ref({ ...DEFAULT_CONTEXT });
|
|
1190
|
-
var inferredOrigins = /* @__PURE__ */ new Set();
|
|
1191
|
-
var allowedOrigins = /* @__PURE__ */ new Set();
|
|
1192
|
-
var allowAnyOrigin = false;
|
|
1193
|
-
var initialized = false;
|
|
1194
|
-
var listenerCount = 0;
|
|
1195
|
-
var nextConsumerId = 0;
|
|
1196
|
-
var currentHandler = null;
|
|
1197
|
-
var consumerOrigins = /* @__PURE__ */ new Map();
|
|
1198
|
-
var allowAnyOriginConsumers = /* @__PURE__ */ new Set();
|
|
1199
|
-
/**
|
|
1200
|
-
* Derive origin from URL (protocol + host)
|
|
1201
|
-
*/
|
|
1202
|
-
function getOriginFromUrl(url) {
|
|
1203
|
-
try {
|
|
1204
|
-
return new URL(url).origin;
|
|
1205
|
-
} catch {
|
|
1206
|
-
return null;
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
function normalizeAllowedOrigins(origins) {
|
|
1210
|
-
const normalized = /* @__PURE__ */ new Set();
|
|
1211
|
-
if (!origins) return normalized;
|
|
1212
|
-
for (const origin of origins) normalized.add(getOriginFromUrl(origin) || origin);
|
|
1213
|
-
return normalized;
|
|
1214
|
-
}
|
|
1215
|
-
function recomputeOriginPolicy() {
|
|
1216
|
-
allowedOrigins = new Set(inferredOrigins);
|
|
1217
|
-
for (const origins of consumerOrigins.values()) for (const origin of origins) allowedOrigins.add(origin);
|
|
1218
|
-
allowAnyOrigin = allowAnyOriginConsumers.size > 0;
|
|
1219
|
-
}
|
|
1220
|
-
function resetPlatformContextState() {
|
|
1221
|
-
inferredOrigins = /* @__PURE__ */ new Set();
|
|
1222
|
-
allowedOrigins = /* @__PURE__ */ new Set();
|
|
1223
|
-
allowAnyOrigin = false;
|
|
1224
|
-
initialized = false;
|
|
1225
|
-
listenerCount = 0;
|
|
1226
|
-
currentHandler = null;
|
|
1227
|
-
consumerOrigins.clear();
|
|
1228
|
-
allowAnyOriginConsumers.clear();
|
|
1229
|
-
platformContext.value = { ...DEFAULT_CONTEXT };
|
|
1230
|
-
}
|
|
1231
|
-
/**
|
|
1232
|
-
* Check if an origin is allowed for postMessage communication
|
|
1233
|
-
*/
|
|
1234
|
-
function isOriginAllowed(origin) {
|
|
1235
|
-
if (allowAnyOrigin) {
|
|
1236
|
-
console.warn("[MINT SDK] postMessage origin validation disabled - only use in development");
|
|
1237
|
-
return true;
|
|
1238
|
-
}
|
|
1239
|
-
if (origin === window.location.origin) return true;
|
|
1240
|
-
return allowedOrigins.has(origin);
|
|
1241
|
-
}
|
|
1242
|
-
/**
|
|
1243
|
-
* Platform context composable for plugin integration with MINT Platform.
|
|
1244
|
-
*
|
|
1245
|
-
* Provides secure communication with the parent platform via postMessage.
|
|
1246
|
-
*
|
|
1247
|
-
* @param options - Configuration options
|
|
1248
|
-
* @param options.allowedOrigins - List of allowed origins for postMessage
|
|
1249
|
-
* @param options.allowAnyOrigin - Allow any origin (UNSAFE, development only)
|
|
1250
|
-
*
|
|
1251
|
-
* @example
|
|
1252
|
-
* ```typescript
|
|
1253
|
-
* // Basic usage - derives origin from platform injection
|
|
1254
|
-
* const { isIntegrated, user, theme } = usePlatformContext()
|
|
1255
|
-
*
|
|
1256
|
-
* // With explicit allowed origins
|
|
1257
|
-
* const { isIntegrated } = usePlatformContext({
|
|
1258
|
-
* allowedOrigins: ['https://mint.example.com']
|
|
1259
|
-
* })
|
|
1260
|
-
*
|
|
1261
|
-
* // Development mode (UNSAFE)
|
|
1262
|
-
* const { isIntegrated } = usePlatformContext({
|
|
1263
|
-
* allowAnyOrigin: import.meta.env.DEV
|
|
1264
|
-
* })
|
|
1265
|
-
* ```
|
|
1266
|
-
*/
|
|
1267
|
-
/** Connects a plugin to the MINT platform via postMessage, exposing user, theme, and experiment context. */
|
|
1268
|
-
function usePlatformContext(options = {}) {
|
|
1269
|
-
const consumerId = ++nextConsumerId;
|
|
1270
|
-
const instanceOrigins = normalizeAllowedOrigins(options.allowedOrigins);
|
|
1271
|
-
const instanceAllowAnyOrigin = options.allowAnyOrigin === true;
|
|
1272
|
-
function detectPlatform() {
|
|
1273
|
-
const detectedOrigins = /* @__PURE__ */ new Set();
|
|
1274
|
-
const platformData = window.__MINT_PLATFORM__;
|
|
1275
|
-
if (platformData) {
|
|
1276
|
-
platformContext.value = {
|
|
1277
|
-
...platformData,
|
|
1278
|
-
isIntegrated: true
|
|
1279
|
-
};
|
|
1280
|
-
if (platformData.platformOrigin) detectedOrigins.add(platformData.platformOrigin);
|
|
1281
|
-
else if (platformData.platformApiUrl) {
|
|
1282
|
-
const origin = getOriginFromUrl(platformData.platformApiUrl);
|
|
1283
|
-
if (origin) detectedOrigins.add(origin);
|
|
1284
|
-
}
|
|
1285
|
-
} else {
|
|
1286
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
1287
|
-
const hasPluginParam = urlParams.has("mint-plugin");
|
|
1288
|
-
const platformOrigin = urlParams.get("mint-origin");
|
|
1289
|
-
if (platformOrigin) {
|
|
1290
|
-
const origin = getOriginFromUrl(platformOrigin);
|
|
1291
|
-
if (origin) detectedOrigins.add(origin);
|
|
1292
|
-
}
|
|
1293
|
-
platformContext.value = {
|
|
1294
|
-
isIntegrated: hasPluginParam,
|
|
1295
|
-
theme: (() => {
|
|
1296
|
-
try {
|
|
1297
|
-
const s = localStorage.getItem("mint-settings");
|
|
1298
|
-
if (s) return JSON.parse(s).theme || "system";
|
|
1299
|
-
} catch {}
|
|
1300
|
-
return "system";
|
|
1301
|
-
})(),
|
|
1302
|
-
platformOrigin: platformOrigin || void 0
|
|
1303
|
-
};
|
|
1304
|
-
}
|
|
1305
|
-
inferredOrigins = detectedOrigins;
|
|
1306
|
-
}
|
|
1307
|
-
function handlePlatformMessage(event) {
|
|
1308
|
-
if (event.source !== window.parent) return;
|
|
1309
|
-
if (!isOriginAllowed(event.origin)) {
|
|
1310
|
-
console.warn(`[MINT SDK] Rejected postMessage from untrusted origin: ${event.origin}`);
|
|
1311
|
-
return;
|
|
1312
|
-
}
|
|
1313
|
-
try {
|
|
1314
|
-
const platformEvent = event.data;
|
|
1315
|
-
if (!platformEvent.type?.startsWith("mint:")) return;
|
|
1316
|
-
switch (platformEvent.type) {
|
|
1317
|
-
case "mint:theme-changed":
|
|
1318
|
-
platformContext.value.theme = platformEvent.payload;
|
|
1319
|
-
break;
|
|
1320
|
-
case "mint:user-changed":
|
|
1321
|
-
platformContext.value.user = platformEvent.payload;
|
|
1322
|
-
break;
|
|
1323
|
-
}
|
|
1324
|
-
} catch {}
|
|
1325
|
-
}
|
|
1326
|
-
/**
|
|
1327
|
-
* Send a message to the parent platform.
|
|
1328
|
-
* Uses validated target origin for security.
|
|
1329
|
-
*/
|
|
1330
|
-
function sendToPlatform(event) {
|
|
1331
|
-
if (!platformContext.value.isIntegrated || window.parent === window) return;
|
|
1332
|
-
let targetOrigin;
|
|
1333
|
-
if (platformContext.value.platformOrigin) targetOrigin = platformContext.value.platformOrigin;
|
|
1334
|
-
else if (allowedOrigins.size > 0) targetOrigin = allowedOrigins.values().next().value;
|
|
1335
|
-
else if (allowAnyOrigin) {
|
|
1336
|
-
targetOrigin = "*";
|
|
1337
|
-
console.warn("[MINT SDK] Using wildcard origin for postMessage - only use in development");
|
|
1338
|
-
} else {
|
|
1339
|
-
console.warn("[MINT SDK] Cannot send postMessage: no platform origin configured");
|
|
1340
|
-
return;
|
|
1341
|
-
}
|
|
1342
|
-
window.parent.postMessage(event, targetOrigin);
|
|
1343
|
-
}
|
|
1344
|
-
/**
|
|
1345
|
-
* Request navigation to a path in the platform.
|
|
1346
|
-
*/
|
|
1347
|
-
function navigate(path) {
|
|
1348
|
-
sendToPlatform({
|
|
1349
|
-
type: "mint:navigate",
|
|
1350
|
-
payload: path
|
|
1351
|
-
});
|
|
1352
|
-
}
|
|
1353
|
-
/**
|
|
1354
|
-
* Show a notification in the platform.
|
|
1355
|
-
*/
|
|
1356
|
-
function notify(message, type = "info") {
|
|
1357
|
-
sendToPlatform({
|
|
1358
|
-
type: "mint:notification",
|
|
1359
|
-
payload: {
|
|
1360
|
-
message,
|
|
1361
|
-
type
|
|
1362
|
-
}
|
|
1363
|
-
});
|
|
1364
|
-
}
|
|
1365
|
-
onMounted(() => {
|
|
1366
|
-
consumerOrigins.set(consumerId, instanceOrigins);
|
|
1367
|
-
if (instanceAllowAnyOrigin) allowAnyOriginConsumers.add(consumerId);
|
|
1368
|
-
if (!initialized) {
|
|
1369
|
-
detectPlatform();
|
|
1370
|
-
currentHandler = handlePlatformMessage;
|
|
1371
|
-
window.addEventListener("message", handlePlatformMessage);
|
|
1372
|
-
initialized = true;
|
|
1373
|
-
}
|
|
1374
|
-
listenerCount++;
|
|
1375
|
-
recomputeOriginPolicy();
|
|
1376
|
-
});
|
|
1377
|
-
onUnmounted(() => {
|
|
1378
|
-
consumerOrigins.delete(consumerId);
|
|
1379
|
-
allowAnyOriginConsumers.delete(consumerId);
|
|
1380
|
-
listenerCount = Math.max(0, listenerCount - 1);
|
|
1381
|
-
if (listenerCount === 0) {
|
|
1382
|
-
if (currentHandler) window.removeEventListener("message", currentHandler);
|
|
1383
|
-
resetPlatformContextState();
|
|
1384
|
-
return;
|
|
1412
|
+
const list = groups.get(key);
|
|
1413
|
+
if (list) list.push(exp);
|
|
1414
|
+
else groups.set(key, [exp]);
|
|
1385
1415
|
}
|
|
1386
|
-
|
|
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();
|
|
1387
1436
|
});
|
|
1437
|
+
if (immediate) fetchExperiments();
|
|
1388
1438
|
return {
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
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
|
|
1398
1458
|
};
|
|
1399
1459
|
}
|
|
1400
1460
|
//#endregion
|
|
@@ -2038,6 +2098,108 @@ function useWellPlateEditor(initialState, options = {}) {
|
|
|
2038
2098
|
};
|
|
2039
2099
|
}
|
|
2040
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
|
|
2041
2203
|
//#region src/composables/useAutoGroup.ts
|
|
2042
2204
|
var DEFAULT_COLORS = [
|
|
2043
2205
|
"#3B82F6",
|
|
@@ -2213,8 +2375,9 @@ function computeGroups(allSamples, columns, enabledFields, outlierActions, delim
|
|
|
2213
2375
|
const keyParts = [];
|
|
2214
2376
|
for (const idx of enabledIndices) if (idx < row.length && idx < columns.length) keyParts.push(row[idx]);
|
|
2215
2377
|
const groupKey = keyParts.join(" / ");
|
|
2216
|
-
|
|
2217
|
-
|
|
2378
|
+
const group = groupMap.get(groupKey);
|
|
2379
|
+
if (group) group.push(sample);
|
|
2380
|
+
else groupMap.set(groupKey, [sample]);
|
|
2218
2381
|
const fields = {};
|
|
2219
2382
|
for (const col of columns) if (col.index < row.length) fields[col.name] = row[col.index];
|
|
2220
2383
|
metadata.push({
|
|
@@ -2262,7 +2425,9 @@ function computeGroups(allSamples, columns, enabledFields, outlierActions, delim
|
|
|
2262
2425
|
* Returns null if no samples with conditions are found.
|
|
2263
2426
|
*/
|
|
2264
2427
|
function extractSamplesFromDesignData(rawData) {
|
|
2265
|
-
const
|
|
2428
|
+
const designData = unwrapExperimentDesignData(rawData);
|
|
2429
|
+
if (!designData) return null;
|
|
2430
|
+
const samples = designData.samples;
|
|
2266
2431
|
if (!Array.isArray(samples) || samples.length === 0) return null;
|
|
2267
2432
|
const allConditionKeys = [];
|
|
2268
2433
|
const keySet = /* @__PURE__ */ new Set();
|
|
@@ -2299,8 +2464,9 @@ function computeGroupsFromCsv(csvData, columns, enabledFields) {
|
|
|
2299
2464
|
for (const row of csvData.rows) {
|
|
2300
2465
|
const sampleName = row[csvData.sampleColumn];
|
|
2301
2466
|
const groupKey = enabledCols.map((col) => row[col.originalName ?? col.name]).join(" / ");
|
|
2302
|
-
|
|
2303
|
-
|
|
2467
|
+
const group = groupMap.get(groupKey);
|
|
2468
|
+
if (group) group.push(sampleName);
|
|
2469
|
+
else groupMap.set(groupKey, [sampleName]);
|
|
2304
2470
|
const fields = {};
|
|
2305
2471
|
for (const col of columns) fields[col.name] = row[col.originalName ?? col.name];
|
|
2306
2472
|
metadata.push({
|
|
@@ -2339,7 +2505,8 @@ function useAutoGroup() {
|
|
|
2339
2505
|
const enabledFields = ref(/* @__PURE__ */ new Set());
|
|
2340
2506
|
const isTabularMode = computed(() => (inputMode.value === "csv" || inputMode.value === "experiment") && csvData.value !== null);
|
|
2341
2507
|
const samples = computed(() => {
|
|
2342
|
-
|
|
2508
|
+
const data = csvData.value;
|
|
2509
|
+
if (isTabularMode.value && data) return data.rows.map((r) => r[data.sampleColumn]);
|
|
2343
2510
|
return rawText.value.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
2344
2511
|
});
|
|
2345
2512
|
const hasOutliers = computed(() => outliers.value.length > 0);
|
|
@@ -2709,6 +2876,169 @@ function normalizeIds(ids) {
|
|
|
2709
2876
|
return [...new Set(ids.filter(Boolean))];
|
|
2710
2877
|
}
|
|
2711
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
|
|
2712
3042
|
//#region src/composables/useScheduleDrag.ts
|
|
2713
3043
|
/** Handles pointer-driven create, move, and resize drag interactions for the ScheduleCalendar grid. */
|
|
2714
3044
|
function useScheduleDrag(options) {
|
|
@@ -2889,45 +3219,6 @@ function clampRange(start, end, min, max) {
|
|
|
2889
3219
|
};
|
|
2890
3220
|
}
|
|
2891
3221
|
//#endregion
|
|
2892
|
-
//#region src/composables/platformContextHelpers.ts
|
|
2893
|
-
function getInjectedPlatformContext() {
|
|
2894
|
-
if (typeof window === "undefined") return void 0;
|
|
2895
|
-
return window.__MINT_PLATFORM__;
|
|
2896
|
-
}
|
|
2897
|
-
function getInjectedExperimentContext() {
|
|
2898
|
-
return getInjectedPlatformContext();
|
|
2899
|
-
}
|
|
2900
|
-
function currentExperimentFromContext(context = getInjectedExperimentContext()) {
|
|
2901
|
-
const candidate = context?.currentExperiment ?? context?.experiment;
|
|
2902
|
-
return candidate && typeof candidate === "object" ? candidate : void 0;
|
|
2903
|
-
}
|
|
2904
|
-
function currentExperimentIdFromContext(context = getInjectedExperimentContext()) {
|
|
2905
|
-
return parseExperimentId(context?.currentExperimentId ?? context?.experimentId ?? context?.currentExperiment ?? context?.experiment);
|
|
2906
|
-
}
|
|
2907
|
-
function currentExperimentIdFromUrl() {
|
|
2908
|
-
if (typeof window === "undefined") return void 0;
|
|
2909
|
-
const params = new URLSearchParams(window.location.search);
|
|
2910
|
-
return parseExperimentId(params.get("experimentId") ?? params.get("experiment_id")) ?? experimentIdFromPath(window.location.pathname) ?? experimentIdFromPath(window.location.hash.replace(/^#\/?/, "/"));
|
|
2911
|
-
}
|
|
2912
|
-
function resolveCurrentExperimentId(context = getInjectedExperimentContext()) {
|
|
2913
|
-
return currentExperimentIdFromContext(context) ?? currentExperimentIdFromUrl();
|
|
2914
|
-
}
|
|
2915
|
-
function parseExperimentId(value) {
|
|
2916
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
2917
|
-
if (typeof value === "string" && value.trim()) {
|
|
2918
|
-
const parsed = Number(value);
|
|
2919
|
-
return Number.isFinite(parsed) ? parsed : void 0;
|
|
2920
|
-
}
|
|
2921
|
-
if (value && typeof value === "object" && "id" in value) return parseExperimentId(value.id);
|
|
2922
|
-
}
|
|
2923
|
-
function experimentIdFromPath(path) {
|
|
2924
|
-
const segments = path.split("/").filter(Boolean);
|
|
2925
|
-
for (let index = 0; index < segments.length - 1; index += 1) {
|
|
2926
|
-
const segment = segments[index]?.toLowerCase();
|
|
2927
|
-
if (segment === "experiment" || segment === "experiments") return parseExperimentId(decodeURIComponent(segments[index + 1] ?? ""));
|
|
2928
|
-
}
|
|
2929
|
-
}
|
|
2930
|
-
//#endregion
|
|
2931
3222
|
//#region src/composables/useExperimentSave.ts
|
|
2932
3223
|
/** Persists experiment design, analysis, and built-in template preset data through the platform API. */
|
|
2933
3224
|
function useExperimentSave(options = {}) {
|
|
@@ -3737,8 +4028,9 @@ function useRackEditor(initialRacks, options) {
|
|
|
3737
4028
|
return count;
|
|
3738
4029
|
});
|
|
3739
4030
|
function reset() {
|
|
3740
|
-
|
|
3741
|
-
|
|
4031
|
+
const rack = createDefaultRack("Rack 1", 0);
|
|
4032
|
+
racks.value = [rack];
|
|
4033
|
+
activeRackId.value = rack.id;
|
|
3742
4034
|
}
|
|
3743
4035
|
return {
|
|
3744
4036
|
racks,
|
|
@@ -4246,10 +4538,18 @@ var BUILT_IN_TEMPLATES = [
|
|
|
4246
4538
|
}
|
|
4247
4539
|
];
|
|
4248
4540
|
var STORAGE_KEY = "mint-custom-protocol-templates";
|
|
4541
|
+
function isStepTemplate(value) {
|
|
4542
|
+
if (!value || typeof value !== "object") return false;
|
|
4543
|
+
const candidate = value;
|
|
4544
|
+
return typeof candidate.id === "string" && typeof candidate.type === "string" && typeof candidate.name === "string" && Array.isArray(candidate.parameters);
|
|
4545
|
+
}
|
|
4249
4546
|
function loadCustomTemplates() {
|
|
4250
4547
|
try {
|
|
4251
4548
|
const stored = localStorage.getItem(STORAGE_KEY);
|
|
4252
|
-
if (stored)
|
|
4549
|
+
if (stored) {
|
|
4550
|
+
const parsed = JSON.parse(stored);
|
|
4551
|
+
return Array.isArray(parsed) ? parsed.filter(isStepTemplate) : [];
|
|
4552
|
+
}
|
|
4253
4553
|
} catch {}
|
|
4254
4554
|
return [];
|
|
4255
4555
|
}
|
|
@@ -4356,59 +4656,6 @@ function useProtocolTemplates() {
|
|
|
4356
4656
|
};
|
|
4357
4657
|
}
|
|
4358
4658
|
//#endregion
|
|
4359
|
-
|
|
4360
|
-
/** Fetches and normalises experiment output data (tree, table, summary) from the platform API. */
|
|
4361
|
-
function useExperimentData(options = {}) {
|
|
4362
|
-
const api = useApi({ baseUrl: options.apiBaseUrl });
|
|
4363
|
-
const data = ref(null);
|
|
4364
|
-
const request = useRequestSyncState("Failed to fetch experiment data");
|
|
4365
|
-
const isLoading = request.loading;
|
|
4366
|
-
const error = request.error;
|
|
4367
|
-
const lastLoadedAt = request.lastLoadedAt;
|
|
4368
|
-
let lastExperimentId = null;
|
|
4369
|
-
const treeData = computed(() => {
|
|
4370
|
-
if (!data.value) return [];
|
|
4371
|
-
const tree = data.value.tree_data ?? data.value.treeData;
|
|
4372
|
-
return Array.isArray(tree) ? tree : [];
|
|
4373
|
-
});
|
|
4374
|
-
const tableData = computed(() => {
|
|
4375
|
-
if (!data.value) return [];
|
|
4376
|
-
const table = data.value.table_data ?? data.value.tableData;
|
|
4377
|
-
return Array.isArray(table) ? table : [];
|
|
4378
|
-
});
|
|
4379
|
-
const summaryData = computed(() => {
|
|
4380
|
-
if (!data.value) return null;
|
|
4381
|
-
const summary = data.value.summary_data ?? data.value.summaryData;
|
|
4382
|
-
if (summary && typeof summary === "object" && "metadata" in summary) return summary;
|
|
4383
|
-
return null;
|
|
4384
|
-
});
|
|
4385
|
-
async function fetchData(experimentId) {
|
|
4386
|
-
lastExperimentId = experimentId;
|
|
4387
|
-
try {
|
|
4388
|
-
data.value = await request.run(() => api.get(`/experiments/${experimentId}/data`), {
|
|
4389
|
-
success: "load",
|
|
4390
|
-
errorMessage: "Failed to fetch experiment data"
|
|
4391
|
-
});
|
|
4392
|
-
} catch {
|
|
4393
|
-
data.value = null;
|
|
4394
|
-
}
|
|
4395
|
-
}
|
|
4396
|
-
async function refresh() {
|
|
4397
|
-
if (lastExperimentId !== null) await fetchData(lastExperimentId);
|
|
4398
|
-
}
|
|
4399
|
-
return {
|
|
4400
|
-
data,
|
|
4401
|
-
treeData,
|
|
4402
|
-
tableData,
|
|
4403
|
-
summaryData,
|
|
4404
|
-
isLoading,
|
|
4405
|
-
error,
|
|
4406
|
-
lastLoadedAt,
|
|
4407
|
-
fetch: fetchData,
|
|
4408
|
-
refresh
|
|
4409
|
-
};
|
|
4410
|
-
}
|
|
4411
|
-
//#endregion
|
|
4412
|
-
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 };
|
|
4413
4660
|
|
|
4414
|
-
//# sourceMappingURL=
|
|
4661
|
+
//# sourceMappingURL=useProtocolTemplates-n6AJqSqv.js.map
|