@morscherlab/mint-sdk 1.5.0-beta.2 → 1.5.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AlertBox-CIhqsWbN.js → AlertBox-CexONPhA.js} +2 -2
- package/dist/{AlertBox-CIhqsWbN.js.map → AlertBox-CexONPhA.js.map} +1 -1
- package/dist/{AppSidebar-BDrd7Kwn.js → AppSidebar-FxlF5_dI.js} +55 -40
- package/dist/AppSidebar-FxlF5_dI.js.map +1 -0
- package/dist/{ConfirmDialog-CjWoxAD1.js → ConfirmDialog-D9QXFpsy.js} +2 -2
- package/dist/ConfirmDialog-D9QXFpsy.js.map +1 -0
- package/dist/{ExperimentPopover-BM92LlrQ.js → ExperimentPopover-Bbd6RaoH.js} +3 -3
- package/dist/{ExperimentPopover-BM92LlrQ.js.map → ExperimentPopover-Bbd6RaoH.js.map} +1 -1
- package/dist/{ExperimentSelectorModal-LsB1ABmh.js → ExperimentSelectorModal-CUkKmgfr.js} +35 -6
- package/dist/ExperimentSelectorModal-CUkKmgfr.js.map +1 -0
- package/dist/{SegmentedControl-Dz41vAFM.js → SegmentedControl-C-sckoJZ.js} +2 -2
- package/dist/{SegmentedControl-Dz41vAFM.js.map → SegmentedControl-C-sckoJZ.js.map} +1 -1
- package/dist/{SettingsModal-C-MSR-bk.js → SettingsModal-BIC0dAYc.js} +6 -6
- package/dist/{SettingsModal-C-MSR-bk.js.map → SettingsModal-BIC0dAYc.js.map} +1 -1
- package/dist/components/FormBuilder.vue.d.ts +1 -1
- package/dist/components/PluginIcon.vue.d.ts +2 -2
- package/dist/components/SectionCard.vue.d.ts +13 -1
- package/dist/components/SettingsModal.vue.d.ts +1 -1
- package/dist/components/internal/FormSchemaInternal.vue.d.ts +9 -9
- package/dist/components/internal/FormSectionRenderer.vue.d.ts +4 -4
- package/dist/composables/useExperimentSelector.d.ts +2 -0
- package/dist/composables/useOnboarding.d.ts +1 -1
- package/dist/{controlSchemaModel-BbXbMgc6.js → controlSchemaModel-C_XqpkgE.js} +2 -2
- package/dist/{controlSchemaModel-BbXbMgc6.js.map → controlSchemaModel-C_XqpkgE.js.map} +1 -1
- package/dist/{data-LzdkEZJ-.js → data-CfY7o8QC.js} +9 -9
- package/dist/{data-LzdkEZJ-.js.map → data-CfY7o8QC.js.map} +1 -1
- package/dist/data.js +7 -7
- package/dist/index.d.ts +0 -1
- package/dist/index.js +14 -19
- package/dist/index.js.map +1 -1
- package/dist/{install-UTqrAgHe.js → install-QF3Sh82O.js} +15 -39
- package/dist/install-QF3Sh82O.js.map +1 -0
- package/dist/install.js +1 -1
- package/dist/{lab-CbdDLAVr.js → lab-BcX5JBSY.js} +34 -112
- package/dist/lab-BcX5JBSY.js.map +1 -0
- package/dist/lab.js +4 -4
- package/dist/{records-CWRztE5M.js → records-BAymyHAn.js} +10 -110
- package/dist/records-BAymyHAn.js.map +1 -0
- package/dist/{runtime-DrL6lKAb.js → runtime-DngeJtdq.js} +82 -45
- package/dist/runtime-DngeJtdq.js.map +1 -0
- package/dist/runtime.js +6 -6
- package/dist/{shell-Ch7cJnjo.js → shell-CWZi97nW.js} +4 -4
- package/dist/{shell-Ch7cJnjo.js.map → shell-CWZi97nW.js.map} +1 -1
- package/dist/shell.js +4 -4
- package/dist/styles.css +181 -125
- package/dist/styles.d.ts +1 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/instrument.d.ts +5 -17
- package/dist/{useExperimentSamples-Cz2v3V_q.js → useExperimentSamples-DEDAKa2E.js} +3 -3
- package/dist/{useExperimentSamples-Cz2v3V_q.js.map → useExperimentSamples-DEDAKa2E.js.map} +1 -1
- package/dist/{useListReorder-QqJc1dpX.js → useListReorder-W1bzK_bo.js} +15 -3
- package/dist/useListReorder-W1bzK_bo.js.map +1 -0
- package/dist/{useRackEditor-BHGM1Ngz.js → useRackEditor-Bmtuo0aj.js} +155 -26
- package/dist/useRackEditor-Bmtuo0aj.js.map +1 -0
- package/dist/{useRealtime-CNQbrS01.js → useRealtime-jOetF2mM.js} +3 -3
- package/dist/{useRealtime-CNQbrS01.js.map → useRealtime-jOetF2mM.js.map} +1 -1
- package/dist/{useRequest-Bfsh7gIm.js → useRequest-D9-3BS-3.js} +2 -2
- package/dist/{useRequest-Bfsh7gIm.js.map → useRequest-D9-3BS-3.js.map} +1 -1
- package/package.json +7 -4
- package/src/__tests__/components/AppTopBarOnboarding.test.ts +10 -0
- package/src/__tests__/components/ConfirmDialog.test.ts +2 -2
- package/src/__tests__/components/ExperimentSelectorModal.test.ts +16 -0
- package/src/__tests__/components/SectionCard.test.ts +33 -0
- package/src/__tests__/components/deprecationTags.test.ts +4 -18
- package/src/__tests__/composables/useJobsEndpoint.test.ts +50 -0
- package/src/__tests__/composables/useOnboarding.test.ts +2 -2
- package/src/__tests__/docs/frontendDocsCatalog.test.ts +3 -3
- package/src/components/AppSidebar.vue +2 -2
- package/src/components/CollapsibleCard.vue +1 -1
- package/src/components/ConfirmDialog.vue +1 -1
- package/src/components/ExperimentSelectorModal.vue +35 -0
- package/src/components/FileUploader.vue +3 -35
- package/src/components/GeneratedPluginView.vue +35 -81
- package/src/components/GeneratedResultRenderer.vue +2 -1
- package/src/components/InstrumentStatusPanels.vue +3 -3
- package/src/components/PlotlyChart.vue +1 -1
- package/src/components/PluginIcon.story.vue +2 -2
- package/src/components/PluginIcon.vue +2 -2
- package/src/components/SectionCard.vue +42 -6
- package/src/components/internal/AppTopBarOnboardingInternal.vue +30 -14
- package/src/composables/index.ts +1 -2
- package/src/composables/useExperimentSelector.ts +4 -0
- package/src/composables/useJobsEndpoint.ts +15 -4
- package/src/composables/useOnboarding.ts +3 -3
- package/src/generated-ui/main.ts +3 -1
- package/src/index.ts +0 -3
- package/src/styles/components/alert-box.css +1 -1
- package/src/styles/components/app-sidebar.css +5 -5
- package/src/styles/components/button.css +3 -3
- package/src/styles/components/checkbox.css +1 -1
- package/src/styles/components/collapsible-card.css +1 -1
- package/src/styles/components/confirm-dialog.css +2 -2
- package/src/styles/components/file-uploader.css +3 -3
- package/src/styles/components/icon-button.css +3 -3
- package/src/styles/components/loading-spinner.css +1 -1
- package/src/styles/components/onboarding.css +39 -0
- package/src/styles/components/pill.css +1 -1
- package/src/styles/components/plugin-icon.css +2 -2
- package/src/styles/components/presence-avatars.css +4 -4
- package/src/styles/components/progress-bar.css +1 -1
- package/src/styles/components/section-card.css +65 -6
- package/src/styles/components/sequence-input.css +1 -1
- package/src/styles/components/slider.css +1 -1
- package/src/styles/components/toggle.css +1 -1
- package/src/styles/variables.css +10 -4
- package/src/types/index.ts +0 -1
- package/src/types/instrument.ts +5 -18
- package/dist/AppSidebar-BDrd7Kwn.js.map +0 -1
- package/dist/ConfirmDialog-CjWoxAD1.js.map +0 -1
- package/dist/ExperimentSelectorModal-LsB1ABmh.js.map +0 -1
- package/dist/deprecatedRootExports.d.ts +0 -191
- package/dist/install-UTqrAgHe.js.map +0 -1
- package/dist/lab-CbdDLAVr.js.map +0 -1
- package/dist/records-CWRztE5M.js.map +0 -1
- package/dist/runtime-DrL6lKAb.js.map +0 -1
- package/dist/useListReorder-QqJc1dpX.js.map +0 -1
- package/dist/useRackEditor-BHGM1Ngz.js.map +0 -1
- package/src/deprecatedRootExports.ts +0 -226
|
@@ -1595,6 +1595,56 @@ describe('useJobsEndpoint', () => {
|
|
|
1595
1595
|
}
|
|
1596
1596
|
})
|
|
1597
1597
|
|
|
1598
|
+
it.each([
|
|
1599
|
+
{ name: 'resolves when the server still has the job', answers: [() => response(completedJob('submitted-job'))], outcome: 'completed' },
|
|
1600
|
+
{ name: 'rejects when the server no longer has the job', answers: [() => response({ detail: 'Job not found' }, 404)], outcome: 'Job submitted-job is no longer available' },
|
|
1601
|
+
{ name: 'checks again on the next snapshot after a retryable error', answers: [() => response({ detail: 'Busy' }, 503), () => response(completedJob('submitted-job'))], outcome: 'completed' },
|
|
1602
|
+
])('should check a waited job that a stale stream snapshot omits and $name', async ({ answers, outcome }) => {
|
|
1603
|
+
const encoder = new TextEncoder()
|
|
1604
|
+
let pushFrame!: (frame: string) => void
|
|
1605
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
1606
|
+
start(controller) { pushFrame = frame => controller.enqueue(encoder.encode(frame)) },
|
|
1607
|
+
})
|
|
1608
|
+
let lookups = 0
|
|
1609
|
+
const transport = vi.fn<GeneratedAnalysisTransport>(async (input, init) => {
|
|
1610
|
+
const url = String(input)
|
|
1611
|
+
if (url.endsWith('/jobs/sessions') && init?.method === 'GET') return response({ sessions: [] })
|
|
1612
|
+
if (url.endsWith('/jobs/sessions')) {
|
|
1613
|
+
return response({ id: 'session-1', tab_id: 'tab-1', created_at: '', last_seen_at: '', active_job_ids: [] })
|
|
1614
|
+
}
|
|
1615
|
+
if (url.endsWith('/jobs/events')) return new Response(stream, { headers: { 'Content-Type': 'text/event-stream' } })
|
|
1616
|
+
if (url.endsWith('/jobs') && init?.method === 'POST') return response(activeJob('submitted-job'))
|
|
1617
|
+
if (url.endsWith('/jobs')) return response({ jobs: [] })
|
|
1618
|
+
if (url.endsWith('/jobs/submitted-job')) {
|
|
1619
|
+
lookups += 1
|
|
1620
|
+
return lookups === 1 ? response(activeJob('submitted-job', 'running')) : answers[lookups - 2]!()
|
|
1621
|
+
}
|
|
1622
|
+
throw new Error(`Unexpected request: ${url}`)
|
|
1623
|
+
})
|
|
1624
|
+
const scope = effectScope()
|
|
1625
|
+
const generated = scope.run(() => useJobsEndpoint({
|
|
1626
|
+
baseUrl: '/api/demo',
|
|
1627
|
+
transport,
|
|
1628
|
+
storage: memoryStorage(),
|
|
1629
|
+
createId: () => 'tab-1',
|
|
1630
|
+
streamReconnectDelay: 60_000,
|
|
1631
|
+
}))!
|
|
1632
|
+
|
|
1633
|
+
try {
|
|
1634
|
+
const waiting = generated.wait(await generated.submit('run', {}))
|
|
1635
|
+
.then(job => job.status, (cause: Error) => cause.message)
|
|
1636
|
+
// Each snapshot was built before the submit reached the server.
|
|
1637
|
+
for (const [index] of answers.entries()) {
|
|
1638
|
+
await vi.waitFor(() => expect(lookups).toBe(index + 1))
|
|
1639
|
+
pushFrame(`event: snapshot\ndata: ${JSON.stringify({ jobs: [] })}\n\n`)
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
expect(await waiting).toBe(outcome)
|
|
1643
|
+
} finally {
|
|
1644
|
+
scope.stop()
|
|
1645
|
+
}
|
|
1646
|
+
})
|
|
1647
|
+
|
|
1598
1648
|
it('rejects pending waiters when their job is deleted or the runtime is disposed', async () => {
|
|
1599
1649
|
const queued = activeJob('delete-wait-job')
|
|
1600
1650
|
const disposeJobRequest = deferred<Response>()
|
|
@@ -98,7 +98,7 @@ describe('provideOnboarding', () => {
|
|
|
98
98
|
expect(state.active.value).toMatchObject({ step: { id: 'b' }, index: 0, count: 1 })
|
|
99
99
|
})
|
|
100
100
|
|
|
101
|
-
it('should mark every step seen on dismiss and not start
|
|
101
|
+
it('should mark every step of every tour seen on dismiss and not start a tour again', async () => {
|
|
102
102
|
const state = setup()
|
|
103
103
|
await flush()
|
|
104
104
|
state.dismiss()
|
|
@@ -107,7 +107,7 @@ describe('provideOnboarding', () => {
|
|
|
107
107
|
const again = setup()
|
|
108
108
|
await flush()
|
|
109
109
|
|
|
110
|
-
expect(stored()).toEqual(expect.arrayContaining(['step:intro/a', 'step:intro/b']))
|
|
110
|
+
expect(stored()).toEqual(expect.arrayContaining(['step:intro/a', 'step:intro/b', 'step:list/new']))
|
|
111
111
|
expect(again.active.value).toBeNull()
|
|
112
112
|
})
|
|
113
113
|
|
|
@@ -470,9 +470,9 @@ describe('frontend docs catalog coverage', () => {
|
|
|
470
470
|
'BaseButton',
|
|
471
471
|
'PluginWorkspaceView',
|
|
472
472
|
'DataFrame',
|
|
473
|
-
'
|
|
474
|
-
'
|
|
475
|
-
'
|
|
473
|
+
'useExperiment',
|
|
474
|
+
'useRequest',
|
|
475
|
+
'UseExperimentReturn',
|
|
476
476
|
'FormSectionSchema',
|
|
477
477
|
]))
|
|
478
478
|
})
|
|
@@ -208,11 +208,11 @@ function railIconStyle(section: SidebarToolSection): Record<string, string> {
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
function badgeToneClass(section: SidebarToolSection): string {
|
|
211
|
-
return `mint-sidebar__rail-badge--${section.badgeTone ?? '
|
|
211
|
+
return `mint-sidebar__rail-badge--${section.badgeTone ?? 'primary'}`
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
function tagToneClass(section: SidebarToolSection): string {
|
|
215
|
-
return `mint-sidebar__tag--${section.badgeTone ?? '
|
|
215
|
+
return `mint-sidebar__tag--${section.badgeTone ?? 'primary'}`
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
function actionClasses(action: SidebarToolSectionAction): string[] {
|
|
@@ -41,7 +41,7 @@ function handleConfirm() {
|
|
|
41
41
|
|
|
42
42
|
const confirmVariant = computed<ButtonVariant>(() => {
|
|
43
43
|
if (props.tone === 'error') return 'danger'
|
|
44
|
-
if (props.tone === 'warning' || props.tone === 'cta') return '
|
|
44
|
+
if (props.tone === 'warning' || props.tone === 'cta') return 'primary'
|
|
45
45
|
if (props.tone === 'success') return 'success'
|
|
46
46
|
return 'primary'
|
|
47
47
|
})
|
|
@@ -53,6 +53,28 @@ function close() {
|
|
|
53
53
|
|
|
54
54
|
const experimentStore = useExperimentStore()
|
|
55
55
|
|
|
56
|
+
// The filter choices (not the search) survive a close or a page reload, so a
|
|
57
|
+
// user who works on their own ongoing experiments does not pick them again.
|
|
58
|
+
// One key for every plugin: all plugin pages share the platform origin. The
|
|
59
|
+
// experiment type is left out, since each plugin allows its own types.
|
|
60
|
+
const FILTERS_STORAGE_KEY = 'mint.experimentSelector.filters'
|
|
61
|
+
const REMEMBERED_FILTERS = ['status', 'project', 'datePreset', 'mine'] as const
|
|
62
|
+
|
|
63
|
+
function readRememberedFilters(): Partial<ExperimentFilters> {
|
|
64
|
+
try {
|
|
65
|
+
const saved: unknown = JSON.parse(globalThis.localStorage?.getItem(FILTERS_STORAGE_KEY) ?? '{}')
|
|
66
|
+
if (!saved || typeof saved !== 'object') return {}
|
|
67
|
+
const restored: Record<string, unknown> = {}
|
|
68
|
+
for (const key of REMEMBERED_FILTERS) {
|
|
69
|
+
const value = (saved as Record<string, unknown>)[key]
|
|
70
|
+
if (typeof value === 'string' || value === true) restored[key] = value
|
|
71
|
+
}
|
|
72
|
+
return restored as Partial<ExperimentFilters>
|
|
73
|
+
} catch {
|
|
74
|
+
return {}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
56
78
|
const {
|
|
57
79
|
experiments,
|
|
58
80
|
filters,
|
|
@@ -68,8 +90,21 @@ const {
|
|
|
68
90
|
experimentType: props.experimentType,
|
|
69
91
|
allowedExperimentTypes: props.allowedExperimentTypes,
|
|
70
92
|
withStatusCounts: true,
|
|
93
|
+
initialFilters: readRememberedFilters(),
|
|
71
94
|
})
|
|
72
95
|
|
|
96
|
+
watch(
|
|
97
|
+
() => REMEMBERED_FILTERS.map(key => filters[key]),
|
|
98
|
+
() => {
|
|
99
|
+
const saved = Object.fromEntries(REMEMBERED_FILTERS.filter(key => filters[key]).map(key => [key, filters[key]]))
|
|
100
|
+
try {
|
|
101
|
+
globalThis.localStorage?.setItem(FILTERS_STORAGE_KEY, JSON.stringify(saved))
|
|
102
|
+
} catch {
|
|
103
|
+
// Storage blocked (private mode): the filters last only while the page is open.
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
)
|
|
107
|
+
|
|
73
108
|
// Re-mounting the list on open clears its keyboard highlight.
|
|
74
109
|
const openCount = ref(0)
|
|
75
110
|
|
|
@@ -63,35 +63,8 @@ const maxSizeLabel = computed(() => {
|
|
|
63
63
|
return `${props.maxSize} bytes`
|
|
64
64
|
})
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
tsv: { color: '#10B981', label: 'TSV' },
|
|
69
|
-
xlsx: { color: '#10B981', label: 'XLSX' },
|
|
70
|
-
xls: { color: '#10B981', label: 'XLS' },
|
|
71
|
-
pdf: { color: '#EF4444', label: 'PDF' },
|
|
72
|
-
png: { color: '#8B5CF6', label: 'PNG' },
|
|
73
|
-
jpg: { color: '#8B5CF6', label: 'JPG' },
|
|
74
|
-
jpeg: { color: '#8B5CF6', label: 'JPEG' },
|
|
75
|
-
gif: { color: '#8B5CF6', label: 'GIF' },
|
|
76
|
-
svg: { color: '#8B5CF6', label: 'SVG' },
|
|
77
|
-
webp: { color: '#8B5CF6', label: 'WEBP' },
|
|
78
|
-
json: { color: '#F59E0B', label: 'JSON' },
|
|
79
|
-
xml: { color: '#F59E0B', label: 'XML' },
|
|
80
|
-
txt: { color: '#64748B', label: 'TXT' },
|
|
81
|
-
zip: { color: '#6366F1', label: 'ZIP' },
|
|
82
|
-
gz: { color: '#6366F1', label: 'GZ' },
|
|
83
|
-
py: { color: '#3B82F6', label: 'PY' },
|
|
84
|
-
js: { color: '#EAB308', label: 'JS' },
|
|
85
|
-
ts: { color: '#3B82F6', label: 'TS' },
|
|
86
|
-
fasta: { color: '#EC4899', label: 'FASTA' },
|
|
87
|
-
fastq: { color: '#EC4899', label: 'FASTQ' },
|
|
88
|
-
bam: { color: '#EC4899', label: 'BAM' },
|
|
89
|
-
vcf: { color: '#EC4899', label: 'VCF' },
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function getFileType(filename: string): { color: string; label: string } {
|
|
93
|
-
const ext = filename.split('.').pop()?.toLowerCase() || ''
|
|
94
|
-
return fileTypeMap[ext] || { color: '#94A3B8', label: ext.toUpperCase() || 'FILE' }
|
|
66
|
+
function fileTypeLabel(filename: string): string {
|
|
67
|
+
return filename.split('.').pop()?.toUpperCase() || 'FILE'
|
|
95
68
|
}
|
|
96
69
|
|
|
97
70
|
function validateFiles(files: FileList | File[]): File[] {
|
|
@@ -420,12 +393,7 @@ function formatFileSize(bytes: number): string {
|
|
|
420
393
|
class="mint-file-uploader__file"
|
|
421
394
|
>
|
|
422
395
|
<div class="mint-file-uploader__file-info">
|
|
423
|
-
<span
|
|
424
|
-
class="mint-file-uploader__file-badge"
|
|
425
|
-
:style="{ '--badge-color': getFileType(file.name).color }"
|
|
426
|
-
>
|
|
427
|
-
{{ getFileType(file.name).label }}
|
|
428
|
-
</span>
|
|
396
|
+
<span class="mint-file-uploader__file-badge">{{ fileTypeLabel(file.name) }}</span>
|
|
429
397
|
<div class="mint-file-uploader__file-meta">
|
|
430
398
|
<span class="mint-file-uploader__file-name">{{ file.name }}</span>
|
|
431
399
|
<span class="mint-file-uploader__file-size">{{ formatFileSize(file.size) }}</span>
|
|
@@ -22,10 +22,12 @@ import {
|
|
|
22
22
|
type GeneratedAnalysisTransport,
|
|
23
23
|
} from '../composables/useJobsEndpoint'
|
|
24
24
|
import AlertBox from './AlertBox.vue'
|
|
25
|
+
import AppContainer from './AppContainer.vue'
|
|
25
26
|
import EmptyState from './EmptyState.vue'
|
|
26
27
|
import GeneratedResultRenderer from './GeneratedResultRenderer.vue'
|
|
27
28
|
import LoadingSpinner from './LoadingSpinner.vue'
|
|
28
29
|
import PluginWorkspaceView from './PluginWorkspaceView.vue'
|
|
30
|
+
import SectionCard from './SectionCard.vue'
|
|
29
31
|
import { GENERATED_JOBS_API_KEY } from './internal/generatedJobsApi'
|
|
30
32
|
import { definePluginWorkspaceView } from '../composables/componentSpecs'
|
|
31
33
|
import { providePluginIdentity } from '../composables/pluginIdentity'
|
|
@@ -48,6 +50,7 @@ const generated = useJobsEndpoint({
|
|
|
48
50
|
provide(GENERATED_JOBS_API_KEY, { baseUrl: props.baseUrl, transport: props.transport })
|
|
49
51
|
const activeDefinitionId = ref('')
|
|
50
52
|
const selectedJobId = ref('')
|
|
53
|
+
const sidebarCollapsed = ref(false)
|
|
51
54
|
const valuesByDefinition = reactive<Record<string, Record<string, unknown>>>({})
|
|
52
55
|
const definitions = computed(() => generated.manifest.value?.jobs ?? [])
|
|
53
56
|
const activeDefinition = computed(() => (
|
|
@@ -77,12 +80,22 @@ const workspace = computed(() => definePluginWorkspaceView({
|
|
|
77
80
|
subtitle: generated.manifest.value?.ui.subtitle || generated.manifest.value?.ui.description,
|
|
78
81
|
// The platform shell renders the toasts around this view.
|
|
79
82
|
toasts: false,
|
|
83
|
+
floating: true,
|
|
80
84
|
topBar: {
|
|
85
|
+
variant: 'card',
|
|
81
86
|
pills: pillNav.value,
|
|
87
|
+
themeToggle: true,
|
|
82
88
|
settings: false,
|
|
83
89
|
standaloneLabel: props.showStandaloneLabel ? 'Standalone' : false,
|
|
84
90
|
},
|
|
85
|
-
sidebar: {
|
|
91
|
+
sidebar: {
|
|
92
|
+
title: 'Parameters',
|
|
93
|
+
width: '300px',
|
|
94
|
+
collapsible: true,
|
|
95
|
+
collapsedWidth: '3rem',
|
|
96
|
+
panels: panels.value,
|
|
97
|
+
forms: forms.value,
|
|
98
|
+
},
|
|
86
99
|
jobs: generated,
|
|
87
100
|
}))
|
|
88
101
|
|
|
@@ -160,6 +173,7 @@ onMounted(() => {
|
|
|
160
173
|
<template>
|
|
161
174
|
<PluginWorkspaceView
|
|
162
175
|
v-model:view="activeDefinitionId"
|
|
176
|
+
v-model:sidebar-collapsed="sidebarCollapsed"
|
|
163
177
|
:spec="workspace"
|
|
164
178
|
:values="currentValues"
|
|
165
179
|
:form-loading="generated.isSubmitting.value"
|
|
@@ -168,7 +182,7 @@ onMounted(() => {
|
|
|
168
182
|
@form-submit="submitJob"
|
|
169
183
|
@job-select="selectJob"
|
|
170
184
|
>
|
|
171
|
-
<
|
|
185
|
+
<AppContainer direction="column" gap="var(--mint-gutter)" class="mint-generated-workspace">
|
|
172
186
|
<div v-if="generated.isLoading.value" class="mint-generated-workspace__loading">
|
|
173
187
|
<LoadingSpinner size="lg" />
|
|
174
188
|
<span>Loading analysis workspace</span>
|
|
@@ -177,18 +191,15 @@ onMounted(() => {
|
|
|
177
191
|
<template v-else>
|
|
178
192
|
<AlertBox v-if="generated.error.value" tone="error">{{ generated.error.value }}</AlertBox>
|
|
179
193
|
|
|
180
|
-
<
|
|
181
|
-
<
|
|
182
|
-
<
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
</
|
|
186
|
-
<
|
|
187
|
-
|
|
188
|
-
</
|
|
189
|
-
</header>
|
|
190
|
-
|
|
191
|
-
<section class="mint-generated-workspace__result">
|
|
194
|
+
<SectionCard :title="activeDefinition?.title ?? 'Result'">
|
|
195
|
+
<template v-if="activeDefinition" #actions>
|
|
196
|
+
<span class="mint-generated-workspace__resources" aria-label="Job resource requirements">
|
|
197
|
+
{{ resourceSummary(activeDefinition) }}
|
|
198
|
+
</span>
|
|
199
|
+
</template>
|
|
200
|
+
<p v-if="activeDefinition?.description" class="mint-generated-workspace__description">
|
|
201
|
+
{{ activeDefinition.description }}
|
|
202
|
+
</p>
|
|
192
203
|
<GeneratedResultRenderer
|
|
193
204
|
v-if="currentResult"
|
|
194
205
|
:result="currentResult"
|
|
@@ -199,93 +210,36 @@ onMounted(() => {
|
|
|
199
210
|
description="Set the parameters and run the analysis. Jobs continue independently if you switch tabs."
|
|
200
211
|
size="md"
|
|
201
212
|
/>
|
|
202
|
-
</
|
|
213
|
+
</SectionCard>
|
|
203
214
|
</template>
|
|
204
|
-
</
|
|
215
|
+
</AppContainer>
|
|
205
216
|
</PluginWorkspaceView>
|
|
206
217
|
</template>
|
|
207
218
|
|
|
208
219
|
<style scoped>
|
|
209
220
|
.mint-generated-workspace {
|
|
210
|
-
position: relative;
|
|
211
221
|
min-width: 0;
|
|
212
|
-
min-height:
|
|
213
|
-
|
|
214
|
-
background: var(--bg-primary);
|
|
222
|
+
min-height: 0;
|
|
223
|
+
overflow-y: auto;
|
|
215
224
|
}
|
|
216
225
|
|
|
217
226
|
.mint-generated-workspace__loading {
|
|
218
227
|
display: flex;
|
|
219
|
-
|
|
228
|
+
flex: 1;
|
|
220
229
|
align-items: center;
|
|
221
230
|
justify-content: center;
|
|
222
231
|
gap: 0.75rem;
|
|
223
232
|
color: var(--text-secondary);
|
|
224
233
|
}
|
|
225
234
|
|
|
226
|
-
.mint-generated-workspace__header {
|
|
227
|
-
display: flex;
|
|
228
|
-
align-items: flex-start;
|
|
229
|
-
justify-content: space-between;
|
|
230
|
-
gap: 1.5rem;
|
|
231
|
-
max-width: 76rem;
|
|
232
|
-
margin: 0 auto 1.25rem;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
.mint-generated-workspace__header h1 {
|
|
236
|
-
margin: 0;
|
|
237
|
-
color: var(--text-primary);
|
|
238
|
-
font-size: clamp(1.35rem, 2vw, 1.8rem);
|
|
239
|
-
line-height: 1.2;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
.mint-generated-workspace__header p:not(.mint-generated-workspace__eyebrow) {
|
|
243
|
-
margin: 0.4rem 0 0;
|
|
244
|
-
color: var(--text-secondary);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
.mint-generated-workspace__eyebrow {
|
|
248
|
-
margin: 0 0 0.35rem;
|
|
249
|
-
color: var(--text-secondary);
|
|
250
|
-
font-size: 0.72rem;
|
|
251
|
-
font-weight: 700;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
235
|
.mint-generated-workspace__resources {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
justify-content: flex-end;
|
|
258
|
-
gap: 0.4rem;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
.mint-generated-workspace__resources span {
|
|
262
|
-
padding: 0.3rem 0.55rem;
|
|
263
|
-
border: 1px solid var(--border-color);
|
|
264
|
-
border-radius: 999px;
|
|
265
|
-
background: var(--bg-secondary);
|
|
266
|
-
color: var(--text-secondary);
|
|
236
|
+
color: var(--text-muted);
|
|
237
|
+
font-family: var(--font-mono);
|
|
267
238
|
font-size: 0.75rem;
|
|
268
|
-
font-weight: 600;
|
|
269
239
|
}
|
|
270
240
|
|
|
271
|
-
.mint-generated-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
margin: 0 auto;
|
|
275
|
-
padding: clamp(1rem, 2vw, 1.75rem);
|
|
276
|
-
border: 1px solid var(--border-color);
|
|
277
|
-
border-radius: 1rem;
|
|
278
|
-
background: var(--bg-secondary);
|
|
279
|
-
box-shadow: var(--shadow-sm);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
@media (max-width: 640px) {
|
|
283
|
-
.mint-generated-workspace__header {
|
|
284
|
-
flex-direction: column;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
.mint-generated-workspace__resources {
|
|
288
|
-
justify-content: flex-start;
|
|
289
|
-
}
|
|
241
|
+
.mint-generated-workspace__description {
|
|
242
|
+
margin: 0 0 var(--mint-gutter);
|
|
243
|
+
color: var(--text-secondary);
|
|
290
244
|
}
|
|
291
245
|
</style>
|
|
@@ -133,6 +133,7 @@ function humanize(value: string): string {
|
|
|
133
133
|
padding: 0.75rem 1rem;
|
|
134
134
|
border-radius: var(--radius-md);
|
|
135
135
|
background: var(--bg-tertiary);
|
|
136
|
+
font-family: var(--font-mono);
|
|
136
137
|
font-size: 0.8125rem;
|
|
137
138
|
line-height: 1.55;
|
|
138
139
|
white-space: pre-wrap;
|
|
@@ -152,7 +153,7 @@ function humanize(value: string): string {
|
|
|
152
153
|
gap: 1rem;
|
|
153
154
|
padding: 1rem;
|
|
154
155
|
border: 1px solid var(--border-color);
|
|
155
|
-
border-radius:
|
|
156
|
+
border-radius: var(--radius-lg);
|
|
156
157
|
color: var(--color-primary);
|
|
157
158
|
text-decoration: none;
|
|
158
159
|
}
|
|
@@ -105,11 +105,11 @@ function formatValue(value: string | number | boolean | null): string {
|
|
|
105
105
|
</template>
|
|
106
106
|
|
|
107
107
|
<style scoped>
|
|
108
|
-
.mint-status-panels__tabs { padding: 0
|
|
109
|
-
.mint-status-panels__body { padding: 0.75rem
|
|
108
|
+
.mint-status-panels__tabs { padding: 0 var(--mint-section-card-inset); }
|
|
109
|
+
.mint-status-panels__body { padding: 0.75rem var(--mint-section-card-inset); }
|
|
110
110
|
.mint-status-panels__bar { display: flex; flex-direction: column; gap: 0.5rem; }
|
|
111
111
|
.mint-status-panels__series { align-self: flex-end; }
|
|
112
|
-
.mint-status-panels__kv { margin: 0; padding: 0.25rem
|
|
112
|
+
.mint-status-panels__kv { margin: 0; padding: 0.25rem var(--mint-section-card-inset) 0.5rem; }
|
|
113
113
|
.mint-status-panels__kv-row {
|
|
114
114
|
display: flex;
|
|
115
115
|
align-items: baseline;
|
|
@@ -52,8 +52,8 @@ function initState() {
|
|
|
52
52
|
<HstText v-model="state.icon" title="icon" />
|
|
53
53
|
<HstSelect v-model="state.size" title="size" :options="['sm', 'md', 'topbar', 'lg']" />
|
|
54
54
|
<HstSelect v-model="state.variant" title="variant" :options="['solid', 'tinted']" />
|
|
55
|
-
<HstSelect v-model="state.tone" title="tone (deprecated, no effect; removed in
|
|
56
|
-
<HstText v-model="state.color" title="color (deprecated, no effect; removed in
|
|
55
|
+
<HstSelect v-model="state.tone" title="tone (deprecated, no effect; removed in 2.0)" :options="['', 'neutral', 'primary', 'cta', 'success', 'warning', 'error', 'info']" />
|
|
56
|
+
<HstText v-model="state.color" title="color (deprecated, no effect; removed in 2.0)" />
|
|
57
57
|
</template>
|
|
58
58
|
</Variant>
|
|
59
59
|
|
|
@@ -18,9 +18,9 @@ interface Props {
|
|
|
18
18
|
icon?: string
|
|
19
19
|
size?: 'sm' | 'md' | 'topbar' | 'lg'
|
|
20
20
|
variant?: 'solid' | 'tinted'
|
|
21
|
-
/** @deprecated v2 design: the tile is always the neutral accent-soft tile, so the tone has no visible effect. Removed in
|
|
21
|
+
/** @deprecated v2 design: the tile is always the neutral accent-soft tile, so the tone has no visible effect. Removed in 2.0. */
|
|
22
22
|
tone?: Tone
|
|
23
|
-
/** @deprecated v2 design: the tile is always the neutral accent-soft tile, so the color has no visible effect. Removed in
|
|
23
|
+
/** @deprecated v2 design: the tile is always the neutral accent-soft tile, so the color has no visible effect. Removed in 2.0. */
|
|
24
24
|
color?: string
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/** One-card page region from the MINT 1.1 card system: 12px radius, the
|
|
3
3
|
* single --card-shadow token, a header strip (icon chip, title, mono count,
|
|
4
4
|
* actions), and hairline-separated content — no card-in-card. */
|
|
5
|
-
import { computed } from 'vue'
|
|
5
|
+
import { computed, useId } from 'vue'
|
|
6
6
|
import type { SectionCardAccent, SectionCardDivider } from '../types'
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
@@ -23,6 +23,8 @@ interface Props {
|
|
|
23
23
|
* definite height (a flex or grid track).
|
|
24
24
|
*/
|
|
25
25
|
fill?: boolean
|
|
26
|
+
/** The title toggles the body; bind `v-model:collapsed` to control or keep the state. */
|
|
27
|
+
collapsible?: boolean
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -32,8 +34,14 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
32
34
|
uppercase: false,
|
|
33
35
|
divider: 'strong',
|
|
34
36
|
fill: false,
|
|
37
|
+
collapsible: false,
|
|
35
38
|
})
|
|
36
39
|
|
|
40
|
+
const collapsed = defineModel<boolean>('collapsed', { default: false })
|
|
41
|
+
const bodyId = useId()
|
|
42
|
+
/** `collapsed` hides content only on a card that also shows the toggle to open it again. */
|
|
43
|
+
const hidden = computed(() => props.collapsible && collapsed.value)
|
|
44
|
+
|
|
37
45
|
const iconPaths = computed(() =>
|
|
38
46
|
props.icon === undefined ? [] : Array.isArray(props.icon) ? props.icon : [props.icon],
|
|
39
47
|
)
|
|
@@ -41,6 +49,7 @@ const iconPaths = computed(() =>
|
|
|
41
49
|
const headerClasses = computed(() => [
|
|
42
50
|
'mint-section-card__header',
|
|
43
51
|
props.divider === 'light' ? 'mint-section-card__header--divider-light' : '',
|
|
52
|
+
hidden.value ? 'mint-section-card__header--collapsed' : '',
|
|
44
53
|
])
|
|
45
54
|
|
|
46
55
|
const titleClasses = computed(() => [
|
|
@@ -51,6 +60,7 @@ const titleClasses = computed(() => [
|
|
|
51
60
|
const cardClasses = computed(() => [
|
|
52
61
|
'mint-section-card',
|
|
53
62
|
props.fill ? 'mint-section-card--fill' : '',
|
|
63
|
+
hidden.value ? 'mint-section-card--collapsed' : '',
|
|
54
64
|
])
|
|
55
65
|
</script>
|
|
56
66
|
|
|
@@ -74,20 +84,46 @@ const cardClasses = computed(() => [
|
|
|
74
84
|
<path v-for="(d, i) in iconPaths" :key="i" :d="d" />
|
|
75
85
|
</svg>
|
|
76
86
|
</span>
|
|
77
|
-
|
|
78
|
-
<
|
|
87
|
+
<!-- Disclosure pattern: the button sits inside the heading, never the reverse. -->
|
|
88
|
+
<h2 :class="titleClasses">
|
|
89
|
+
<button
|
|
90
|
+
v-if="collapsible"
|
|
91
|
+
type="button"
|
|
92
|
+
class="mint-section-card__toggle"
|
|
93
|
+
:aria-expanded="!collapsed"
|
|
94
|
+
:aria-controls="bodyId"
|
|
95
|
+
@click="collapsed = !collapsed"
|
|
96
|
+
>
|
|
97
|
+
{{ title }}
|
|
98
|
+
<span v-if="count !== undefined" class="mint-section-card__count">{{ count }}</span>
|
|
99
|
+
<svg
|
|
100
|
+
class="mint-section-card__chevron"
|
|
101
|
+
viewBox="0 0 24 24"
|
|
102
|
+
fill="none"
|
|
103
|
+
stroke="currentColor"
|
|
104
|
+
stroke-width="2"
|
|
105
|
+
stroke-linecap="round"
|
|
106
|
+
stroke-linejoin="round"
|
|
107
|
+
aria-hidden="true"
|
|
108
|
+
>
|
|
109
|
+
<path d="m6 9 6 6 6-6" />
|
|
110
|
+
</svg>
|
|
111
|
+
</button>
|
|
112
|
+
<template v-else>{{ title }}</template>
|
|
113
|
+
</h2>
|
|
114
|
+
<span v-if="count !== undefined && !collapsible" class="mint-section-card__count">{{ count }}</span>
|
|
79
115
|
<div class="mint-section-card__spacer"></div>
|
|
80
116
|
<slot name="actions" />
|
|
81
117
|
</div>
|
|
82
118
|
<!-- Controls belong to the card's chrome, so they sit outside the body and
|
|
83
119
|
stay put while the body scrolls. -->
|
|
84
|
-
<div v-if="$slots.toolbar" class="mint-section-card__toolbar">
|
|
120
|
+
<div v-if="$slots.toolbar" v-show="!hidden" class="mint-section-card__toolbar">
|
|
85
121
|
<slot name="toolbar" />
|
|
86
122
|
</div>
|
|
87
|
-
<div class="mint-section-card__body">
|
|
123
|
+
<div :id="bodyId" v-show="!hidden" class="mint-section-card__body">
|
|
88
124
|
<slot />
|
|
89
125
|
</div>
|
|
90
|
-
<div v-if="$slots.footer" class="mint-section-card__footer">
|
|
126
|
+
<div v-if="$slots.footer" v-show="!hidden" class="mint-section-card__footer">
|
|
91
127
|
<slot name="footer" />
|
|
92
128
|
</div>
|
|
93
129
|
</section>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
|
|
7
7
|
import { autoUpdate, computePosition, offset, shift } from '@floating-ui/dom'
|
|
8
|
-
import { Check, CircleHelp, Play } from 'lucide-vue-next'
|
|
8
|
+
import { Check, CircleHelp, Play, X } from 'lucide-vue-next'
|
|
9
9
|
import { useEventListener } from '../../composables/useEventListener'
|
|
10
10
|
import type { OnboardingState } from '../../composables/useOnboarding'
|
|
11
11
|
import OnboardingLayerInternal from './OnboardingLayerInternal.vue'
|
|
@@ -50,6 +50,12 @@ watch(open, async (isOpen) => {
|
|
|
50
50
|
})
|
|
51
51
|
onBeforeUnmount(() => stopAutoUpdate?.())
|
|
52
52
|
|
|
53
|
+
// The close button removes itself, so focus moves on to Help instead of the page.
|
|
54
|
+
function hideChecklist() {
|
|
55
|
+
props.state.setChecklistHidden(true)
|
|
56
|
+
nextTick(() => helpButton.value?.focus())
|
|
57
|
+
}
|
|
58
|
+
|
|
53
59
|
function run(action: () => void) {
|
|
54
60
|
open.value = false
|
|
55
61
|
action()
|
|
@@ -66,19 +72,29 @@ useEventListener(document, 'keydown', (event) => {
|
|
|
66
72
|
|
|
67
73
|
<template>
|
|
68
74
|
<div v-if="hasContent" ref="root" class="mint-onboarding-help">
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
75
|
+
<span v-if="state.checklistVisible.value" class="mint-onboarding-help__chip-group">
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
class="mint-onboarding-help__chip"
|
|
79
|
+
:class="{ 'mint-onboarding-help__chip--open': open }"
|
|
80
|
+
data-onboarding="topbar-checklist"
|
|
81
|
+
:aria-expanded="open"
|
|
82
|
+
@click="toggle"
|
|
83
|
+
>
|
|
84
|
+
Getting started
|
|
85
|
+
<span class="mint-onboarding-help__count">{{ state.tasksDone.value }}/{{ state.tasks.value.length }}</span>
|
|
86
|
+
<span class="mint-onboarding-help__bar" aria-hidden="true"><span :style="{ width: `${progress}%` }" /></span>
|
|
87
|
+
</button>
|
|
88
|
+
<button
|
|
89
|
+
type="button"
|
|
90
|
+
class="mint-onboarding-help__chip-close"
|
|
91
|
+
aria-label="Hide checklist"
|
|
92
|
+
title="Hide checklist (Help can show it again)"
|
|
93
|
+
@click="hideChecklist"
|
|
94
|
+
>
|
|
95
|
+
<X :size="12" aria-hidden="true" />
|
|
96
|
+
</button>
|
|
97
|
+
</span>
|
|
82
98
|
<button
|
|
83
99
|
ref="helpButton"
|
|
84
100
|
type="button"
|