@morscherlab/mint-sdk 1.4.0-beta.9 → 1.4.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.
- package/README.md +14 -3
- package/dist/{AlertBox-B2Bgr3ei.js → AlertBox-DwgNTEqf.js} +3 -3
- package/dist/{AlertBox-B2Bgr3ei.js.map → AlertBox-DwgNTEqf.js.map} +1 -1
- package/dist/{AppSidebar-Dq9W1jE2.js → AppSidebar-CZZaxni0.js} +1103 -144
- package/dist/AppSidebar-CZZaxni0.js.map +1 -0
- package/dist/{ConfirmDialog-nbJj51is.js → ConfirmDialog-CjWoxAD1.js} +2 -2
- package/dist/{ConfirmDialog-nbJj51is.js.map → ConfirmDialog-CjWoxAD1.js.map} +1 -1
- package/dist/{ExperimentPopover-5T1fAq2L.js → ExperimentPopover-DujiuQIO.js} +5 -5
- package/dist/{ExperimentPopover-5T1fAq2L.js.map → ExperimentPopover-DujiuQIO.js.map} +1 -1
- package/dist/{ExperimentSelectorModal-65peG4Up.js → ExperimentSelectorModal-B4j4eUOJ.js} +114 -98
- package/dist/ExperimentSelectorModal-B4j4eUOJ.js.map +1 -0
- package/dist/{SegmentedControl-BtnnjWzs.js → SegmentedControl-DyUPH3I5.js} +2 -2
- package/dist/{SegmentedControl-BtnnjWzs.js.map → SegmentedControl-DyUPH3I5.js.map} +1 -1
- package/dist/{SettingsModal-483nyI6b.js → SettingsModal-D2MOOtuO.js} +26 -25
- package/dist/SettingsModal-D2MOOtuO.js.map +1 -0
- package/dist/__stories__/experiment-helpers.d.ts +10 -0
- package/dist/__tests__/components/AppTopBarOnboarding.test.d.ts +1 -0
- package/dist/__tests__/components/FloatingPanelsInModal.test.d.ts +1 -0
- package/dist/__tests__/composables/useFloatingPanel.test.d.ts +1 -0
- package/dist/__tests__/composables/useOnboarding.test.d.ts +1 -0
- package/dist/__tests__/styles/paletteTokens.test.d.ts +1 -0
- package/dist/__tests__/styles/v2AuditRules.test.d.ts +1 -0
- package/dist/components/AppTopBar.vue.d.ts +20 -5
- package/dist/components/BaseButton.vue.d.ts +4 -2
- package/dist/components/ExperimentSelectorModal.vue.d.ts +15 -1
- package/dist/components/JobsStatusTray.vue.d.ts +15 -4
- package/dist/components/PluginWorkspaceView.props.d.ts +3 -0
- package/dist/components/PluginWorkspaceView.vue.d.ts +3 -0
- package/dist/components/SmartGroupFieldRecipe.vue.d.ts +3 -3
- package/dist/components/internal/AppTopBarOnboardingInternal.vue.d.ts +7 -0
- package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -1
- package/dist/components/internal/AutoGroupModalInternal.vue.d.ts +2 -2
- package/dist/components/internal/OnboardingLayerInternal.vue.d.ts +7 -0
- package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +8 -0
- package/dist/composables/pluginWorkspaceModel.d.ts +2 -2
- package/dist/composables/useFileImport.d.ts +7 -0
- package/dist/composables/useFloatingPanel.d.ts +29 -0
- package/dist/composables/useJobCenter.d.ts +41 -3
- package/dist/composables/useOnboarding.d.ts +119 -0
- package/dist/{controlSchemaModel-B8CTXagP.js → controlSchemaModel-Bsg0t5Ab.js} +2 -2
- package/dist/{controlSchemaModel-B8CTXagP.js.map → controlSchemaModel-Bsg0t5Ab.js.map} +1 -1
- package/dist/{data-CgILxDLW.js → data-Boeh4uPI.js} +13 -12
- package/dist/{data-CgILxDLW.js.map → data-Boeh4uPI.js.map} +1 -1
- package/dist/data.d.ts +1 -1
- package/dist/data.js +9 -9
- package/dist/{experiment-43OJtH7R.js → experiment-DyO54FQB.js} +25 -61
- package/dist/experiment-DyO54FQB.js.map +1 -0
- package/dist/{experimentStatus-Cqp6qNrR.js → experimentStatus-DCn2QKG7.js} +2 -2
- package/dist/{experimentStatus-Cqp6qNrR.js.map → experimentStatus-DCn2QKG7.js.map} +1 -1
- package/dist/index.js +20 -20
- package/dist/{install-BtLWTk6H.js → install-BEHp7Mo-.js} +15 -15
- package/dist/{install-BtLWTk6H.js.map → install-BEHp7Mo-.js.map} +1 -1
- package/dist/install.js +1 -1
- package/dist/{lab-MHzp-_J8.js → lab-D61eaX6f.js} +68 -61
- package/dist/{lab-MHzp-_J8.js.map → lab-D61eaX6f.js.map} +1 -1
- package/dist/lab.js +4 -4
- package/dist/{records-B6gpM_3e.js → records-D4aQXyZu.js} +193 -133
- package/dist/records-D4aQXyZu.js.map +1 -0
- package/dist/{runtime-7vfl_bI3.js → runtime-q-R0CNLh.js} +400 -33
- package/dist/runtime-q-R0CNLh.js.map +1 -0
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +7 -7
- package/dist/{shell-Cs9OJMnm.js → shell-6CGFvUUw.js} +10 -5
- package/dist/{shell-Cs9OJMnm.js.map → shell-6CGFvUUw.js.map} +1 -1
- package/dist/shell.d.ts +1 -0
- package/dist/shell.js +6 -6
- package/dist/stores/index.d.ts +1 -1
- package/dist/stores/index.js +2 -2
- package/dist/stores/settings.d.ts +4 -12
- package/dist/styles.css +1191 -479
- package/dist/types/components.d.ts +2 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/jobs.d.ts +9 -0
- package/dist/types/platform.d.ts +1 -1
- package/dist/{useDropdownState-8yWMu-7_.js → useDropdownState-uFIz6Yv8.js} +16 -4
- package/dist/useDropdownState-uFIz6Yv8.js.map +1 -0
- package/dist/{useExperimentSamples-DHnxKFAF.js → useExperimentSamples-QEFoq4tf.js} +48 -6
- package/dist/useExperimentSamples-QEFoq4tf.js.map +1 -0
- package/dist/{useListReorder-BitdUhxn.js → useListReorder-DsuzwWGS.js} +122 -172
- package/dist/useListReorder-DsuzwWGS.js.map +1 -0
- package/dist/{useRackEditor-DiwCt77M.js → useRackEditor-KpZ4iJ6o.js} +22 -32
- package/dist/useRackEditor-KpZ4iJ6o.js.map +1 -0
- package/dist/useRealtime-B58Lo-Tm.js +508 -0
- package/dist/useRealtime-B58Lo-Tm.js.map +1 -0
- package/dist/{useRequest-D506eHi8.js → useRequest-BuDmW8Jc.js} +8 -37
- package/dist/useRequest-BuDmW8Jc.js.map +1 -0
- package/package.json +3 -2
- package/src/__stories__/experiment-helpers.ts +3 -3
- package/src/__tests__/components/ActionMenu.test.ts +2 -2
- package/src/__tests__/components/AppTopBar.test.ts +206 -1
- package/src/__tests__/components/AppTopBarOnboarding.test.ts +160 -0
- package/src/__tests__/components/AppTopBarPageSelector.test.ts +125 -9
- package/src/__tests__/components/BaseButton.test.ts +19 -0
- package/src/__tests__/components/BaseModal.test.ts +13 -0
- package/src/__tests__/components/ExperimentSelectorModal.test.ts +34 -1
- package/src/__tests__/components/FloatingPanelsInModal.test.ts +94 -0
- package/src/__tests__/components/GeneratedPluginView.test.ts +1 -1
- package/src/__tests__/components/JobsStatusTray.test.ts +373 -71
- package/src/__tests__/components/PluginWorkspaceView.test.ts +63 -1
- package/src/__tests__/components/SampleSelector.test.ts +20 -0
- package/src/__tests__/components/SettingsModal.test.ts +9 -2
- package/src/__tests__/composables/useFileImport.test.ts +36 -0
- package/src/__tests__/composables/useFloatingPanel.test.ts +68 -0
- package/src/__tests__/composables/useJobCenter.test.ts +262 -3
- package/src/__tests__/composables/useOnboarding.test.ts +317 -0
- package/src/__tests__/docs/frontendDocsCatalog.test.ts +4 -3
- package/src/__tests__/fixtures/table-import/cells.xlsx +0 -0
- package/src/__tests__/fixtures/table-import/samples.xlsx +0 -0
- package/src/__tests__/stores/settings.test.ts +21 -21
- package/src/__tests__/styles/cssTokens.test.ts +4 -1
- package/src/__tests__/styles/menusShellRefresh.test.ts +8 -2
- package/src/__tests__/styles/paletteTokens.test.ts +44 -0
- package/src/__tests__/styles/v2AuditRules.test.ts +104 -0
- package/src/components/ActionMenu.vue +6 -37
- package/src/components/AppTopBar.onboarding.story.vue +69 -0
- package/src/components/AppTopBar.story.vue +59 -4
- package/src/components/AppTopBar.vue +132 -10
- package/src/components/BaseButton.vue +16 -2
- package/src/components/BaseModal.vue +2 -1
- package/src/components/ExperimentSelectorModal.story.vue +38 -0
- package/src/components/ExperimentSelectorModal.vue +20 -1
- package/src/components/GeneratedPluginView.vue +4 -9
- package/src/components/GeneratedResultRenderer.vue +3 -4
- package/src/components/JobsStatusTray.story.vue +97 -43
- package/src/components/JobsStatusTray.vue +287 -290
- package/src/components/LayoutResizeHandle.story.vue +65 -4
- package/src/components/PluginWorkspaceView.props.ts +3 -0
- package/src/components/PluginWorkspaceView.vue +17 -0
- package/src/components/SampleSelector.story.vue +21 -6
- package/src/components/SampleSelector.vue +10 -3
- package/src/components/SectionCard.story.vue +33 -0
- package/src/components/SettingsModal.vue +7 -12
- package/src/components/Tooltip.vue +9 -32
- package/src/components/internal/AppTopBarOnboardingInternal.vue +172 -0
- package/src/components/internal/AppTopBarPageSelectorInternal.vue +140 -72
- package/src/components/internal/DatePickerDateInternal.vue +7 -45
- package/src/components/internal/DatePickerDateTimeInternal.vue +7 -1
- package/src/components/internal/DatePickerTimeInternal.vue +8 -1
- package/src/components/internal/NumberUnitInputInternal.vue +4 -18
- package/src/components/internal/OnboardingLayerInternal.vue +173 -0
- package/src/components/internal/SelectMultipleInternal.vue +6 -1
- package/src/components/internal/SelectSearchableInternal.vue +6 -1
- package/src/components/internal/WellEditPopupInternal.vue +11 -22
- package/src/components/internal/WorkspaceShellInternal.vue +12 -0
- package/src/composables/pluginWorkspaceModel.ts +2 -2
- package/src/composables/useFileImport.ts +51 -1
- package/src/composables/useFloatingPanel.ts +107 -0
- package/src/composables/useJobCenter.ts +188 -6
- package/src/composables/useOnboarding.ts +465 -0
- package/src/composables/usePluginWorkspace.ts +1 -1
- package/src/data.ts +1 -0
- package/src/runtime.ts +2 -1
- package/src/shell.ts +17 -0
- package/src/stores/index.ts +0 -2
- package/src/stores/settings.ts +20 -50
- package/src/styles/components/action-menu.css +1 -1
- package/src/styles/components/app-layout.css +6 -3
- package/src/styles/components/app-page-selector.css +120 -45
- package/src/styles/components/app-top-bar.css +165 -3
- package/src/styles/components/button.css +8 -0
- package/src/styles/components/dataframe.css +1 -1
- package/src/styles/components/date-picker.css +4 -2
- package/src/styles/components/datetime-picker.css +4 -4
- package/src/styles/components/experiment-selector-modal.css +13 -0
- package/src/styles/components/jobs-status-tray.css +134 -296
- package/src/styles/components/layout-resize-handle.css +59 -16
- package/src/styles/components/modal.css +3 -3
- package/src/styles/components/number-input.css +1 -1
- package/src/styles/components/onboarding.css +368 -0
- package/src/styles/components/progress-bar.css +3 -3
- package/src/styles/components/sample-selector.css +43 -0
- package/src/styles/components/searchable-select.css +3 -7
- package/src/styles/components/section-card.css +30 -6
- package/src/styles/components/settings-modal.css +7 -2
- package/src/styles/components/time-picker.css +3 -6
- package/src/styles/components/toast.css +1 -1
- package/src/styles/components/tooltip.css +2 -2
- package/src/styles/components/unit-input.css +1 -1
- package/src/styles/components/well-edit-popup.css +2 -2
- package/src/styles/index.css +1 -0
- package/src/styles/variables.css +153 -15
- package/src/types/components.ts +2 -0
- package/src/types/index.ts +1 -0
- package/src/types/jobs.ts +10 -0
- package/src/types/platform.ts +1 -1
- package/dist/AppSidebar-Dq9W1jE2.js.map +0 -1
- package/dist/ExperimentSelectorModal-65peG4Up.js.map +0 -1
- package/dist/SettingsModal-483nyI6b.js.map +0 -1
- package/dist/experiment-43OJtH7R.js.map +0 -1
- package/dist/records-B6gpM_3e.js.map +0 -1
- package/dist/runtime-7vfl_bI3.js.map +0 -1
- package/dist/useDropdownState-8yWMu-7_.js.map +0 -1
- package/dist/useExperimentSamples-DHnxKFAF.js.map +0 -1
- package/dist/useListReorder-BitdUhxn.js.map +0 -1
- package/dist/useRackEditor-DiwCt77M.js.map +0 -1
- package/dist/useRealtime-C3iy9QkU.js +0 -699
- package/dist/useRealtime-C3iy9QkU.js.map +0 -1
- package/dist/useRequest-D506eHi8.js.map +0 -1
|
@@ -3,15 +3,20 @@ import { ref } from 'vue'
|
|
|
3
3
|
import LayoutResizeHandle from './LayoutResizeHandle.vue'
|
|
4
4
|
|
|
5
5
|
const width = ref(320)
|
|
6
|
+
const gapWidth = ref(320)
|
|
7
|
+
const edgeWidth = ref(280)
|
|
8
|
+
const height = ref(50)
|
|
9
|
+
|
|
10
|
+
const panel = 'background: var(--bg-card); border: 1px solid var(--border-color); border-radius: var(--radius-lg); padding: 0.75rem;'
|
|
11
|
+
const caption = 'margin: 0 0 0.5rem; font: 0.75rem var(--font-mono); color: var(--text-secondary);'
|
|
6
12
|
</script>
|
|
7
13
|
|
|
8
14
|
<template>
|
|
9
15
|
<Story title="Layout/LayoutResizeHandle">
|
|
10
16
|
<Variant title="Keyboard splitter">
|
|
17
|
+
<!-- Directly between two panels, the handle is the gutter. -->
|
|
11
18
|
<div style="display: flex; height: 16rem; padding: 2rem;">
|
|
12
|
-
<div :style="{ width:
|
|
13
|
-
Panel width: {{ width }}px
|
|
14
|
-
</div>
|
|
19
|
+
<div :style="`${panel} width: ${width}px;`">Panel width: {{ width }}px</div>
|
|
15
20
|
<LayoutResizeHandle
|
|
16
21
|
v-model="width"
|
|
17
22
|
orientation="vertical"
|
|
@@ -19,7 +24,63 @@ const width = ref(320)
|
|
|
19
24
|
:min="240"
|
|
20
25
|
:max="480"
|
|
21
26
|
/>
|
|
22
|
-
<div style="flex: 1
|
|
27
|
+
<div :style="`${panel} flex: 1;`" />
|
|
28
|
+
</div>
|
|
29
|
+
</Variant>
|
|
30
|
+
|
|
31
|
+
<Variant title="Gaps and edge">
|
|
32
|
+
<div style="display: grid; gap: 1.5rem; padding: 2rem;">
|
|
33
|
+
<div data-gap="flex-8">
|
|
34
|
+
<p :style="caption">flex, 8px gutter (compact), handle between the panels</p>
|
|
35
|
+
<div style="display: flex; height: 8rem; --mint-gutter: 0.5rem;">
|
|
36
|
+
<div :style="`${panel} width: ${gapWidth}px;`">Left panel</div>
|
|
37
|
+
<LayoutResizeHandle v-model="gapWidth" orientation="vertical" label="Resize left panel (8px)" :min="200" :max="480" />
|
|
38
|
+
<div :style="`${panel} flex: 1;`">Right panel</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
<div data-gap="wrapper-12">
|
|
42
|
+
<p :style="caption">flex, 12px gutter, handle in a gutter-wide wrapper</p>
|
|
43
|
+
<div style="display: flex; height: 8rem; --mint-gutter: 0.75rem;">
|
|
44
|
+
<div :style="`${panel} width: ${gapWidth}px;`">Left panel</div>
|
|
45
|
+
<div style="display: flex; justify-content: center; flex-shrink: 0; width: var(--mint-gutter);">
|
|
46
|
+
<LayoutResizeHandle v-model="gapWidth" orientation="vertical" label="Resize left panel (12px)" :min="200" :max="480" />
|
|
47
|
+
</div>
|
|
48
|
+
<div :style="`${panel} flex: 1;`">Right panel</div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
<div data-gap="grid-8">
|
|
52
|
+
<p :style="caption">grid, 8px track</p>
|
|
53
|
+
<div :style="`display: grid; grid-template-columns: ${gapWidth}px 0.5rem 1fr; height: 8rem;`">
|
|
54
|
+
<div :style="panel">Left panel</div>
|
|
55
|
+
<LayoutResizeHandle
|
|
56
|
+
v-model="gapWidth"
|
|
57
|
+
orientation="vertical"
|
|
58
|
+
label="Resize left panel (grid)"
|
|
59
|
+
:min="200"
|
|
60
|
+
:max="480"
|
|
61
|
+
style="--mint-layout-resize-gap: 0.5rem;"
|
|
62
|
+
/>
|
|
63
|
+
<div :style="panel">Right panel</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
<div data-gap="edge">
|
|
67
|
+
<p :style="caption">edge="right", 12px gutter</p>
|
|
68
|
+
<div style="display: flex; gap: var(--mint-gutter); height: 8rem; --mint-gutter: 0.75rem;">
|
|
69
|
+
<aside :style="`${panel} position: relative; width: ${edgeWidth}px;`">
|
|
70
|
+
Sidebar
|
|
71
|
+
<LayoutResizeHandle v-model="edgeWidth" orientation="vertical" edge="right" label="Resize sidebar" :min="200" :max="480" />
|
|
72
|
+
</aside>
|
|
73
|
+
<div :style="`${panel} flex: 1;`">Main</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
<div data-gap="horizontal">
|
|
77
|
+
<p :style="caption">horizontal, 8px gutter</p>
|
|
78
|
+
<div style="display: flex; flex-direction: column; height: 14rem; --mint-gutter: 0.5rem;">
|
|
79
|
+
<div :style="`${panel} flex: 0 0 ${height}%;`">Top panel</div>
|
|
80
|
+
<LayoutResizeHandle v-model="height" orientation="horizontal" label="Resize top panel" :min="20" :max="80" :step="1" />
|
|
81
|
+
<div :style="`${panel} flex: 1;`">Bottom panel</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
23
84
|
</div>
|
|
24
85
|
</Variant>
|
|
25
86
|
</Story>
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
PillNavOption,
|
|
5
5
|
PluginWorkspaceViewSpec,
|
|
6
6
|
} from '../types'
|
|
7
|
+
import type { JobState } from '../types/jobs'
|
|
7
8
|
import type {
|
|
8
9
|
ControlComponentBinding,
|
|
9
10
|
ControlComponentBindingsById,
|
|
@@ -46,6 +47,8 @@ export interface PluginWorkspaceViewEmits {
|
|
|
46
47
|
'section-action': [sectionId: string, actionId: string]
|
|
47
48
|
'form-submit': [sectionId: string, values: Record<string, unknown>]
|
|
48
49
|
'form-cancel': [sectionId: string]
|
|
50
|
+
/** A job row in the jobs tray, or the just-completed job on the top-bar chip, was chosen. */
|
|
51
|
+
'job-select': [job: JobState]
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
/** Typed `emit` function shape produced by `defineEmits<PluginWorkspaceViewEmits>()`. */
|
|
@@ -20,9 +20,12 @@ import type {
|
|
|
20
20
|
PillNavItem,
|
|
21
21
|
PillNavOption,
|
|
22
22
|
} from '../types'
|
|
23
|
+
import type { JobState } from '../types/jobs'
|
|
23
24
|
import { useAppExperiment } from '../composables/useAppExperiment'
|
|
25
|
+
import { useJobCenter } from '../composables/useJobCenter'
|
|
24
26
|
import { usePluginWorkspace } from '../composables/usePluginWorkspace'
|
|
25
27
|
import AppToastContainer from './AppToastContainer.vue'
|
|
28
|
+
import JobsStatusTray from './JobsStatusTray.vue'
|
|
26
29
|
import WorkspaceControlsInternal from './internal/WorkspaceControlsInternal.vue'
|
|
27
30
|
import WorkspaceShellInternal from './internal/WorkspaceShellInternal.vue'
|
|
28
31
|
import WorkspaceSidebarInternal from './internal/WorkspaceSidebarInternal.vue'
|
|
@@ -76,6 +79,14 @@ if (props.spec.experiment) {
|
|
|
76
79
|
})
|
|
77
80
|
}
|
|
78
81
|
|
|
82
|
+
// The job center is setup-time structure too: no `jobs`, no jobs control.
|
|
83
|
+
const jobs = props.spec.jobs
|
|
84
|
+
const jobCenter = jobs ? useJobCenter({ source: jobs }) : null
|
|
85
|
+
|
|
86
|
+
function handleJobSelect(job: JobState) {
|
|
87
|
+
emit('job-select', job)
|
|
88
|
+
}
|
|
89
|
+
|
|
79
90
|
const {
|
|
80
91
|
shell,
|
|
81
92
|
sidebar,
|
|
@@ -105,6 +116,11 @@ function handleFormCancel(sectionId: string) {
|
|
|
105
116
|
<template>
|
|
106
117
|
<WorkspaceShellInternal
|
|
107
118
|
:shell="shell"
|
|
119
|
+
:job="jobCenter ? jobCenter.topBarJob.value : undefined"
|
|
120
|
+
:jobs-done="jobCenter?.completedCount.value"
|
|
121
|
+
:jobs-open="jobCenter?.isOpen.value"
|
|
122
|
+
:jobs-panel-id="jobCenter?.panelId"
|
|
123
|
+
@job-click="jobCenter?.handleTopBarClick(handleJobSelect)"
|
|
108
124
|
@pill-select="handlePillSelect"
|
|
109
125
|
@pill-option-select="(option: PillNavOption, item: PillNavItem) => emit('pill-option-select', option, item)"
|
|
110
126
|
@settings-values-change="emit('settings-values-change', $event)"
|
|
@@ -158,6 +174,7 @@ function handleFormCancel(sectionId: string) {
|
|
|
158
174
|
</template>
|
|
159
175
|
</WorkspaceControlsInternal>
|
|
160
176
|
<AppToastContainer v-if="spec.toasts !== false" />
|
|
177
|
+
<JobsStatusTray v-if="jobCenter" :center="jobCenter" @select="handleJobSelect" />
|
|
161
178
|
</template>
|
|
162
179
|
</WorkspaceShellInternal>
|
|
163
180
|
</template>
|
|
@@ -21,6 +21,13 @@ const mockGroups: SampleGroup[] = [
|
|
|
21
21
|
{ name: 'Vehicle', color: '#F59E0B', samples: ['Vehicle_Rep1', 'Vehicle_Rep2', 'Vehicle_Rep3'] },
|
|
22
22
|
]
|
|
23
23
|
|
|
24
|
+
// 1024 selected runs in two groups: the long count must not push the actions out.
|
|
25
|
+
const manyRuns = Array.from({ length: 1024 }, (_, i) => `Run_${String(i + 1).padStart(4, '0')}`)
|
|
26
|
+
const manyGroups: SampleGroup[] = [
|
|
27
|
+
{ name: 'Batch A', color: '#3B82F6', samples: manyRuns.slice(0, 512) },
|
|
28
|
+
{ name: 'Batch B', color: '#10B981', samples: manyRuns.slice(512) },
|
|
29
|
+
]
|
|
30
|
+
|
|
24
31
|
const manualSamples = [
|
|
25
32
|
'Pt001_TumorA_d7_rep1',
|
|
26
33
|
'Pt001_TumorA_d7_rep1_reseq2024',
|
|
@@ -136,12 +143,20 @@ function handleSmartGroup(result: AutoGroupResult, state: { groups: SampleGroup[
|
|
|
136
143
|
</Variant>
|
|
137
144
|
|
|
138
145
|
<Variant title="Narrow Header">
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
146
|
+
<!-- At 17.625rem of header content width and below, the grouping button shows only its icon and the count shortens. -->
|
|
147
|
+
<div style="display: flex; align-items: flex-start; gap: 2rem; padding: 2rem;">
|
|
148
|
+
<div v-for="width in [220, 300, 340]" :key="width" :data-width="width" :style="{ width: `${width}px` }">
|
|
149
|
+
<p style="margin: 0 0 0.5rem; font: 0.75rem var(--font-mono); color: var(--text-secondary);">{{ width }}px</p>
|
|
150
|
+
<SampleSelector
|
|
151
|
+
:model-value="['Control_Rep1']"
|
|
152
|
+
:samples="mockSamples"
|
|
153
|
+
:groups="mockGroups"
|
|
154
|
+
/>
|
|
155
|
+
</div>
|
|
156
|
+
<div data-width="320-long" style="width: 320px; height: 22rem;">
|
|
157
|
+
<p style="margin: 0 0 0.5rem; font: 0.75rem var(--font-mono); color: var(--text-secondary);">320px, 1024 selected</p>
|
|
158
|
+
<SampleSelector :model-value="manyRuns" :samples="manyRuns" :groups="manyGroups" />
|
|
159
|
+
</div>
|
|
145
160
|
</div>
|
|
146
161
|
</Variant>
|
|
147
162
|
|
|
@@ -256,6 +256,8 @@ function addNewGroup() {
|
|
|
256
256
|
newGroupName.value = ''
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
+
const groupingLabel = computed(() => (props.enableSmartGroup ? 'Smart group' : 'Group'))
|
|
260
|
+
|
|
259
261
|
/** Selected samples among the resolved list, for the select-all header. */
|
|
260
262
|
const selectedCount = computed(() => resolvedSamples.value.filter(s => props.modelValue.includes(s)).length)
|
|
261
263
|
|
|
@@ -271,16 +273,21 @@ defineExpose({ handleSmartGroupApply })
|
|
|
271
273
|
<div class="mint-sample-selector">
|
|
272
274
|
<div class="mint-sample-selector__header">
|
|
273
275
|
<span class="mint-sample-selector__title">Samples</span>
|
|
274
|
-
|
|
276
|
+
<!-- A narrow selector shows "N sel."; screen readers keep "N selected". -->
|
|
277
|
+
<span class="mint-sample-selector__selected">
|
|
278
|
+
<span class="mint-sample-selector__selected-full">{{ selectedCount }} selected</span>
|
|
279
|
+
<span class="mint-sample-selector__selected-short" aria-hidden="true">{{ selectedCount }} sel.</span>
|
|
280
|
+
</span>
|
|
275
281
|
<div v-if="!isPlaceholderState && enableGrouping" class="mint-sample-selector__header-actions">
|
|
276
282
|
<BaseButton
|
|
277
283
|
variant="ghost"
|
|
278
284
|
size="sm"
|
|
279
285
|
:icon="enableSmartGroup ? WandSparkles : Layers"
|
|
280
|
-
|
|
286
|
+
:title="groupingLabel"
|
|
287
|
+
class="mint-sample-selector__action-btn mint-sample-selector__group-btn"
|
|
281
288
|
@click="openGroupingModal(groupingActionMode)"
|
|
282
289
|
>
|
|
283
|
-
{{
|
|
290
|
+
{{ groupingLabel }}
|
|
284
291
|
</BaseButton>
|
|
285
292
|
<BaseButton
|
|
286
293
|
v-if="internalGroups.length > 0"
|
|
@@ -107,6 +107,39 @@ function initPlayground() {
|
|
|
107
107
|
</div>
|
|
108
108
|
</Variant>
|
|
109
109
|
|
|
110
|
+
<Variant title="Rows with lead">
|
|
111
|
+
<div style="padding: 2rem; max-width: 600px; display: grid; gap: 1rem;">
|
|
112
|
+
<SectionCard title="Experiments" count="3 total" :icon="flaskIcon" accent="experiment" uppercase>
|
|
113
|
+
<div class="mint-section-card__rows">
|
|
114
|
+
<a v-for="e in experiments" :key="e.code" href="#" class="mint-section-card__row">
|
|
115
|
+
<span class="mint-section-card__row-lead">
|
|
116
|
+
<span style="width: 0.375rem; height: 0.375rem; border-radius: 50%; background: var(--mint-success)" />
|
|
117
|
+
</span>
|
|
118
|
+
<span style="flex: 1; min-width: 0">
|
|
119
|
+
<span style="display: block; font-size: 0.8125rem; font-weight: 500; color: var(--text-primary)">{{ e.name }}</span>
|
|
120
|
+
<span style="display: block; font-family: var(--font-mono); font-size: 0.65625rem; color: var(--text-muted)">{{ e.code }}</span>
|
|
121
|
+
</span>
|
|
122
|
+
<span style="flex-shrink: 0; font-size: 0.71875rem; color: var(--text-muted)">{{ e.updated }}</span>
|
|
123
|
+
</a>
|
|
124
|
+
</div>
|
|
125
|
+
</SectionCard>
|
|
126
|
+
<SectionCard title="Projects" count="2 active" :icon="folderIcon" accent="project" uppercase>
|
|
127
|
+
<div class="mint-section-card__rows">
|
|
128
|
+
<a href="#" class="mint-section-card__row">
|
|
129
|
+
<span class="mint-section-card__row-lead" aria-hidden="true" />
|
|
130
|
+
<span style="flex: 1; font-size: 0.8125rem; font-weight: 500; color: var(--text-primary)">Hypoxia metabolic rewiring</span>
|
|
131
|
+
<span style="font-size: 0.71875rem; color: var(--text-muted)">12 experiments</span>
|
|
132
|
+
</a>
|
|
133
|
+
<a href="#" class="mint-section-card__row">
|
|
134
|
+
<span class="mint-section-card__row-lead" aria-hidden="true" />
|
|
135
|
+
<span style="flex: 1; font-size: 0.8125rem; font-weight: 500; color: var(--text-primary)">Serine one-carbon flux</span>
|
|
136
|
+
<span style="font-size: 0.71875rem; color: var(--text-muted)">7 experiments</span>
|
|
137
|
+
</a>
|
|
138
|
+
</div>
|
|
139
|
+
</SectionCard>
|
|
140
|
+
</div>
|
|
141
|
+
</Variant>
|
|
142
|
+
|
|
110
143
|
<Variant title="Detail card with footer">
|
|
111
144
|
<div style="padding: 2rem; max-width: 600px;">
|
|
112
145
|
<SectionCard title="Analysis artifacts" :count="4" divider="light">
|
|
@@ -358,13 +358,8 @@ const themeOptions: { value: ThemeMode; label: string; halves: ('light' | 'dark'
|
|
|
358
358
|
{ value: 'system', label: 'System', halves: ['light', 'dark'] },
|
|
359
359
|
]
|
|
360
360
|
|
|
361
|
-
/**
|
|
362
|
-
|
|
363
|
-
const [from, to] = colorPalettes[key].hues
|
|
364
|
-
const span = ((to - from + 540) % 360) - 180
|
|
365
|
-
const tone = key === 'pastel' ? '60% 72%' : '65% 48%'
|
|
366
|
-
return Array.from({ length: 5 }, (_, i) => `hsl(${Math.round(from + (span * i) / 4)} ${tone})`)
|
|
367
|
-
}
|
|
361
|
+
/** Accent tokens a palette swatch shows; `data-palette` on the strip resolves them per palette. */
|
|
362
|
+
const paletteSwatchTokens = ['--color-primary', '--color-primary-hover', '--color-primary-soft', '--color-primary-border']
|
|
368
363
|
|
|
369
364
|
const densityPreviewRows = [
|
|
370
365
|
['S-0012', '2.41', '12.5'],
|
|
@@ -562,7 +557,7 @@ function canShowForCurrentUser(item: AccessControlled): boolean {
|
|
|
562
557
|
<div class="mint-settings-modal__setting">
|
|
563
558
|
<div class="mint-settings-modal__setting-text">
|
|
564
559
|
<span class="mint-settings-modal__setting-label">Color palette</span>
|
|
565
|
-
<span class="mint-settings-modal__setting-hint">
|
|
560
|
+
<span class="mint-settings-modal__setting-hint">Accent colour across the interface</span>
|
|
566
561
|
</div>
|
|
567
562
|
<div class="mint-settings-modal__palettes">
|
|
568
563
|
<button
|
|
@@ -573,12 +568,12 @@ function canShowForCurrentUser(item: AccessControlled): boolean {
|
|
|
573
568
|
:class="['mint-settings-modal__palette', { 'mint-settings-modal__palette--active': settings.colorPalette === key }]"
|
|
574
569
|
@click="settings.colorPalette = key as ColorPalette"
|
|
575
570
|
>
|
|
576
|
-
<span class="mint-settings-modal__swatches" aria-hidden="true">
|
|
571
|
+
<span class="mint-settings-modal__swatches" :data-palette="key" aria-hidden="true">
|
|
577
572
|
<span
|
|
578
|
-
v-for="
|
|
579
|
-
:key="
|
|
573
|
+
v-for="token in paletteSwatchTokens"
|
|
574
|
+
:key="token"
|
|
580
575
|
class="mint-settings-modal__swatch"
|
|
581
|
-
:style="{ backgroundColor:
|
|
576
|
+
:style="{ backgroundColor: `var(${token})` }"
|
|
582
577
|
/>
|
|
583
578
|
</span>
|
|
584
579
|
<span class="mint-settings-modal__palette-name">{{ palette.name }}</span>
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Floating tooltip using @floating-ui with auto-flip, delay, optional keyboard shortcut badge, and body Teleport. */
|
|
3
|
-
import { ref, onBeforeUnmount
|
|
4
|
-
import {
|
|
5
|
-
import { useEventListener } from '../composables/useEventListener'
|
|
3
|
+
import { ref, onBeforeUnmount } from 'vue'
|
|
4
|
+
import { useFloatingPanel } from '../composables/useFloatingPanel'
|
|
6
5
|
|
|
7
6
|
interface Props {
|
|
8
7
|
text: string
|
|
@@ -21,40 +20,19 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
21
20
|
|
|
22
21
|
const tooltipId = `mint-tooltip-${Math.random().toString(36).slice(2, 9)}`
|
|
23
22
|
const visible = ref(false)
|
|
24
|
-
const actualPlacement = ref<Placement>(props.position)
|
|
25
23
|
const triggerRef = ref<HTMLElement | null>(null)
|
|
26
24
|
const contentRef = ref<HTMLElement | null>(null)
|
|
27
|
-
const coords = ref({ top: '0px', left: '0px' })
|
|
28
25
|
let timeoutId: ReturnType<typeof setTimeout> | undefined
|
|
29
26
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
placement: props.position,
|
|
35
|
-
middleware: [
|
|
36
|
-
offset(6),
|
|
37
|
-
flip(),
|
|
38
|
-
shift({ padding: 8 }),
|
|
39
|
-
],
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
coords.value = { top: `${y}px`, left: `${x}px` }
|
|
43
|
-
actualPlacement.value = placement
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
useEventListener(() => window, 'scroll', updatePosition, {
|
|
47
|
-
passive: true,
|
|
48
|
-
capture: true,
|
|
49
|
-
enabled: visible,
|
|
27
|
+
// Follows the trigger while visible; the placement after flip picks the arrow side.
|
|
28
|
+
const { style: coords, placement: actualPlacement } = useFloatingPanel(triggerRef, contentRef, visible, {
|
|
29
|
+
placement: () => props.position,
|
|
30
|
+
offset: 6,
|
|
50
31
|
})
|
|
51
|
-
useEventListener(() => window, 'resize', updatePosition, { enabled: visible })
|
|
52
32
|
|
|
53
|
-
|
|
54
|
-
timeoutId = setTimeout(
|
|
33
|
+
function show() {
|
|
34
|
+
timeoutId = setTimeout(() => {
|
|
55
35
|
visible.value = true
|
|
56
|
-
await nextTick()
|
|
57
|
-
await updatePosition()
|
|
58
36
|
}, props.delay)
|
|
59
37
|
}
|
|
60
38
|
|
|
@@ -96,8 +74,7 @@ function resolvedMaxWidth(value: string | number | undefined): string | undefine
|
|
|
96
74
|
maxWidth ? 'mint-tooltip__content--multiline' : '',
|
|
97
75
|
]"
|
|
98
76
|
:style="{
|
|
99
|
-
|
|
100
|
-
left: coords.left,
|
|
77
|
+
...coords,
|
|
101
78
|
maxWidth: resolvedMaxWidth(maxWidth),
|
|
102
79
|
}"
|
|
103
80
|
role="tooltip"
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The top-bar onboarding controls: the "Getting started n/m" chip while the
|
|
4
|
+
* checklist is open, and the Help button with the checklist, tours and news.
|
|
5
|
+
*/
|
|
6
|
+
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
|
|
7
|
+
import { autoUpdate, computePosition, offset, shift } from '@floating-ui/dom'
|
|
8
|
+
import { Check, CircleHelp, Play } from 'lucide-vue-next'
|
|
9
|
+
import { useEventListener } from '../../composables/useEventListener'
|
|
10
|
+
import type { OnboardingState } from '../../composables/useOnboarding'
|
|
11
|
+
import OnboardingLayerInternal from './OnboardingLayerInternal.vue'
|
|
12
|
+
|
|
13
|
+
const props = defineProps<{ state: OnboardingState }>()
|
|
14
|
+
|
|
15
|
+
const root = ref<HTMLElement | null>(null)
|
|
16
|
+
const helpButton = ref<HTMLElement | null>(null)
|
|
17
|
+
const panel = ref<HTMLElement | null>(null)
|
|
18
|
+
const panelPosition = ref({ x: 0, y: 0 })
|
|
19
|
+
const open = ref(false)
|
|
20
|
+
const hasContent = computed(() =>
|
|
21
|
+
props.state.tasks.value.length > 0 || props.state.tours.value.length > 0 || props.state.news.value.length > 0)
|
|
22
|
+
const progress = computed(() => {
|
|
23
|
+
const total = props.state.tasks.value.length
|
|
24
|
+
return total ? Math.round((props.state.tasksDone.value / total) * 100) : 0
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
function toggle() {
|
|
28
|
+
open.value = !open.value
|
|
29
|
+
if (open.value) props.state.refresh()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// The panel goes to the body, as the top bar clips its overflow; it follows the button.
|
|
33
|
+
let stopAutoUpdate: (() => void) | undefined
|
|
34
|
+
watch(open, async (isOpen) => {
|
|
35
|
+
stopAutoUpdate?.()
|
|
36
|
+
stopAutoUpdate = undefined
|
|
37
|
+
if (!isOpen) return
|
|
38
|
+
await nextTick()
|
|
39
|
+
const button = helpButton.value
|
|
40
|
+
const floating = panel.value
|
|
41
|
+
if (!button || !floating || !open.value) return
|
|
42
|
+
stopAutoUpdate = autoUpdate(button, floating, async () => {
|
|
43
|
+
const { x, y } = await computePosition(button, floating, {
|
|
44
|
+
strategy: 'fixed',
|
|
45
|
+
placement: 'bottom-end',
|
|
46
|
+
middleware: [offset(8), shift({ padding: 8 })],
|
|
47
|
+
})
|
|
48
|
+
panelPosition.value = { x, y }
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
onBeforeUnmount(() => stopAutoUpdate?.())
|
|
52
|
+
|
|
53
|
+
function run(action: () => void) {
|
|
54
|
+
open.value = false
|
|
55
|
+
action()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
useEventListener(document, 'pointerdown', (event) => {
|
|
59
|
+
const target = event.target as Node
|
|
60
|
+
if (open.value && !root.value?.contains(target) && !panel.value?.contains(target)) open.value = false
|
|
61
|
+
})
|
|
62
|
+
useEventListener(document, 'keydown', (event) => {
|
|
63
|
+
if (open.value && event.key === 'Escape') open.value = false
|
|
64
|
+
})
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<template>
|
|
68
|
+
<div v-if="hasContent" ref="root" class="mint-onboarding-help">
|
|
69
|
+
<button
|
|
70
|
+
v-if="state.checklistVisible.value"
|
|
71
|
+
type="button"
|
|
72
|
+
class="mint-onboarding-help__chip"
|
|
73
|
+
:class="{ 'mint-onboarding-help__chip--open': open }"
|
|
74
|
+
data-onboarding="topbar-checklist"
|
|
75
|
+
:aria-expanded="open"
|
|
76
|
+
@click="toggle"
|
|
77
|
+
>
|
|
78
|
+
Getting started
|
|
79
|
+
<span class="mint-onboarding-help__count">{{ state.tasksDone.value }}/{{ state.tasks.value.length }}</span>
|
|
80
|
+
<span class="mint-onboarding-help__bar" aria-hidden="true"><span :style="{ width: `${progress}%` }" /></span>
|
|
81
|
+
</button>
|
|
82
|
+
<button
|
|
83
|
+
ref="helpButton"
|
|
84
|
+
type="button"
|
|
85
|
+
class="mint-topbar__settings-btn"
|
|
86
|
+
:class="{ 'mint-topbar__settings-btn--open': open }"
|
|
87
|
+
data-onboarding="topbar-help"
|
|
88
|
+
:aria-label="state.unseenNews.value ? `Help, ${state.unseenNews.value} new` : 'Help'"
|
|
89
|
+
:aria-expanded="open"
|
|
90
|
+
@click="toggle"
|
|
91
|
+
>
|
|
92
|
+
<CircleHelp class="mint-topbar__settings-icon" aria-hidden="true" />
|
|
93
|
+
<span v-if="state.unseenNews.value" class="mint-onboarding-help__badge" aria-hidden="true">{{ state.unseenNews.value }}</span>
|
|
94
|
+
</button>
|
|
95
|
+
|
|
96
|
+
<Teleport to="body">
|
|
97
|
+
<div
|
|
98
|
+
v-if="open"
|
|
99
|
+
ref="panel"
|
|
100
|
+
class="mint-popover mint-onboarding-help__panel"
|
|
101
|
+
role="dialog"
|
|
102
|
+
aria-label="Help"
|
|
103
|
+
:style="{ left: `${panelPosition.x}px`, top: `${panelPosition.y}px` }"
|
|
104
|
+
>
|
|
105
|
+
<template v-if="state.tasks.value.length">
|
|
106
|
+
<div class="mint-onboarding-help__header">
|
|
107
|
+
<span class="mint-onboarding-help__heading">Getting started</span>
|
|
108
|
+
<span class="mint-onboarding-help__count">{{ state.tasksDone.value }}/{{ state.tasks.value.length }}</span>
|
|
109
|
+
</div>
|
|
110
|
+
<div class="mint-onboarding-help__progress" aria-hidden="true"><span :style="{ width: `${progress}%` }" /></div>
|
|
111
|
+
<ul class="mint-onboarding-help__todos">
|
|
112
|
+
<li
|
|
113
|
+
v-for="task in state.tasks.value"
|
|
114
|
+
:key="task.id"
|
|
115
|
+
:class="['mint-onboarding-help__todo', { 'mint-onboarding-help__todo--done': task.done }]"
|
|
116
|
+
>
|
|
117
|
+
<span class="mint-onboarding-help__check" role="img" :aria-label="task.done ? 'Done' : 'Not done'">
|
|
118
|
+
<Check v-if="task.done" :size="10" :stroke-width="3" aria-hidden="true" />
|
|
119
|
+
</span>
|
|
120
|
+
<span class="mint-onboarding-help__todo-title">{{ task.title }}</span>
|
|
121
|
+
<button
|
|
122
|
+
v-if="task.action && !task.done"
|
|
123
|
+
type="button"
|
|
124
|
+
class="mint-onboarding-help__link"
|
|
125
|
+
@click="run(task.action.run)"
|
|
126
|
+
>
|
|
127
|
+
{{ task.action.label }}
|
|
128
|
+
</button>
|
|
129
|
+
</li>
|
|
130
|
+
</ul>
|
|
131
|
+
<button
|
|
132
|
+
v-if="state.tasksDone.value < state.tasks.value.length"
|
|
133
|
+
type="button"
|
|
134
|
+
class="mint-onboarding-help__link mint-onboarding-help__hide"
|
|
135
|
+
@click="state.setChecklistHidden(!state.checklistHidden.value)"
|
|
136
|
+
>
|
|
137
|
+
{{ state.checklistHidden.value ? 'Show checklist in the top bar' : 'Hide checklist from the top bar' }}
|
|
138
|
+
</button>
|
|
139
|
+
</template>
|
|
140
|
+
|
|
141
|
+
<template v-if="state.tours.value.length">
|
|
142
|
+
<div class="mint-onboarding-help__section">Tours</div>
|
|
143
|
+
<button
|
|
144
|
+
v-for="tour in state.tours.value"
|
|
145
|
+
:key="tour.id"
|
|
146
|
+
type="button"
|
|
147
|
+
class="mint-menu-item"
|
|
148
|
+
@click="run(() => state.startTour(tour.id))"
|
|
149
|
+
>
|
|
150
|
+
<Play class="mint-onboarding-help__icon" aria-hidden="true" />
|
|
151
|
+
{{ tour.title }}
|
|
152
|
+
</button>
|
|
153
|
+
</template>
|
|
154
|
+
|
|
155
|
+
<template v-if="state.news.value.length">
|
|
156
|
+
<div class="mint-onboarding-help__section">What's new</div>
|
|
157
|
+
<button
|
|
158
|
+
v-for="item in state.news.value"
|
|
159
|
+
:key="item.id"
|
|
160
|
+
type="button"
|
|
161
|
+
class="mint-menu-item"
|
|
162
|
+
@click="run(() => state.showNews(item.id))"
|
|
163
|
+
>
|
|
164
|
+
{{ item.title }}
|
|
165
|
+
<span v-if="item.unseen" class="mint-onboarding-help__new">New</span>
|
|
166
|
+
</button>
|
|
167
|
+
</template>
|
|
168
|
+
</div>
|
|
169
|
+
</Teleport>
|
|
170
|
+
</div>
|
|
171
|
+
<OnboardingLayerInternal :state="state" />
|
|
172
|
+
</template>
|