@morscherlab/mint-sdk 1.4.0-beta.16 → 1.4.0-beta.18
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 +14 -3
- package/dist/{AlertBox-SnsbVow-.js → AlertBox-CzLg0UaL.js} +2 -2
- package/dist/{AlertBox-SnsbVow-.js.map → AlertBox-CzLg0UaL.js.map} +1 -1
- package/dist/{AppSidebar-CB0-RQ4m.js → AppSidebar-6oTT-gPO.js} +43 -13
- package/dist/AppSidebar-6oTT-gPO.js.map +1 -0
- package/dist/{ExperimentPopover-ChKCrklH.js → ExperimentPopover-9l4QZlvQ.js} +2 -2
- package/dist/{ExperimentPopover-ChKCrklH.js.map → ExperimentPopover-9l4QZlvQ.js.map} +1 -1
- package/dist/{ExperimentSelectorModal-BqifjNaN.js → ExperimentSelectorModal-pR5fK3pA.js} +3 -3
- package/dist/{ExperimentSelectorModal-BqifjNaN.js.map → ExperimentSelectorModal-pR5fK3pA.js.map} +1 -1
- package/dist/{SegmentedControl-hOgBVdju.js → SegmentedControl-CJ7EAmOq.js} +2 -2
- package/dist/{SegmentedControl-hOgBVdju.js.map → SegmentedControl-CJ7EAmOq.js.map} +1 -1
- package/dist/{SettingsModal-CoZK3EDN.js → SettingsModal-Dyitgzfp.js} +6 -6
- package/dist/{SettingsModal-CoZK3EDN.js.map → SettingsModal-Dyitgzfp.js.map} +1 -1
- package/dist/components/AppTopBar.vue.d.ts +7 -0
- package/dist/components/JobsStatusTray.vue.d.ts +11 -4
- package/dist/components/PluginWorkspaceView.props.d.ts +3 -0
- package/dist/components/PluginWorkspaceView.vue.d.ts +3 -0
- package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +8 -0
- package/dist/composables/useJobCenter.d.ts +11 -3
- package/dist/{controlSchemaModel-CIH5Hqpw.js → controlSchemaModel-DCUOx8ll.js} +2 -2
- package/dist/{controlSchemaModel-CIH5Hqpw.js.map → controlSchemaModel-DCUOx8ll.js.map} +1 -1
- package/dist/{data-OZ94DhVC.js → data-BIiV4kKw.js} +9 -9
- package/dist/{data-OZ94DhVC.js.map → data-BIiV4kKw.js.map} +1 -1
- package/dist/data.js +7 -7
- package/dist/index.js +16 -16
- package/dist/{install-BKgb5qZw.js → install-Dg-Z6ze5.js} +13 -13
- package/dist/{install-BKgb5qZw.js.map → install-Dg-Z6ze5.js.map} +1 -1
- package/dist/install.js +1 -1
- package/dist/{lab-ZDKlx1Oo.js → lab-BHVT1qN2.js} +9 -9
- package/dist/{lab-ZDKlx1Oo.js.map → lab-BHVT1qN2.js.map} +1 -1
- package/dist/lab.js +4 -4
- package/dist/{records-B4yl2oIu.js → records-CR_pYzIP.js} +62 -62
- package/dist/{records-B4yl2oIu.js.map → records-CR_pYzIP.js.map} +1 -1
- package/dist/{runtime-Cz1GJa_O.js → runtime-cHv1phcc.js} +379 -31
- package/dist/runtime-cHv1phcc.js.map +1 -0
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +6 -6
- package/dist/{shell-kq9p4VsX.js → shell-BDuZf2zb.js} +4 -4
- package/dist/{shell-kq9p4VsX.js.map → shell-BDuZf2zb.js.map} +1 -1
- package/dist/shell.js +4 -4
- package/dist/styles.css +163 -307
- package/dist/types/components.d.ts +2 -0
- package/dist/{useExperimentSamples-BLx29CkM.js → useExperimentSamples-Dno1fFMq.js} +3 -3
- package/dist/{useExperimentSamples-BLx29CkM.js.map → useExperimentSamples-Dno1fFMq.js.map} +1 -1
- package/dist/{useListReorder-C_8RYeVP.js → useListReorder-CQ9OCScc.js} +76 -76
- package/dist/useListReorder-CQ9OCScc.js.map +1 -0
- package/dist/{useRackEditor-BhYlXKFM.js → useRackEditor-wL3kPrRT.js} +6 -6
- package/dist/{useRackEditor-BhYlXKFM.js.map → useRackEditor-wL3kPrRT.js.map} +1 -1
- package/dist/useRealtime-BW02xy4Z.js +398 -0
- package/dist/useRealtime-BW02xy4Z.js.map +1 -0
- package/dist/{useRequest-C9uwM79q.js → useRequest-CDkrXNU7.js} +2 -2
- package/dist/{useRequest-C9uwM79q.js.map → useRequest-CDkrXNU7.js.map} +1 -1
- package/package.json +1 -1
- package/src/__tests__/components/AppTopBar.test.ts +33 -2
- package/src/__tests__/components/GeneratedPluginView.test.ts +1 -1
- package/src/__tests__/components/JobsStatusTray.test.ts +137 -71
- package/src/__tests__/components/PluginWorkspaceView.test.ts +56 -1
- package/src/__tests__/composables/useJobCenter.test.ts +223 -3
- package/src/__tests__/docs/frontendDocsCatalog.test.ts +3 -3
- package/src/__tests__/styles/v2AuditRules.test.ts +8 -0
- package/src/components/AppTopBar.vue +14 -0
- package/src/components/GeneratedPluginView.vue +4 -9
- package/src/components/JobsStatusTray.story.vue +78 -43
- package/src/components/JobsStatusTray.vue +171 -279
- package/src/components/PluginWorkspaceView.props.ts +3 -0
- package/src/components/PluginWorkspaceView.vue +17 -0
- package/src/components/internal/WorkspaceShellInternal.vue +12 -0
- package/src/composables/useJobCenter.ts +127 -6
- package/src/runtime.ts +0 -1
- package/src/styles/components/app-top-bar.css +7 -0
- package/src/styles/components/jobs-status-tray.css +129 -296
- package/src/styles/variables.css +16 -0
- package/src/types/components.ts +2 -0
- package/dist/AppSidebar-CB0-RQ4m.js.map +0 -1
- package/dist/runtime-Cz1GJa_O.js.map +0 -1
- package/dist/useListReorder-C_8RYeVP.js.map +0 -1
- package/dist/useRealtime-CUMbW_ln.js +0 -699
- package/dist/useRealtime-CUMbW_ln.js.map +0 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { i as useEventListener, o as __exportAll, t as useDropdownState } from "./useDropdownState-Bb2N4m40.js";
|
|
2
2
|
import { i as AuthenticationRequiredError, o as useApi } from "./experiment-DyO54FQB.js";
|
|
3
|
-
import { L as EmptyState_default,
|
|
4
|
-
import { A as
|
|
5
|
-
import { r as LoadingSpinner_default, t as AlertBox_default } from "./AlertBox-
|
|
6
|
-
import { D as DataFrame_default, T as WellPlate_default, a as GeneratedPlotlyResult_default, b as PlateEditor_default, c as downloadEndpoint, d as useEventStream, f as useSettingsEndpoint, i as GeneratedResultRenderer_default, l as pluginFormDataFromPayload, m as resolvePluginBaseUrl, n as ScheduleCalendar_default, o as createClient, p as buildEndpointUrl, t as useRackEditor, u as uploadEndpoint, v as DoseCalculator_default } from "./useRackEditor-
|
|
7
|
-
import { A as controlValuesToComponentBindingsById, C as usePluginWorkspace, D as useControlSchema, E as getControlDefaults, F as getDefaultControlView, M as controlsTo, O as controlBindings, S as AppLayout_default, T as defineControls, d as useForm, f as AppTopBar_default, j as controlValuesToComponentProps, k as controlValuesToComponentBindings, l as useFormBuilder, p as providePluginIdentity, t as AppSidebar_default, u as evaluateCondition, w as defineControlComponentBindings } from "./AppSidebar-
|
|
8
|
-
import { n as isControlModelBinding } from "./controlSchemaModel-
|
|
9
|
-
import {
|
|
10
|
-
import { Fragment, Teleport, TransitionGroup, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createTextVNode, createVNode, defineComponent, getCurrentScope, guardReactiveProps, mergeProps, normalizeClass, normalizeProps, normalizeStyle, onMounted, onScopeDispose, openBlock, provide, reactive, readonly, ref, renderList, renderSlot, resolveDynamicComponent, toDisplayString, toHandlers, toValue, unref, useId, useSlots, useTemplateRef, watch, withCtx, withModifiers } from "vue";
|
|
3
|
+
import { L as EmptyState_default, d as isActiveJobStatus, f as isTerminalJobStatus, g as resolveJobCapabilities, h as normalizeJobState, m as normalizeJobPercent, o as resolveCurrentPluginId, p as jobStatusLabel, t as isRecord, v as useAppExperiment } from "./records-CR_pYzIP.js";
|
|
4
|
+
import { A as useJobsEndpoint, D as _plugin_vue_export_helper_default, E as GeneratedPathInput_default, M as usePlatform, O as GENERATED_JOBS_API_KEY, R as definePluginWorkspaceView, _ as getTypeDefault, g as getFieldRegistryEntry, h as replaceRecord, k as createGeneratedAnalysisTransport } from "./useListReorder-CQ9OCScc.js";
|
|
5
|
+
import { r as LoadingSpinner_default, t as AlertBox_default } from "./AlertBox-CzLg0UaL.js";
|
|
6
|
+
import { D as DataFrame_default, T as WellPlate_default, a as GeneratedPlotlyResult_default, b as PlateEditor_default, c as downloadEndpoint, d as useEventStream, f as useSettingsEndpoint, i as GeneratedResultRenderer_default, l as pluginFormDataFromPayload, m as resolvePluginBaseUrl, n as ScheduleCalendar_default, o as createClient, p as buildEndpointUrl, t as useRackEditor, u as uploadEndpoint, v as DoseCalculator_default } from "./useRackEditor-wL3kPrRT.js";
|
|
7
|
+
import { A as controlValuesToComponentBindingsById, C as usePluginWorkspace, D as useControlSchema, E as getControlDefaults, F as getDefaultControlView, M as controlsTo, O as controlBindings, S as AppLayout_default, T as defineControls, d as useForm, f as AppTopBar_default, j as controlValuesToComponentProps, k as controlValuesToComponentBindings, l as useFormBuilder, p as providePluginIdentity, t as AppSidebar_default, u as evaluateCondition, w as defineControlComponentBindings } from "./AppSidebar-6oTT-gPO.js";
|
|
8
|
+
import { n as isControlModelBinding } from "./controlSchemaModel-DCUOx8ll.js";
|
|
9
|
+
import { i as JobsStatusTray_default, n as subscribeRealtime, r as useRealtime, t as currentPresence } from "./useRealtime-BW02xy4Z.js";
|
|
10
|
+
import { Fragment, Teleport, TransitionGroup, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createTextVNode, createVNode, defineComponent, getCurrentScope, guardReactiveProps, mergeProps, normalizeClass, normalizeProps, normalizeStyle, onMounted, onScopeDispose, onUnmounted, openBlock, provide, reactive, readonly, ref, renderList, renderSlot, resolveDynamicComponent, shallowRef, toDisplayString, toHandlers, toValue, unref, useId, useSlots, useTemplateRef, watch, withCtx, withModifiers } from "vue";
|
|
11
11
|
import { ChartColumn, Eye, FlaskConical, Pencil, RefreshCw } from "lucide-vue-next";
|
|
12
12
|
//#region src/composables/useToast.ts
|
|
13
13
|
var toasts = ref([]);
|
|
@@ -232,6 +232,314 @@ var AppToastContainer_default = /* @__PURE__ */ defineComponent({
|
|
|
232
232
|
}
|
|
233
233
|
});
|
|
234
234
|
//#endregion
|
|
235
|
+
//#region src/composables/useJobCenter.ts
|
|
236
|
+
/** How long a job that just completed stays in the top-bar run slot. */
|
|
237
|
+
var FINISHED_HOLD_MS = 6e3;
|
|
238
|
+
/**
|
|
239
|
+
* Combines independent job runtimes without copying their reactive state.
|
|
240
|
+
*
|
|
241
|
+
* Job ids must be globally unique across the sources. A duplicate id throws
|
|
242
|
+
* when jobs are read or an id-routed action is requested, rather than silently
|
|
243
|
+
* dispatching a mutation to the wrong owner.
|
|
244
|
+
*/
|
|
245
|
+
function combinePluginJobSources(sources) {
|
|
246
|
+
const jobs = computed(() => {
|
|
247
|
+
const combined = [];
|
|
248
|
+
const owners = /* @__PURE__ */ new Set();
|
|
249
|
+
for (const source of sources) for (const job of toValue(source.jobs)) {
|
|
250
|
+
if (owners.has(job.job_id)) throw duplicatePluginJobId(job.job_id);
|
|
251
|
+
owners.add(job.job_id);
|
|
252
|
+
combined.push(job);
|
|
253
|
+
}
|
|
254
|
+
return combined;
|
|
255
|
+
});
|
|
256
|
+
function ownerFor(jobId) {
|
|
257
|
+
let owner;
|
|
258
|
+
for (const source of sources) {
|
|
259
|
+
if (!toValue(source.jobs).some((job) => job.job_id === jobId)) continue;
|
|
260
|
+
if (owner) throw duplicatePluginJobId(jobId);
|
|
261
|
+
owner = source;
|
|
262
|
+
}
|
|
263
|
+
if (!owner) throw new Error(`Unknown plugin job id: ${jobId}`);
|
|
264
|
+
return owner;
|
|
265
|
+
}
|
|
266
|
+
const combined = { jobs };
|
|
267
|
+
if (sources.some((source) => source.refresh)) combined.refresh = async () => {
|
|
268
|
+
await Promise.all(sources.map((source) => source.refresh?.()));
|
|
269
|
+
return jobs.value;
|
|
270
|
+
};
|
|
271
|
+
if (sources.some((source) => source.cancel)) combined.cancel = async (jobId) => {
|
|
272
|
+
const owner = ownerFor(jobId);
|
|
273
|
+
if (!owner.cancel) throw new Error(`Job ${jobId} cannot be cancelled by its source`);
|
|
274
|
+
return owner.cancel(jobId);
|
|
275
|
+
};
|
|
276
|
+
if (sources.length > 0 && sources.every((source) => source.delete)) combined.delete = async (jobId) => {
|
|
277
|
+
const owner = ownerFor(jobId);
|
|
278
|
+
if (!owner.delete) throw new Error(`Job ${jobId} cannot be deleted by its source`);
|
|
279
|
+
return owner.delete(jobId);
|
|
280
|
+
};
|
|
281
|
+
if (sources.length > 0 && sources.every((source) => source.clear)) combined.clear = async () => {
|
|
282
|
+
const results = await Promise.all(sources.map((source) => source.clear()));
|
|
283
|
+
return results.every((result) => typeof result === "number") ? results.reduce((total, result) => total + result, 0) : void 0;
|
|
284
|
+
};
|
|
285
|
+
if (sources.some((source) => source.dispose)) combined.dispose = () => {
|
|
286
|
+
const errors = [];
|
|
287
|
+
for (const source of sources) try {
|
|
288
|
+
source.dispose?.();
|
|
289
|
+
} catch (cause) {
|
|
290
|
+
errors.push(cause);
|
|
291
|
+
}
|
|
292
|
+
if (errors.length === 1) throw errors[0];
|
|
293
|
+
if (errors.length > 1) throw new PluginJobSourceDisposeError(errors);
|
|
294
|
+
};
|
|
295
|
+
return combined;
|
|
296
|
+
}
|
|
297
|
+
var PluginJobSourceDisposeError = class extends Error {
|
|
298
|
+
cause;
|
|
299
|
+
errors;
|
|
300
|
+
constructor(errors) {
|
|
301
|
+
super("Failed to dispose plugin job sources");
|
|
302
|
+
this.name = "PluginJobSourceDisposeError";
|
|
303
|
+
this.cause = errors[0];
|
|
304
|
+
this.errors = Object.freeze([...errors]);
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
function duplicatePluginJobId(jobId) {
|
|
308
|
+
return /* @__PURE__ */ new Error(`Duplicate plugin job id across combined sources: ${jobId}`);
|
|
309
|
+
}
|
|
310
|
+
/** The stored seen failures, or null when nothing is stored or storage is blocked. */
|
|
311
|
+
function readSeenFailed(key) {
|
|
312
|
+
try {
|
|
313
|
+
const parsed = JSON.parse(globalThis.localStorage?.getItem(key) ?? "null");
|
|
314
|
+
return Array.isArray(parsed) ? new Set(parsed.filter((id) => typeof id === "string")) : null;
|
|
315
|
+
} catch {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function writeSeenFailed(key, seen) {
|
|
320
|
+
try {
|
|
321
|
+
globalThis.localStorage?.setItem(key, JSON.stringify([...seen]));
|
|
322
|
+
} catch {}
|
|
323
|
+
}
|
|
324
|
+
function errorMessage(error, fallback) {
|
|
325
|
+
return error instanceof Error ? error.message : fallback;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Headless controller for plugin job presentation and actions.
|
|
329
|
+
*
|
|
330
|
+
* Every job list is derived directly from the source. The controller never
|
|
331
|
+
* mirrors source jobs into another ref.
|
|
332
|
+
*/
|
|
333
|
+
function useJobCenter(options = {}) {
|
|
334
|
+
const isOpen = ref(false);
|
|
335
|
+
const isLoading = ref(false);
|
|
336
|
+
const error = shallowRef(null);
|
|
337
|
+
const pendingJobIds = ref(/* @__PURE__ */ new Set());
|
|
338
|
+
let requestGeneration = 0;
|
|
339
|
+
let active = true;
|
|
340
|
+
const source = computed(() => toValue(options.source));
|
|
341
|
+
const jobs = computed(() => {
|
|
342
|
+
const currentSource = source.value;
|
|
343
|
+
return currentSource ? [...toValue(currentSource.jobs)].map(normalizeJobState) : [];
|
|
344
|
+
});
|
|
345
|
+
const activeJobs = computed(() => jobs.value.filter((job) => isActiveJobStatus(job.status)));
|
|
346
|
+
const historyJobs = computed(() => jobs.value.filter((job) => !isActiveJobStatus(job.status)));
|
|
347
|
+
const deletableJobs = computed(() => historyJobs.value.filter((job) => job.can_delete));
|
|
348
|
+
const featuredJob = computed(() => activeJobs.value[0] ?? historyJobs.value[0] ?? null);
|
|
349
|
+
const completedCount = computed(() => jobs.value.filter((job) => job.status === "completed").length);
|
|
350
|
+
const { integrated, user, plugin, isAdmin } = usePlatform();
|
|
351
|
+
const isOwnJob = (job) => {
|
|
352
|
+
if (!integrated.value) return true;
|
|
353
|
+
if (user.value == null) return isAdmin.value;
|
|
354
|
+
return (job.owner_user_id ?? job.user_id) === String(user.value.id);
|
|
355
|
+
};
|
|
356
|
+
const ownActiveJobs = computed(() => activeJobs.value.filter(isOwnJob));
|
|
357
|
+
const justFinished = shallowRef(null);
|
|
358
|
+
const heldJob = computed(() => {
|
|
359
|
+
const held = justFinished.value;
|
|
360
|
+
return held ? jobs.value.find((job) => job.job_id === held.job_id) ?? null : null;
|
|
361
|
+
});
|
|
362
|
+
let finishedTimer;
|
|
363
|
+
watch([source, ownActiveJobs], ([currentSource, now], [previousSource, before]) => {
|
|
364
|
+
if (currentSource !== previousSource) {
|
|
365
|
+
justFinished.value = null;
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const nowIds = new Set(now.map((job) => job.job_id));
|
|
369
|
+
const left = new Set(before.filter((job) => !nowIds.has(job.job_id)).map((job) => job.job_id));
|
|
370
|
+
const finished = jobs.value.find((job) => left.has(job.job_id) && job.status === "completed");
|
|
371
|
+
if (!finished) return;
|
|
372
|
+
justFinished.value = finished;
|
|
373
|
+
clearTimeout(finishedTimer);
|
|
374
|
+
finishedTimer = setTimeout(() => {
|
|
375
|
+
justFinished.value = null;
|
|
376
|
+
}, FINISHED_HOLD_MS);
|
|
377
|
+
});
|
|
378
|
+
const seenFailedKey = `mint.jobs.${plugin.value?.id ?? "standalone"}.seen-failed`;
|
|
379
|
+
const storedSeenFailed = readSeenFailed(seenFailedKey);
|
|
380
|
+
const seenFailed = shallowRef(storedSeenFailed ?? /* @__PURE__ */ new Set());
|
|
381
|
+
const unseenFailedJobs = computed(() => historyJobs.value.filter((job) => job.status === "failed" && isOwnJob(job) && !seenFailed.value.has(job.job_id)));
|
|
382
|
+
function markFailedSeen() {
|
|
383
|
+
seenFailed.value = /* @__PURE__ */ new Set([...seenFailed.value, ...unseenFailedJobs.value.map((job) => job.job_id)]);
|
|
384
|
+
writeSeenFailed(seenFailedKey, seenFailed.value);
|
|
385
|
+
}
|
|
386
|
+
let hasBaseline = storedSeenFailed !== null;
|
|
387
|
+
let awaitingLoad = options.loadOnMount !== false && Boolean(source.value?.refresh);
|
|
388
|
+
function takeBaseline() {
|
|
389
|
+
if (hasBaseline || !active) return;
|
|
390
|
+
hasBaseline = true;
|
|
391
|
+
markFailedSeen();
|
|
392
|
+
}
|
|
393
|
+
watch(jobs, (list) => {
|
|
394
|
+
if (!awaitingLoad && list.length > 0) takeBaseline();
|
|
395
|
+
}, { immediate: true });
|
|
396
|
+
watch([isOpen, unseenFailedJobs], ([open, unseen]) => {
|
|
397
|
+
if (open && unseen.length > 0) markFailedSeen();
|
|
398
|
+
});
|
|
399
|
+
const topBarJob = computed(() => {
|
|
400
|
+
const own = ownActiveJobs.value;
|
|
401
|
+
const shown = own.find((job) => job.status === "awaiting_input") ?? own[0];
|
|
402
|
+
if (shown) return options.topBarJob?.(shown) ?? {
|
|
403
|
+
status: shown.status,
|
|
404
|
+
label: shown.progress.message || `${shown.name} ${jobStatusLabel(shown.status).toLowerCase()}`,
|
|
405
|
+
percent: shown.status === "running" ? normalizeJobPercent(shown.progress.percent) : null
|
|
406
|
+
};
|
|
407
|
+
if (heldJob.value) return {
|
|
408
|
+
status: "completed",
|
|
409
|
+
label: `${heldJob.value.name} completed`
|
|
410
|
+
};
|
|
411
|
+
const failed = unseenFailedJobs.value.length;
|
|
412
|
+
return failed > 0 ? {
|
|
413
|
+
status: "failed",
|
|
414
|
+
label: `${failed} failed`
|
|
415
|
+
} : null;
|
|
416
|
+
});
|
|
417
|
+
function handleTopBarClick(onSelect) {
|
|
418
|
+
const finished = heldJob.value;
|
|
419
|
+
if (!isOpen.value && ownActiveJobs.value.length === 0 && finished) onSelect(finished);
|
|
420
|
+
else isOpen.value = !isOpen.value;
|
|
421
|
+
}
|
|
422
|
+
const canRefresh = computed(() => Boolean(source.value?.refresh));
|
|
423
|
+
const canCancel = computed(() => Boolean(source.value?.cancel));
|
|
424
|
+
const canDelete = computed(() => Boolean(source.value?.delete));
|
|
425
|
+
const canClearFinished = computed(() => Boolean(source.value?.clear || source.value?.delete));
|
|
426
|
+
function setPending(jobId, pending) {
|
|
427
|
+
const next = new Set(pendingJobIds.value);
|
|
428
|
+
if (pending) next.add(jobId);
|
|
429
|
+
else next.delete(jobId);
|
|
430
|
+
pendingJobIds.value = next;
|
|
431
|
+
}
|
|
432
|
+
async function refresh() {
|
|
433
|
+
const listJobs = source.value?.refresh;
|
|
434
|
+
if (!listJobs) return false;
|
|
435
|
+
const generation = ++requestGeneration;
|
|
436
|
+
isLoading.value = true;
|
|
437
|
+
error.value = null;
|
|
438
|
+
try {
|
|
439
|
+
await listJobs();
|
|
440
|
+
return active && generation === requestGeneration;
|
|
441
|
+
} catch (cause) {
|
|
442
|
+
if (active && generation === requestGeneration) error.value = errorMessage(cause, "Failed to load jobs");
|
|
443
|
+
return false;
|
|
444
|
+
} finally {
|
|
445
|
+
if (active && generation === requestGeneration) isLoading.value = false;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
async function cancelJob(job) {
|
|
449
|
+
const sourceCancel = source.value?.cancel;
|
|
450
|
+
if (!job.can_cancel || !sourceCancel || pendingJobIds.value.has(job.job_id)) return false;
|
|
451
|
+
setPending(job.job_id, true);
|
|
452
|
+
error.value = null;
|
|
453
|
+
try {
|
|
454
|
+
await sourceCancel(job.job_id);
|
|
455
|
+
return active;
|
|
456
|
+
} catch (cause) {
|
|
457
|
+
if (active) error.value = errorMessage(cause, "Failed to cancel job");
|
|
458
|
+
return false;
|
|
459
|
+
} finally {
|
|
460
|
+
if (active) setPending(job.job_id, false);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
async function deleteJob(job) {
|
|
464
|
+
const sourceDelete = source.value?.delete;
|
|
465
|
+
if (!job.can_delete || !sourceDelete || pendingJobIds.value.has(job.job_id)) return false;
|
|
466
|
+
setPending(job.job_id, true);
|
|
467
|
+
error.value = null;
|
|
468
|
+
try {
|
|
469
|
+
const deleted = await sourceDelete(job.job_id);
|
|
470
|
+
return active && deleted !== false;
|
|
471
|
+
} catch (cause) {
|
|
472
|
+
if (active) error.value = errorMessage(cause, "Failed to delete job");
|
|
473
|
+
return false;
|
|
474
|
+
} finally {
|
|
475
|
+
if (active) setPending(job.job_id, false);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
async function clearFinished() {
|
|
479
|
+
const targets = deletableJobs.value;
|
|
480
|
+
if (targets.length === 0) return false;
|
|
481
|
+
error.value = null;
|
|
482
|
+
try {
|
|
483
|
+
const currentSource = source.value;
|
|
484
|
+
if (currentSource?.clear) {
|
|
485
|
+
const deleted = await currentSource.clear();
|
|
486
|
+
if (!active) return false;
|
|
487
|
+
return typeof deleted !== "number" || deleted === targets.length;
|
|
488
|
+
}
|
|
489
|
+
if (!currentSource?.delete) return false;
|
|
490
|
+
return (await Promise.all(targets.map((job) => deleteJob(job)))).every(Boolean);
|
|
491
|
+
} catch (cause) {
|
|
492
|
+
if (active) error.value = errorMessage(cause, "Failed to clear jobs");
|
|
493
|
+
return false;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
onMounted(() => {
|
|
497
|
+
if (options.loadOnMount === false) return;
|
|
498
|
+
refresh().then((loaded) => {
|
|
499
|
+
awaitingLoad = false;
|
|
500
|
+
if (loaded || jobs.value.length > 0) takeBaseline();
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
onUnmounted(() => {
|
|
504
|
+
active = false;
|
|
505
|
+
requestGeneration += 1;
|
|
506
|
+
clearTimeout(finishedTimer);
|
|
507
|
+
});
|
|
508
|
+
return {
|
|
509
|
+
jobs,
|
|
510
|
+
activeJobs,
|
|
511
|
+
historyJobs,
|
|
512
|
+
deletableJobs,
|
|
513
|
+
featuredJob,
|
|
514
|
+
canRefresh,
|
|
515
|
+
canCancel,
|
|
516
|
+
canDelete,
|
|
517
|
+
canClearFinished,
|
|
518
|
+
panelId: `mint-jobs-tray-${useId()}`,
|
|
519
|
+
topBarJob,
|
|
520
|
+
completedCount,
|
|
521
|
+
isOpen,
|
|
522
|
+
isLoading,
|
|
523
|
+
error,
|
|
524
|
+
pendingJobIds,
|
|
525
|
+
open: () => {
|
|
526
|
+
isOpen.value = true;
|
|
527
|
+
},
|
|
528
|
+
close: () => {
|
|
529
|
+
isOpen.value = false;
|
|
530
|
+
},
|
|
531
|
+
toggle: () => {
|
|
532
|
+
isOpen.value = !isOpen.value;
|
|
533
|
+
},
|
|
534
|
+
handleTopBarClick,
|
|
535
|
+
refresh,
|
|
536
|
+
cancelJob,
|
|
537
|
+
deleteJob,
|
|
538
|
+
clearFinished,
|
|
539
|
+
isPending: (job) => pendingJobIds.value.has(job.job_id)
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
//#endregion
|
|
235
543
|
//#region src/composables/controlWorkspaceOptions.ts
|
|
236
544
|
function mergeControlWorkspaceOptions(base, override) {
|
|
237
545
|
return {
|
|
@@ -396,11 +704,18 @@ var WorkspaceControlsInternal_default = /* @__PURE__ */ defineComponent({
|
|
|
396
704
|
//#region src/components/internal/WorkspaceShellInternal.vue
|
|
397
705
|
var WorkspaceShellInternal_default = /* @__PURE__ */ defineComponent({
|
|
398
706
|
__name: "WorkspaceShellInternal",
|
|
399
|
-
props: {
|
|
707
|
+
props: {
|
|
708
|
+
shell: {},
|
|
709
|
+
job: {},
|
|
710
|
+
jobsDone: {},
|
|
711
|
+
jobsOpen: { type: Boolean },
|
|
712
|
+
jobsPanelId: {}
|
|
713
|
+
},
|
|
400
714
|
emits: [
|
|
401
715
|
"pill-select",
|
|
402
716
|
"pill-option-select",
|
|
403
|
-
"settings-values-change"
|
|
717
|
+
"settings-values-change",
|
|
718
|
+
"job-click"
|
|
404
719
|
],
|
|
405
720
|
setup(__props, { emit: __emit }) {
|
|
406
721
|
/**
|
|
@@ -429,9 +744,14 @@ var WorkspaceShellInternal_default = /* @__PURE__ */ defineComponent({
|
|
|
429
744
|
subtitle: __props.shell.subtitle,
|
|
430
745
|
spec: __props.shell.topBar,
|
|
431
746
|
pill: __props.shell.pill,
|
|
432
|
-
|
|
747
|
+
job: __props.job,
|
|
748
|
+
"jobs-done": __props.jobsDone,
|
|
749
|
+
"jobs-open": __props.jobsOpen,
|
|
750
|
+
"jobs-panel-id": __props.jobsPanelId,
|
|
751
|
+
onJobClick: _cache[0] || (_cache[0] = ($event) => emit("job-click")),
|
|
752
|
+
onPillSelect: _cache[1] || (_cache[1] = ($event) => emit("pill-select", $event)),
|
|
433
753
|
onPillOptionSelect: handlePillOptionSelect,
|
|
434
|
-
onSettingsValuesChange: _cache[
|
|
754
|
+
onSettingsValuesChange: _cache[2] || (_cache[2] = ($event) => emit("settings-values-change", $event))
|
|
435
755
|
}, createSlots({ _: 2 }, [renderList(__props.shell.topBarSlotNames, (slotName) => {
|
|
436
756
|
return {
|
|
437
757
|
name: slotName,
|
|
@@ -441,7 +761,11 @@ var WorkspaceShellInternal_default = /* @__PURE__ */ defineComponent({
|
|
|
441
761
|
"title",
|
|
442
762
|
"subtitle",
|
|
443
763
|
"spec",
|
|
444
|
-
"pill"
|
|
764
|
+
"pill",
|
|
765
|
+
"job",
|
|
766
|
+
"jobs-done",
|
|
767
|
+
"jobs-open",
|
|
768
|
+
"jobs-panel-id"
|
|
445
769
|
])) : createCommentVNode("", true)])]),
|
|
446
770
|
default: withCtx(() => [renderSlot(_ctx.$slots, "default", normalizeProps(guardReactiveProps(__props.shell.controls)))]),
|
|
447
771
|
_: 2
|
|
@@ -551,7 +875,8 @@ var PluginWorkspaceView_default = /* @__PURE__ */ defineComponent({
|
|
|
551
875
|
"update:toggle",
|
|
552
876
|
"section-action",
|
|
553
877
|
"form-submit",
|
|
554
|
-
"form-cancel"
|
|
878
|
+
"form-cancel",
|
|
879
|
+
"job-select"
|
|
555
880
|
],
|
|
556
881
|
setup(__props, { emit: __emit }) {
|
|
557
882
|
/**
|
|
@@ -583,6 +908,11 @@ var PluginWorkspaceView_default = /* @__PURE__ */ defineComponent({
|
|
|
583
908
|
saveDisabledMessage: computed(() => toValue(save?.reason))
|
|
584
909
|
});
|
|
585
910
|
}
|
|
911
|
+
const jobs = props.spec.jobs;
|
|
912
|
+
const jobCenter = jobs ? useJobCenter({ source: jobs }) : null;
|
|
913
|
+
function handleJobSelect(job) {
|
|
914
|
+
emit("job-select", job);
|
|
915
|
+
}
|
|
586
916
|
const { shell, sidebar, controls, handleControlValuesUpdate, handlePillSelect } = usePluginWorkspace({
|
|
587
917
|
props,
|
|
588
918
|
slots,
|
|
@@ -600,14 +930,27 @@ var PluginWorkspaceView_default = /* @__PURE__ */ defineComponent({
|
|
|
600
930
|
return (_ctx, _cache) => {
|
|
601
931
|
return openBlock(), createBlock(WorkspaceShellInternal_default, {
|
|
602
932
|
shell: unref(shell),
|
|
933
|
+
job: unref(jobCenter) ? unref(jobCenter).topBarJob.value : void 0,
|
|
934
|
+
"jobs-done": unref(jobCenter)?.completedCount.value,
|
|
935
|
+
"jobs-open": unref(jobCenter)?.isOpen.value,
|
|
936
|
+
"jobs-panel-id": unref(jobCenter)?.panelId,
|
|
937
|
+
onJobClick: _cache[2] || (_cache[2] = ($event) => unref(jobCenter)?.handleTopBarClick(handleJobSelect)),
|
|
603
938
|
onPillSelect: unref(handlePillSelect),
|
|
604
|
-
onPillOptionSelect: _cache[
|
|
605
|
-
onSettingsValuesChange: _cache[
|
|
939
|
+
onPillOptionSelect: _cache[3] || (_cache[3] = (option, item) => emit("pill-option-select", option, item)),
|
|
940
|
+
onSettingsValuesChange: _cache[4] || (_cache[4] = ($event) => emit("settings-values-change", $event))
|
|
606
941
|
}, createSlots({
|
|
607
|
-
default: withCtx(() => [
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
942
|
+
default: withCtx(() => [
|
|
943
|
+
createVNode(WorkspaceControlsInternal_default, { controls: unref(controls) }, {
|
|
944
|
+
default: withCtx((controlsScope) => [renderSlot(_ctx.$slots, "default", normalizeProps(guardReactiveProps(controlsScope)))]),
|
|
945
|
+
_: 3
|
|
946
|
+
}, 8, ["controls"]),
|
|
947
|
+
__props.spec.toasts !== false ? (openBlock(), createBlock(AppToastContainer_default, { key: 0 })) : createCommentVNode("", true),
|
|
948
|
+
unref(jobCenter) ? (openBlock(), createBlock(JobsStatusTray_default, {
|
|
949
|
+
key: 1,
|
|
950
|
+
center: unref(jobCenter),
|
|
951
|
+
onSelect: handleJobSelect
|
|
952
|
+
}, null, 8, ["center"])) : createCommentVNode("", true)
|
|
953
|
+
]),
|
|
611
954
|
_: 2
|
|
612
955
|
}, [
|
|
613
956
|
unref(slots).topbar ? {
|
|
@@ -639,7 +982,14 @@ var PluginWorkspaceView_default = /* @__PURE__ */ defineComponent({
|
|
|
639
982
|
})]), 1032, ["sidebar", "onUpdate:values"])])]),
|
|
640
983
|
key: "1"
|
|
641
984
|
} : void 0
|
|
642
|
-
]), 1032, [
|
|
985
|
+
]), 1032, [
|
|
986
|
+
"shell",
|
|
987
|
+
"job",
|
|
988
|
+
"jobs-done",
|
|
989
|
+
"jobs-open",
|
|
990
|
+
"jobs-panel-id",
|
|
991
|
+
"onPillSelect"
|
|
992
|
+
]);
|
|
643
993
|
};
|
|
644
994
|
}
|
|
645
995
|
});
|
|
@@ -901,7 +1251,8 @@ var GeneratedPluginView_default = /*#__PURE__*/ _plugin_vue_export_helper_defaul
|
|
|
901
1251
|
title: "Parameters",
|
|
902
1252
|
panels: panels.value,
|
|
903
1253
|
forms: forms.value
|
|
904
|
-
}
|
|
1254
|
+
},
|
|
1255
|
+
jobs: generated
|
|
905
1256
|
}));
|
|
906
1257
|
watch(() => generated.manifest.value?.ui, (ui) => {
|
|
907
1258
|
if (ui) providePluginIdentity({ plugin: {
|
|
@@ -956,7 +1307,8 @@ var GeneratedPluginView_default = /*#__PURE__*/ _plugin_vue_export_helper_defaul
|
|
|
956
1307
|
"form-loading": unref(generated).isSubmitting.value,
|
|
957
1308
|
"form-disabled": unref(generated).isSubmitting.value,
|
|
958
1309
|
"onUpdate:values": updateValues,
|
|
959
|
-
onFormSubmit: submitJob
|
|
1310
|
+
onFormSubmit: submitJob,
|
|
1311
|
+
onJobSelect: selectJob
|
|
960
1312
|
}, {
|
|
961
1313
|
default: withCtx(() => [createElementVNode("main", _hoisted_1$3, [unref(generated).isLoading.value ? (openBlock(), createElementBlock("div", _hoisted_2$2, [createVNode(LoadingSpinner_default, { size: "lg" }), _cache[1] || (_cache[1] = createElementVNode("span", null, "Loading analysis workspace", -1))])) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
|
|
962
1314
|
unref(generated).error.value ? (openBlock(), createBlock(AlertBox_default, {
|
|
@@ -980,11 +1332,7 @@ var GeneratedPluginView_default = /*#__PURE__*/ _plugin_vue_export_helper_defaul
|
|
|
980
1332
|
description: "Set the parameters and run the analysis. Jobs continue independently if you switch tabs.",
|
|
981
1333
|
size: "md"
|
|
982
1334
|
}))])
|
|
983
|
-
], 64)),
|
|
984
|
-
source: unref(generated),
|
|
985
|
-
"teleport-to": false,
|
|
986
|
-
onSelect: selectJob
|
|
987
|
-
}, null, 8, ["source"])])]),
|
|
1335
|
+
], 64))])]),
|
|
988
1336
|
_: 1
|
|
989
1337
|
}, 8, [
|
|
990
1338
|
"view",
|
|
@@ -995,7 +1343,7 @@ var GeneratedPluginView_default = /*#__PURE__*/ _plugin_vue_export_helper_defaul
|
|
|
995
1343
|
]);
|
|
996
1344
|
};
|
|
997
1345
|
}
|
|
998
|
-
}), [["__scopeId", "data-v-
|
|
1346
|
+
}), [["__scopeId", "data-v-c8ff991b"]]);
|
|
999
1347
|
//#endregion
|
|
1000
1348
|
//#region src/components/internal/PlateEditorBindingInternal.vue
|
|
1001
1349
|
var PlateEditorBindingInternal_default = /* @__PURE__ */ defineComponent({
|
|
@@ -1486,6 +1834,6 @@ var runtime_exports = /* @__PURE__ */ __exportAll({
|
|
|
1486
1834
|
useSettingsEndpoint: () => useSettingsEndpoint
|
|
1487
1835
|
});
|
|
1488
1836
|
//#endregion
|
|
1489
|
-
export { ComponentBindingRenderer_default as a, generatedJobFormSchema as c, PluginWorkspaceView_default as d, useControlWorkspace as f, RuntimeUpdateNotice_default as i, normalizeGeneratedJobInput as l,
|
|
1837
|
+
export { ComponentBindingRenderer_default as a, generatedJobFormSchema as c, PluginWorkspaceView_default as d, useControlWorkspace as f, useToast as g, AppToastContainer_default as h, RuntimeUpdateNotice_default as i, normalizeGeneratedJobInput as l, useJobCenter as m, PresenceAvatars_default as n, GeneratedPluginView_default as o, combinePluginJobSources as p, usePresence as r, generatedJobDefaults as s, runtime_exports as t, Avatar_default as u };
|
|
1490
1838
|
|
|
1491
|
-
//# sourceMappingURL=runtime-
|
|
1839
|
+
//# sourceMappingURL=runtime-cHv1phcc.js.map
|