@morscherlab/mint-sdk 1.0.0-beta.6 → 1.0.0-rc.1

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.
Files changed (48) hide show
  1. package/dist/__tests__/composables/useProtocolTemplates.test.d.ts +1 -0
  2. package/dist/__tests__/stores/settings.test.d.ts +1 -0
  3. package/dist/{auth-QQj2kkze.js → auth-CBG3bWEc.js} +50 -20
  4. package/dist/auth-CBG3bWEc.js.map +1 -0
  5. package/dist/components/SettingsModal.vue.d.ts +5 -0
  6. package/dist/components/index.js +2 -2
  7. package/dist/{components-DihbSJjU.js → components-5KSfsVqf.js} +49 -29
  8. package/dist/components-5KSfsVqf.js.map +1 -0
  9. package/dist/composables/index.js +3 -3
  10. package/dist/{composables-BcgZ6diz.js → composables-D4Myb30a.js} +3 -3
  11. package/dist/{composables-BcgZ6diz.js.map → composables-D4Myb30a.js.map} +1 -1
  12. package/dist/index.js +5 -5
  13. package/dist/install.js +2 -2
  14. package/dist/stores/index.js +1 -1
  15. package/dist/styles.css +16 -0
  16. package/dist/templates/index.js +1 -1
  17. package/dist/{templates-Cyt0Suwf.js → templates-BSlxwV2c.js} +12 -8
  18. package/dist/templates-BSlxwV2c.js.map +1 -0
  19. package/dist/{useExperimentData-CM6Y0u5L.js → useExperimentData-BbbdI5xT.js} +97 -25
  20. package/dist/useExperimentData-BbbdI5xT.js.map +1 -0
  21. package/package.json +1 -1
  22. package/src/__tests__/components/GroupAssigner.test.ts +18 -0
  23. package/src/__tests__/composables/useApi.test.ts +45 -0
  24. package/src/__tests__/composables/useAuth.test.ts +20 -0
  25. package/src/__tests__/composables/useProtocolTemplates.test.ts +64 -0
  26. package/src/__tests__/stores/settings.test.ts +78 -0
  27. package/src/components/AppAvatarMenu.vue +6 -3
  28. package/src/components/AppTopBar.vue +15 -10
  29. package/src/components/AuditTrail.vue +1 -1
  30. package/src/components/Calendar.vue +6 -2
  31. package/src/components/ConcentrationInput.vue +3 -2
  32. package/src/components/GroupAssigner.vue +8 -3
  33. package/src/components/NumberInput.vue +5 -3
  34. package/src/components/SampleHierarchyTree.vue +3 -2
  35. package/src/components/SettingsModal.vue +7 -0
  36. package/src/components/UnitInput.vue +6 -2
  37. package/src/components/WellPlate.vue +3 -3
  38. package/src/composables/useApi.ts +113 -16
  39. package/src/composables/useAutoGroup.ts +13 -8
  40. package/src/composables/useProtocolTemplates.ts +13 -1
  41. package/src/composables/useRackEditor.ts +3 -2
  42. package/src/stores/auth.ts +48 -23
  43. package/src/stores/settings.ts +10 -0
  44. package/src/styles/components/settings-modal.css +9 -0
  45. package/dist/auth-QQj2kkze.js.map +0 -1
  46. package/dist/components-DihbSJjU.js.map +0 -1
  47. package/dist/templates-Cyt0Suwf.js.map +0 -1
  48. package/dist/useExperimentData-CM6Y0u5L.js.map +0 -1
@@ -1,5 +1,5 @@
1
- import { Bn as useConcentrationUnits, Bt as extractTemplateCollection, Fn as useControlWorkspace, In as getFieldRegistryEntry, Ln as getTypeDefault, Nt as createTemplateCollection, _ as toBioTemplateComponentPropsByComponent$1, _t as createBioTemplatePresetCollectionFromControls, an as getBioTemplatePackInfo, 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, p as toBioTemplateComponentBindings, tn as getBioTemplatePresetInfo, u as getBioTemplateComponentBindings, v as toBioTemplateComponentPropsById, y as toBioTemplateComponentSnippets, zt as ensureTemplateFromCollection } from "./templates-Cyt0Suwf.js";
2
- import { r as useSettingsStore, t as useAuthStore } from "./auth-QQj2kkze.js";
1
+ import { Bn as useConcentrationUnits, Bt as extractTemplateCollection, Fn as useControlWorkspace, In as getFieldRegistryEntry, Ln as getTypeDefault, Nt as createTemplateCollection, _ as toBioTemplateComponentPropsByComponent$1, _t as createBioTemplatePresetCollectionFromControls, an as getBioTemplatePackInfo, 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, p as toBioTemplateComponentBindings, tn as getBioTemplatePresetInfo, u as getBioTemplateComponentBindings, v as toBioTemplateComponentPropsById, y as toBioTemplateComponentSnippets, zt as ensureTemplateFromCollection } from "./templates-BSlxwV2c.js";
2
+ import { r as useSettingsStore, t as useAuthStore } from "./auth-CBG3bWEc.js";
3
3
  import { computed, effectScope, getCurrentScope, 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
@@ -784,10 +784,56 @@ var apiClientInstance = null;
784
784
  var interceptorAttached = false;
785
785
  function joinUrlPath(baseUrl, path) {
786
786
  if (!path) return baseUrl;
787
+ if (path.startsWith("?") || path.startsWith("#")) return `${baseUrl.replace(/\/+$/, "")}${path}`;
787
788
  const normalizedBase = baseUrl.replace(/\/+$/, "");
788
789
  const normalizedPath = path.replace(/^\/+/, "/");
789
790
  return `${normalizedBase}${normalizedPath.startsWith("/") ? normalizedPath : `/${normalizedPath}`}`;
790
791
  }
792
+ function getBasePath(baseUrl) {
793
+ if (!baseUrl) return "/";
794
+ try {
795
+ const origin = typeof window !== "undefined" ? window.location.origin : "http://localhost";
796
+ return new URL(baseUrl, origin).pathname.replace(/\/+$/, "") || "/";
797
+ } catch {
798
+ return baseUrl.replace(/^https?:\/\/[^/]+/i, "").replace(/\/+$/, "") || "/";
799
+ }
800
+ }
801
+ function normalizeRequestUrl(baseUrl, url) {
802
+ if (!url || /^https?:\/\//.test(url)) return url;
803
+ const basePath = getBasePath(baseUrl);
804
+ if (basePath === "/") return url;
805
+ const normalizedUrl = url.startsWith("/") ? url : `/${url}`;
806
+ if (normalizedUrl === basePath || normalizedUrl.startsWith(`${basePath}?`) || normalizedUrl.startsWith(`${basePath}#`)) return normalizedUrl.slice(basePath.length);
807
+ if (normalizedUrl.startsWith(`${basePath}/`)) return normalizedUrl.slice(basePath.length) || "";
808
+ return url;
809
+ }
810
+ function asMutableHeaders(headers) {
811
+ return headers ? headers : null;
812
+ }
813
+ function hasAuthorizationHeader(headers) {
814
+ const bag = asMutableHeaders(headers);
815
+ if (!bag) return false;
816
+ if (typeof bag.has === "function") return bag.has("Authorization");
817
+ return Object.keys(bag).some((key) => key.toLowerCase() === "authorization");
818
+ }
819
+ function setAuthorizationHeader(headers, value) {
820
+ const bag = asMutableHeaders(headers);
821
+ if (!bag) return;
822
+ if (typeof bag.set === "function") {
823
+ bag.set("Authorization", value);
824
+ return;
825
+ }
826
+ bag.Authorization = value;
827
+ }
828
+ function deleteAuthorizationHeader(headers) {
829
+ const bag = asMutableHeaders(headers);
830
+ if (!bag) return;
831
+ if (typeof bag.delete === "function") {
832
+ bag.delete("Authorization");
833
+ return;
834
+ }
835
+ for (const key of Object.keys(bag)) if (key.toLowerCase() === "authorization") delete bag[key];
836
+ }
791
837
  function getApiClient() {
792
838
  if (!apiClientInstance) apiClientInstance = axios.create({ headers: { "Content-Type": "application/json" } });
793
839
  return apiClientInstance;
@@ -800,37 +846,50 @@ function useApi(options = {}) {
800
846
  if (!authStore.isInitialized) authStore.initialize();
801
847
  if (!interceptorAttached) {
802
848
  apiClient.interceptors.request.use((config) => {
803
- if (authStore.token && config.headers && !config.headers.Authorization) config.headers.Authorization = `Bearer ${authStore.token}`;
849
+ const request = config;
850
+ if (request._mintSkipAuth) {
851
+ delete request._mintSkipAuth;
852
+ deleteAuthorizationHeader(request.headers);
853
+ return request;
854
+ }
855
+ const currentAuthStore = useAuthStore();
856
+ if (currentAuthStore.token && config.headers && !hasAuthorizationHeader(config.headers)) setAuthorizationHeader(config.headers, `Bearer ${currentAuthStore.token}`);
804
857
  return config;
805
858
  });
806
859
  interceptorAttached = true;
807
860
  }
861
+ function getBaseUrl() {
862
+ return options.baseUrl ?? settingsStore.getApiBaseUrl();
863
+ }
864
+ function normalizeUrl(url) {
865
+ return normalizeRequestUrl(getBaseUrl(), url);
866
+ }
808
867
  function requestConfig(config) {
809
868
  const base = {
810
- baseURL: options.baseUrl ?? settingsStore.getApiBaseUrl(),
869
+ baseURL: getBaseUrl(),
811
870
  timeout: options.timeout ?? settingsStore.requestTimeout,
812
871
  ...config
813
872
  };
814
- if (options.withAuth === false) base.headers = {
815
- ...base.headers,
816
- Authorization: void 0
817
- };
873
+ if (options.withAuth === false) {
874
+ base._mintSkipAuth = true;
875
+ deleteAuthorizationHeader(base.headers);
876
+ }
818
877
  return base;
819
878
  }
820
879
  async function get(url, config) {
821
- return (await apiClient.get(url, requestConfig(config))).data;
880
+ return (await apiClient.get(normalizeUrl(url), requestConfig(config))).data;
822
881
  }
823
882
  async function post(url, data, config) {
824
- return (await apiClient.post(url, data, requestConfig(config))).data;
883
+ return (await apiClient.post(normalizeUrl(url), data, requestConfig(config))).data;
825
884
  }
826
885
  async function put(url, data, config) {
827
- return (await apiClient.put(url, data, requestConfig(config))).data;
886
+ return (await apiClient.put(normalizeUrl(url), data, requestConfig(config))).data;
828
887
  }
829
888
  async function patch(url, data, config) {
830
- return (await apiClient.patch(url, data, requestConfig(config))).data;
889
+ return (await apiClient.patch(normalizeUrl(url), data, requestConfig(config))).data;
831
890
  }
832
891
  async function del(url, config) {
833
- return (await apiClient.delete(url, requestConfig(config))).data;
892
+ return (await apiClient.delete(normalizeUrl(url), requestConfig(config))).data;
834
893
  }
835
894
  async function upload(url, file, fieldName = "file", additionalData) {
836
895
  const formData = new FormData();
@@ -838,10 +897,10 @@ function useApi(options = {}) {
838
897
  if (additionalData) Object.entries(additionalData).forEach(([key, value]) => {
839
898
  if (value !== void 0 && value !== null) formData.append(key, typeof value === "object" ? JSON.stringify(value) : String(value));
840
899
  });
841
- return (await apiClient.post(url, formData, requestConfig({ headers: { "Content-Type": void 0 } }))).data;
900
+ return (await apiClient.post(normalizeUrl(url), formData, requestConfig({ headers: { "Content-Type": void 0 } }))).data;
842
901
  }
843
902
  async function download(url, filename) {
844
- const response = await apiClient.get(url, requestConfig({ responseType: "blob" }));
903
+ const response = await apiClient.get(normalizeUrl(url), requestConfig({ responseType: "blob" }));
845
904
  const blob = new Blob([response.data]);
846
905
  const blobUrl = URL.createObjectURL(blob);
847
906
  if (filename) {
@@ -857,7 +916,8 @@ function useApi(options = {}) {
857
916
  return blobUrl;
858
917
  }
859
918
  function buildUrl(path) {
860
- return joinUrlPath(options.baseUrl ?? settingsStore.getApiBaseUrl(), path);
919
+ const baseUrl = getBaseUrl();
920
+ return joinUrlPath(baseUrl, normalizeRequestUrl(baseUrl, path));
861
921
  }
862
922
  function buildWsUrl(path) {
863
923
  return joinUrlPath(settingsStore.getWsBaseUrl(), path);
@@ -2213,8 +2273,9 @@ function computeGroups(allSamples, columns, enabledFields, outlierActions, delim
2213
2273
  const keyParts = [];
2214
2274
  for (const idx of enabledIndices) if (idx < row.length && idx < columns.length) keyParts.push(row[idx]);
2215
2275
  const groupKey = keyParts.join(" / ");
2216
- if (!groupMap.has(groupKey)) groupMap.set(groupKey, []);
2217
- groupMap.get(groupKey).push(sample);
2276
+ const group = groupMap.get(groupKey);
2277
+ if (group) group.push(sample);
2278
+ else groupMap.set(groupKey, [sample]);
2218
2279
  const fields = {};
2219
2280
  for (const col of columns) if (col.index < row.length) fields[col.name] = row[col.index];
2220
2281
  metadata.push({
@@ -2299,8 +2360,9 @@ function computeGroupsFromCsv(csvData, columns, enabledFields) {
2299
2360
  for (const row of csvData.rows) {
2300
2361
  const sampleName = row[csvData.sampleColumn];
2301
2362
  const groupKey = enabledCols.map((col) => row[col.originalName ?? col.name]).join(" / ");
2302
- if (!groupMap.has(groupKey)) groupMap.set(groupKey, []);
2303
- groupMap.get(groupKey).push(sampleName);
2363
+ const group = groupMap.get(groupKey);
2364
+ if (group) group.push(sampleName);
2365
+ else groupMap.set(groupKey, [sampleName]);
2304
2366
  const fields = {};
2305
2367
  for (const col of columns) fields[col.name] = row[col.originalName ?? col.name];
2306
2368
  metadata.push({
@@ -2339,7 +2401,8 @@ function useAutoGroup() {
2339
2401
  const enabledFields = ref(/* @__PURE__ */ new Set());
2340
2402
  const isTabularMode = computed(() => (inputMode.value === "csv" || inputMode.value === "experiment") && csvData.value !== null);
2341
2403
  const samples = computed(() => {
2342
- if (isTabularMode.value && csvData.value) return csvData.value.rows.map((r) => r[csvData.value.sampleColumn]);
2404
+ const data = csvData.value;
2405
+ if (isTabularMode.value && data) return data.rows.map((r) => r[data.sampleColumn]);
2343
2406
  return rawText.value.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
2344
2407
  });
2345
2408
  const hasOutliers = computed(() => outliers.value.length > 0);
@@ -3737,8 +3800,9 @@ function useRackEditor(initialRacks, options) {
3737
3800
  return count;
3738
3801
  });
3739
3802
  function reset() {
3740
- racks.value = [createDefaultRack("Rack 1", 0)];
3741
- activeRackId.value = racks.value[0].id;
3803
+ const rack = createDefaultRack("Rack 1", 0);
3804
+ racks.value = [rack];
3805
+ activeRackId.value = rack.id;
3742
3806
  }
3743
3807
  return {
3744
3808
  racks,
@@ -4246,10 +4310,18 @@ var BUILT_IN_TEMPLATES = [
4246
4310
  }
4247
4311
  ];
4248
4312
  var STORAGE_KEY = "mint-custom-protocol-templates";
4313
+ function isStepTemplate(value) {
4314
+ if (!value || typeof value !== "object") return false;
4315
+ const candidate = value;
4316
+ return typeof candidate.id === "string" && typeof candidate.type === "string" && typeof candidate.name === "string" && Array.isArray(candidate.parameters);
4317
+ }
4249
4318
  function loadCustomTemplates() {
4250
4319
  try {
4251
4320
  const stored = localStorage.getItem(STORAGE_KEY);
4252
- if (stored) return JSON.parse(stored);
4321
+ if (stored) {
4322
+ const parsed = JSON.parse(stored);
4323
+ return Array.isArray(parsed) ? parsed.filter(isStepTemplate) : [];
4324
+ }
4253
4325
  } catch {}
4254
4326
  return [];
4255
4327
  }
@@ -4411,4 +4483,4 @@ function useExperimentData(options = {}) {
4411
4483
  //#endregion
4412
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 };
4413
4485
 
4414
- //# sourceMappingURL=useExperimentData-CM6Y0u5L.js.map
4486
+ //# sourceMappingURL=useExperimentData-BbbdI5xT.js.map