@morscherlab/mint-sdk 1.2.0-beta.4 → 1.2.0-beta.5
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/{EmptyState-p90quq8Z.js → EmptyState-NBqf2_8z.js} +2 -2
- package/dist/{EmptyState-p90quq8Z.js.map → EmptyState-NBqf2_8z.js.map} +1 -1
- package/dist/{ExperimentPopover-tLwJ7XF4.js → ExperimentPopover-CfOdh5Rm.js} +3 -3
- package/dist/{ExperimentPopover-tLwJ7XF4.js.map → ExperimentPopover-CfOdh5Rm.js.map} +1 -1
- package/dist/{ExperimentSelectorModal-B6P2msT4.js → ExperimentSelectorModal-DvIagrxY.js} +4 -4
- package/dist/{ExperimentSelectorModal-B6P2msT4.js.map → ExperimentSelectorModal-DvIagrxY.js.map} +1 -1
- package/dist/{SettingsModal-CkNM8qAI.js → SettingsModal-W3TYfnP9.js} +4 -7
- package/dist/SettingsModal-W3TYfnP9.js.map +1 -0
- package/dist/__tests__/components/PluginWorkspaceView.internal.test.d.ts +1 -0
- package/dist/__tests__/components/SecretInput.test.d.ts +1 -0
- package/dist/__tests__/components/WellEditPopupInternal.test.d.ts +1 -0
- package/dist/components/AppAvatarMenu.vue.d.ts +4 -0
- package/dist/components/AppSidebar.vue.d.ts +1 -1
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +1 -1
- package/dist/components/DropdownButton.vue.d.ts +2 -0
- package/dist/components/FitPanel.vue.d.ts +1 -1
- package/dist/components/PluginWorkspaceView.props.d.ts +29 -1
- package/dist/components/PluginWorkspaceView.vue.d.ts +5 -5
- package/dist/components/SecretInput.vue.d.ts +28 -0
- package/dist/components/SmartGroupFieldRecipe.vue.d.ts +1 -1
- package/dist/components/index.d.ts +9 -0
- package/dist/components/index.js +8 -8
- package/dist/components/internal/WellEditPopupInternal.vue.d.ts +3 -1
- package/dist/components/internal/WorkspaceControlsInternal.vue.d.ts +23 -0
- package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +70 -0
- package/dist/components/internal/WorkspaceSidebarInternal.vue.d.ts +43 -0
- package/dist/{components-MGNL6tRn.js → components-B80gV0P6.js} +575 -561
- package/dist/components-B80gV0P6.js.map +1 -0
- package/dist/composables/flattenWorkspaceBindings.d.ts +61 -0
- package/dist/composables/index.d.ts +5 -0
- package/dist/composables/index.js +9 -9
- package/dist/composables/pluginWorkspaceModel.d.ts +164 -0
- package/dist/composables/useAsync.d.ts +5 -1
- package/dist/composables/useBioTemplateWorkspace.d.ts +4 -20
- package/dist/composables/useCommandHistory.d.ts +5 -1
- package/dist/composables/useFocusTrap.d.ts +21 -0
- package/dist/composables/useMenuKeyboard.d.ts +29 -0
- package/dist/composables/useOptimisticMutation.d.ts +5 -1
- package/dist/composables/usePluginWorkspace.d.ts +51 -0
- package/dist/composables/useResourceCrud.d.ts +5 -1
- package/dist/composables/useRovingFocus.d.ts +24 -0
- package/dist/composables/useWellPainting.d.ts +5 -1
- package/dist/composables/useWellPlateAdapter.d.ts +5 -1
- package/dist/composables/useWellPlateValidation.d.ts +5 -1
- package/dist/composables/workspaceRecipes.d.ts +188 -0
- package/dist/{composables-CKxOcvgh.js → composables-C8mmD5ta.js} +65 -17
- package/dist/composables-C8mmD5ta.js.map +1 -0
- package/dist/{experiment-utils-CLnzPPq5.js → experiment-utils-DV3DrvE_.js} +70 -51
- package/dist/experiment-utils-DV3DrvE_.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +13 -13
- package/dist/install.js +7 -7
- package/dist/styles.css +100 -49
- package/dist/templates/builderReadUtils.d.ts +1 -1
- package/dist/templates/index.js +4 -4
- package/dist/{templates-BTJ9C9Kb.js → templates-CK72aKO1.js} +14 -23
- package/dist/templates-CK72aKO1.js.map +1 -0
- package/dist/types/form-builder.d.ts +1 -1
- package/dist/{useControlSchema-CXhxTul7.js → useControlSchema-_OVhavKq.js} +280 -63
- package/dist/useControlSchema-_OVhavKq.js.map +1 -0
- package/dist/{useDropdownState-DSb3BC3N.js → useDropdownState-D5G4UvWd.js} +2 -2
- package/dist/{useDropdownState-DSb3BC3N.js.map → useDropdownState-D5G4UvWd.js.map} +1 -1
- package/dist/{useExperimentSelector-D0pjj9WH.js → useExperimentSelector-DSEckf_g.js} +3 -3
- package/dist/{useExperimentSelector-D0pjj9WH.js.map → useExperimentSelector-DSEckf_g.js.map} +1 -1
- package/dist/{useFormBuilder-CuOFB3aP.js → useFormBuilder-Bd714C7y.js} +2 -2
- package/dist/{useFormBuilder-CuOFB3aP.js.map → useFormBuilder-Bd714C7y.js.map} +1 -1
- package/dist/{useProtocolTemplates-BWhQIWV5.js → useProtocolTemplates-Dq5Pzwyd.js} +481 -46
- package/dist/useProtocolTemplates-Dq5Pzwyd.js.map +1 -0
- package/dist/utils/pluginSecrets.d.ts +53 -0
- package/dist/utils/records.d.ts +4 -0
- package/package.json +1 -1
- package/src/__tests__/components/AppAvatarMenu.test.ts +123 -0
- package/src/__tests__/components/BaseTabs.test.ts +67 -0
- package/src/__tests__/components/DropdownButton.test.ts +134 -0
- package/src/__tests__/components/PluginWorkspaceView.internal.test.ts +230 -0
- package/src/__tests__/components/SecretInput.test.ts +87 -0
- package/src/__tests__/components/WellEditPopupInternal.test.ts +108 -0
- package/src/__tests__/composables/usePluginClient.test.ts +140 -0
- package/src/components/AppAvatarMenu.vue +34 -3
- package/src/components/BaseModal.vue +8 -56
- package/src/components/BaseTabs.vue +26 -1
- package/src/components/BioTemplateExperimentWorkspaceView.vue +10 -21
- package/src/components/BioTemplatePackWorkspaceView.vue +11 -15
- package/src/components/BioTemplatePresetWorkspaceView.vue +8 -13
- package/src/components/ComponentBindingRenderer.vue +1 -3
- package/src/components/ControlWorkspaceView.vue +11 -22
- package/src/components/DoseDesignWorkspaceView.vue +6 -25
- package/src/components/DropdownButton.vue +32 -2
- package/src/components/PluginWorkspaceView.props.ts +38 -0
- package/src/components/PluginWorkspaceView.vue +53 -224
- package/src/components/SecretInput.story.vue +79 -0
- package/src/components/SecretInput.vue +150 -0
- package/src/components/index.ts +9 -0
- package/src/components/internal/WellEditPopupInternal.vue +20 -1
- package/src/components/internal/WorkspaceControlsInternal.vue +20 -0
- package/src/components/internal/WorkspaceShellInternal.vue +125 -0
- package/src/components/internal/WorkspaceSidebarInternal.vue +82 -0
- package/src/composables/experimentDesignData.ts +1 -3
- package/src/composables/flattenWorkspaceBindings.ts +104 -0
- package/src/composables/formBuilderRegistry.ts +5 -0
- package/src/composables/index.ts +26 -0
- package/src/composables/pluginWorkspaceModel.ts +182 -0
- package/src/composables/useAsync.ts +5 -1
- package/src/composables/useBioTemplatePackWorkspace.ts +15 -9
- package/src/composables/useBioTemplatePresetWorkspace.ts +14 -15
- package/src/composables/useBioTemplateWorkspace.ts +18 -43
- package/src/composables/useCommandHistory.ts +5 -1
- package/src/composables/useFocusTrap.ts +91 -0
- package/src/composables/useMenuKeyboard.ts +90 -0
- package/src/composables/useOptimisticMutation.ts +5 -1
- package/src/composables/usePluginClient.ts +42 -4
- package/src/composables/usePluginWorkspace.ts +208 -0
- package/src/composables/useResourceCrud.ts +5 -1
- package/src/composables/useRovingFocus.ts +66 -0
- package/src/composables/useWellPainting.ts +5 -1
- package/src/composables/useWellPlateAdapter.ts +5 -1
- package/src/composables/useWellPlateValidation.ts +5 -1
- package/src/composables/workspaceRecipes.ts +267 -0
- package/src/index.ts +16 -0
- package/src/styles/components/secret-input.css +60 -0
- package/src/styles/index.css +1 -0
- package/src/templates/builderReadUtils.ts +1 -3
- package/src/templates/componentTargetResolvers.ts +1 -3
- package/src/templates/controlSchemaTargets.ts +1 -3
- package/src/types/form-builder.ts +1 -0
- package/src/utils/formModelSync.ts +1 -3
- package/src/utils/pluginIcon.ts +1 -3
- package/src/utils/pluginSecrets.ts +102 -0
- package/src/utils/records.ts +9 -0
- package/dist/SettingsModal-CkNM8qAI.js.map +0 -1
- package/dist/components-MGNL6tRn.js.map +0 -1
- package/dist/composables-CKxOcvgh.js.map +0 -1
- package/dist/experiment-utils-CLnzPPq5.js.map +0 -1
- package/dist/templates-BTJ9C9Kb.js.map +0 -1
- package/dist/useControlSchema-CXhxTul7.js.map +0 -1
- package/dist/useProtocolTemplates-BWhQIWV5.js.map +0 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import SecretInput from './SecretInput.vue'
|
|
4
|
+
import type { PluginSecretRef } from '../utils/pluginSecrets'
|
|
5
|
+
|
|
6
|
+
const sizes = ['sm', 'md', 'lg'] as const
|
|
7
|
+
|
|
8
|
+
const storedRef: PluginSecretRef = { $secret: 'sec_0f2a4c6e8a0c2e4a6c8e0a2c4e6a8c0e', set: true }
|
|
9
|
+
const lockedRef: PluginSecretRef = { $secret: null, locked: true }
|
|
10
|
+
|
|
11
|
+
const stored = ref<PluginSecretRef | string | null>({ ...storedRef })
|
|
12
|
+
const unset = ref<PluginSecretRef | string | null>(null)
|
|
13
|
+
const locked = ref<PluginSecretRef | string | null>({ ...lockedRef })
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<Story title="Base Inputs/SecretInput">
|
|
18
|
+
<Variant title="Playground">
|
|
19
|
+
<template #default="{ state }">
|
|
20
|
+
<div style="padding: 2rem; max-width: 600px;">
|
|
21
|
+
<SecretInput
|
|
22
|
+
v-model="state.value"
|
|
23
|
+
:placeholder="state.placeholder"
|
|
24
|
+
:disabled="state.disabled"
|
|
25
|
+
:error="state.error"
|
|
26
|
+
:size="state.size"
|
|
27
|
+
/>
|
|
28
|
+
<p style="margin-top: 1rem; font-size: 0.875rem; color: var(--text-muted, #94a3b8);">
|
|
29
|
+
Submits: <code>{{ JSON.stringify(state.value) }}</code>
|
|
30
|
+
</p>
|
|
31
|
+
</div>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<template #controls="{ state }">
|
|
35
|
+
<HstText v-model="state.placeholder" title="Placeholder" />
|
|
36
|
+
<HstSelect
|
|
37
|
+
v-model="state.size"
|
|
38
|
+
title="Size"
|
|
39
|
+
:options="sizes.map(size => ({ label: size, value: size }))"
|
|
40
|
+
/>
|
|
41
|
+
<HstCheckbox v-model="state.disabled" title="Disabled" />
|
|
42
|
+
<HstCheckbox v-model="state.error" title="Error" />
|
|
43
|
+
</template>
|
|
44
|
+
|
|
45
|
+
<template #initState>
|
|
46
|
+
{{ {
|
|
47
|
+
value: { $secret: 'sec_0f2a4c6e8a0c2e4a6c8e0a2c4e6a8c0e', set: true },
|
|
48
|
+
placeholder: 'Enter a new value',
|
|
49
|
+
disabled: false,
|
|
50
|
+
error: false,
|
|
51
|
+
size: 'md',
|
|
52
|
+
} }}
|
|
53
|
+
</template>
|
|
54
|
+
</Variant>
|
|
55
|
+
|
|
56
|
+
<Variant title="States">
|
|
57
|
+
<div style="padding: 2rem; max-width: 600px; display: flex; flex-direction: column; gap: 1.5rem;">
|
|
58
|
+
<div>
|
|
59
|
+
<p style="margin: 0 0 0.5rem; font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em;">
|
|
60
|
+
Stored
|
|
61
|
+
</p>
|
|
62
|
+
<SecretInput v-model="stored" />
|
|
63
|
+
</div>
|
|
64
|
+
<div>
|
|
65
|
+
<p style="margin: 0 0 0.5rem; font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em;">
|
|
66
|
+
Not set
|
|
67
|
+
</p>
|
|
68
|
+
<SecretInput v-model="unset" />
|
|
69
|
+
</div>
|
|
70
|
+
<div>
|
|
71
|
+
<p style="margin: 0 0 0.5rem; font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em;">
|
|
72
|
+
Provided by environment
|
|
73
|
+
</p>
|
|
74
|
+
<SecretInput v-model="locked" />
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</Variant>
|
|
78
|
+
</Story>
|
|
79
|
+
</template>
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** Edits one plugin secret setting: shows set / not set / provided by environment, and submits a `$secret` reference, a new value, or `null`. */
|
|
3
|
+
import { computed, ref, watch } from 'vue'
|
|
4
|
+
import BaseInput from './BaseInput.vue'
|
|
5
|
+
import BaseButton from './BaseButton.vue'
|
|
6
|
+
import {
|
|
7
|
+
clearPluginSecret,
|
|
8
|
+
isPluginSecretLocked,
|
|
9
|
+
isPluginSecretRef,
|
|
10
|
+
keepPluginSecret,
|
|
11
|
+
pluginSecretLabel,
|
|
12
|
+
pluginSecretState,
|
|
13
|
+
setPluginSecret,
|
|
14
|
+
type PluginSecretRef,
|
|
15
|
+
type PluginSecretState,
|
|
16
|
+
} from '../utils/pluginSecrets'
|
|
17
|
+
|
|
18
|
+
type SecretValue = PluginSecretRef | string | null
|
|
19
|
+
|
|
20
|
+
interface Props {
|
|
21
|
+
/** The `$secret` marker from the settings response, a typed string, or `null`. */
|
|
22
|
+
modelValue?: SecretValue
|
|
23
|
+
placeholder?: string
|
|
24
|
+
disabled?: boolean
|
|
25
|
+
error?: boolean
|
|
26
|
+
size?: 'sm' | 'md' | 'lg'
|
|
27
|
+
/** Label on the control that starts entering a replacement value. */
|
|
28
|
+
changeLabel?: string
|
|
29
|
+
/** Label on the control that clears a stored secret. */
|
|
30
|
+
clearLabel?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
34
|
+
modelValue: null,
|
|
35
|
+
placeholder: 'Enter a new value',
|
|
36
|
+
disabled: false,
|
|
37
|
+
error: false,
|
|
38
|
+
size: 'md',
|
|
39
|
+
changeLabel: 'Change',
|
|
40
|
+
clearLabel: 'Clear',
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const emit = defineEmits<{
|
|
44
|
+
'update:modelValue': [value: SecretValue]
|
|
45
|
+
}>()
|
|
46
|
+
|
|
47
|
+
/** The reference this field started from, so an abandoned edit can restore it. */
|
|
48
|
+
const storedReference = ref<PluginSecretRef | null>(keepPluginSecret(props.modelValue))
|
|
49
|
+
const isEditing = ref(typeof props.modelValue === 'string')
|
|
50
|
+
|
|
51
|
+
watch(
|
|
52
|
+
() => props.modelValue,
|
|
53
|
+
(value) => {
|
|
54
|
+
if (isPluginSecretRef(value)) {
|
|
55
|
+
storedReference.value = keepPluginSecret(value)
|
|
56
|
+
isEditing.value = false
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
const state = computed<PluginSecretState>(() => pluginSecretState(props.modelValue))
|
|
62
|
+
const locked = computed(() => isPluginSecretLocked(props.modelValue)
|
|
63
|
+
|| isPluginSecretLocked(storedReference.value))
|
|
64
|
+
const statusLabel = computed(() => isEditing.value
|
|
65
|
+
? 'New value'
|
|
66
|
+
: pluginSecretLabel(props.modelValue))
|
|
67
|
+
const typedValue = computed(() => typeof props.modelValue === 'string' ? props.modelValue : '')
|
|
68
|
+
const canClear = computed(() => !locked.value && (state.value === 'set' || isEditing.value))
|
|
69
|
+
|
|
70
|
+
function startEditing(): void {
|
|
71
|
+
if (props.disabled || locked.value) return
|
|
72
|
+
isEditing.value = true
|
|
73
|
+
emit('update:modelValue', setPluginSecret(''))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function updateTyped(value: string | number | null): void {
|
|
77
|
+
emit('update:modelValue', setPluginSecret(value == null ? '' : String(value)))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function cancelEditing(): void {
|
|
81
|
+
isEditing.value = false
|
|
82
|
+
emit('update:modelValue', storedReference.value ? { ...storedReference.value } : null)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function clear(): void {
|
|
86
|
+
if (props.disabled || locked.value) return
|
|
87
|
+
isEditing.value = false
|
|
88
|
+
storedReference.value = null
|
|
89
|
+
emit('update:modelValue', clearPluginSecret())
|
|
90
|
+
}
|
|
91
|
+
</script>
|
|
92
|
+
|
|
93
|
+
<template>
|
|
94
|
+
<div class="mint-secret-input" :class="{ 'mint-secret-input--error': error }">
|
|
95
|
+
<div class="mint-secret-input__status">
|
|
96
|
+
<span
|
|
97
|
+
class="mint-secret-input__badge"
|
|
98
|
+
:class="`mint-secret-input__badge--${isEditing ? 'pending' : state}`"
|
|
99
|
+
>{{ statusLabel }}</span>
|
|
100
|
+
<span v-if="locked" class="mint-secret-input__hint">
|
|
101
|
+
This value comes from the environment and cannot be edited here.
|
|
102
|
+
</span>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<BaseInput
|
|
106
|
+
v-if="isEditing"
|
|
107
|
+
type="password"
|
|
108
|
+
autocomplete="new-password"
|
|
109
|
+
:model-value="typedValue"
|
|
110
|
+
:placeholder="placeholder"
|
|
111
|
+
:disabled="disabled"
|
|
112
|
+
:error="error"
|
|
113
|
+
:size="size"
|
|
114
|
+
@update:model-value="updateTyped"
|
|
115
|
+
/>
|
|
116
|
+
|
|
117
|
+
<div class="mint-secret-input__actions">
|
|
118
|
+
<BaseButton
|
|
119
|
+
v-if="!isEditing"
|
|
120
|
+
type="button"
|
|
121
|
+
variant="secondary"
|
|
122
|
+
:size="size === 'lg' ? 'md' : 'sm'"
|
|
123
|
+
:disabled="disabled || locked"
|
|
124
|
+
@click="startEditing"
|
|
125
|
+
>
|
|
126
|
+
{{ changeLabel }}
|
|
127
|
+
</BaseButton>
|
|
128
|
+
<BaseButton
|
|
129
|
+
v-else
|
|
130
|
+
type="button"
|
|
131
|
+
variant="ghost"
|
|
132
|
+
:size="size === 'lg' ? 'md' : 'sm'"
|
|
133
|
+
:disabled="disabled"
|
|
134
|
+
@click="cancelEditing"
|
|
135
|
+
>
|
|
136
|
+
Cancel
|
|
137
|
+
</BaseButton>
|
|
138
|
+
<BaseButton
|
|
139
|
+
v-if="canClear"
|
|
140
|
+
type="button"
|
|
141
|
+
variant="ghost"
|
|
142
|
+
:size="size === 'lg' ? 'md' : 'sm'"
|
|
143
|
+
:disabled="disabled"
|
|
144
|
+
@click="clear"
|
|
145
|
+
>
|
|
146
|
+
{{ clearLabel }}
|
|
147
|
+
</BaseButton>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</template>
|
package/src/components/index.ts
CHANGED
|
@@ -7,12 +7,14 @@ export { default as BaseCheckbox } from './BaseCheckbox.vue'
|
|
|
7
7
|
export { default as BaseToggle } from './BaseToggle.vue'
|
|
8
8
|
export { default as BaseRadioGroup } from './BaseRadioGroup.vue'
|
|
9
9
|
export { default as BaseSlider } from './BaseSlider.vue'
|
|
10
|
+
/** Range slider with a gradient color track and color-coded value badge for threshold visualization. @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; use `BaseSlider` instead. */
|
|
10
11
|
export { default as ColorSlider } from './ColorSlider.vue'
|
|
11
12
|
export { default as BaseTabs } from './BaseTabs.vue'
|
|
12
13
|
export { default as BaseModal } from './BaseModal.vue'
|
|
13
14
|
export { default as SegmentedControl } from './SegmentedControl.vue'
|
|
14
15
|
export { default as MultiSelect } from './MultiSelect.vue'
|
|
15
16
|
export { default as BasePill } from './BasePill.vue'
|
|
17
|
+
/** Button that opens a dropdown menu for selecting one option from a list. @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; use `ActionMenu` instead. */
|
|
16
18
|
export { default as DropdownButton } from './DropdownButton.vue'
|
|
17
19
|
export { default as Calendar } from './Calendar.vue'
|
|
18
20
|
export { default as DataFrame } from './DataFrame.vue'
|
|
@@ -22,6 +24,7 @@ export { default as FormField } from './FormField.vue'
|
|
|
22
24
|
export { default as DatePicker } from './DatePicker.vue'
|
|
23
25
|
export { default as TimePicker } from './TimePicker.vue'
|
|
24
26
|
export { default as TagsInput } from './TagsInput.vue'
|
|
27
|
+
export { default as SecretInput } from './SecretInput.vue'
|
|
25
28
|
export { default as NumberInput } from './NumberInput.vue'
|
|
26
29
|
export { default as FileUploader } from './FileUploader.vue'
|
|
27
30
|
|
|
@@ -97,11 +100,15 @@ export { default as ProtocolStepEditor } from './ProtocolStepEditor.vue'
|
|
|
97
100
|
|
|
98
101
|
// Scientific display components
|
|
99
102
|
export { default as ScientificNumber } from './ScientificNumber.vue'
|
|
103
|
+
/** Renders a chemical formula string (H₂O, C₆H₁₂O₆, Ca(OH)₂) with typographically correct subscripts and superscripts for charges. @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; no platform replacement is planned. */
|
|
100
104
|
export { default as ChemicalFormula } from './ChemicalFormula.vue'
|
|
101
105
|
export { default as SequenceProgressBar } from './SequenceProgressBar.vue'
|
|
106
|
+
/** Filterable instrument alert/event list keyed by each alert's required stable event_key. @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; instrument UI moves to the mld-ms plugins. */
|
|
102
107
|
export { default as InstrumentAlertLog } from './InstrumentAlertLog.vue'
|
|
103
108
|
export { default as InstrumentStateBadge } from './InstrumentStateBadge.vue'
|
|
109
|
+
/** Compact card for live instrument monitor status, sample, method, sequence progress, and ETA. @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; instrument UI moves to the mld-ms plugins. */
|
|
104
110
|
export { default as InstrumentStatusCard } from './InstrumentStatusCard.vue'
|
|
111
|
+
/** Table for Xcalibur-compatible LCMS sequence rows with optional reorder/remove/duplicate controls. @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; instrument UI moves to the mld-ms plugins. */
|
|
105
112
|
export { default as LcmsSequenceTable } from './LcmsSequenceTable.vue'
|
|
106
113
|
|
|
107
114
|
// Scientific input components
|
|
@@ -125,8 +132,10 @@ export { default as ExperimentCodeBadge } from './ExperimentCodeBadge.vue'
|
|
|
125
132
|
|
|
126
133
|
// Scheduling / booking components
|
|
127
134
|
export { default as DateTimePicker } from './DateTimePicker.vue'
|
|
135
|
+
/** Paired start/end time pickers that validate range order and display computed duration. @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; use `TimePicker` instead. */
|
|
128
136
|
export { default as TimeRangeInput } from './TimeRangeInput.vue'
|
|
129
137
|
export { default as ScheduleCalendar } from './ScheduleCalendar.vue'
|
|
138
|
+
/** Card displaying a lab resource with availability status, specs, tags, and a book action. @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; no platform replacement is planned. */
|
|
130
139
|
export { default as ResourceCard } from './ResourceCard.vue'
|
|
131
140
|
export { default as SectionCard } from './SectionCard.vue'
|
|
132
141
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { ref, watch, computed } from 'vue'
|
|
2
|
+
import { onBeforeUnmount, onMounted, ref, watch, computed } from 'vue'
|
|
3
3
|
import type { Well, WellEditData, WellEditField } from '../../types'
|
|
4
4
|
import { useEventListener } from '../../composables/useEventListener'
|
|
5
|
+
import { useFocusTrap } from '../../composables/useFocusTrap'
|
|
5
6
|
|
|
6
7
|
interface Props {
|
|
7
8
|
wellId: string
|
|
@@ -86,6 +87,22 @@ function handleKeydown(e: KeyboardEvent) {
|
|
|
86
87
|
|
|
87
88
|
useEventListener(() => document, 'keydown', handleKeydown)
|
|
88
89
|
|
|
90
|
+
// The popup has no open/close prop of its own — the parent mounts and unmounts it —
|
|
91
|
+
// so the trap activates on mount and releases (restoring focus) just before unmount,
|
|
92
|
+
// the same lifecycle BaseModal drives off `modelValue`.
|
|
93
|
+
const containerRef = ref<HTMLElement | null>(null)
|
|
94
|
+
const trapActive = ref(false)
|
|
95
|
+
|
|
96
|
+
useFocusTrap({ containerRef, active: trapActive })
|
|
97
|
+
|
|
98
|
+
onMounted(() => {
|
|
99
|
+
trapActive.value = true
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
onBeforeUnmount(() => {
|
|
103
|
+
trapActive.value = false
|
|
104
|
+
})
|
|
105
|
+
|
|
89
106
|
const sampleTypeButtons = [
|
|
90
107
|
{ type: 'sample', label: 'S', tooltip: 'Sample' },
|
|
91
108
|
{ type: 'blank', label: 'B', tooltip: 'Blank' },
|
|
@@ -101,8 +118,10 @@ const sampleTypeButtons = [
|
|
|
101
118
|
|
|
102
119
|
<!-- Popup -->
|
|
103
120
|
<div
|
|
121
|
+
ref="containerRef"
|
|
104
122
|
class="mint-well-edit-popup"
|
|
105
123
|
:style="popupStyle"
|
|
124
|
+
tabindex="-1"
|
|
106
125
|
@click.stop
|
|
107
126
|
>
|
|
108
127
|
<!-- Header -->
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Internal: renders PluginWorkspaceView's default (main content) slot bound to the
|
|
4
|
+
* control-schema-derived scoped-slot payload (active view, values, component bindings/props).
|
|
5
|
+
* Not part of the public SDK surface — see PluginWorkspaceView.vue for the public component.
|
|
6
|
+
*/
|
|
7
|
+
import type { PluginWorkspaceControlsModel } from '../../composables/pluginWorkspaceModel'
|
|
8
|
+
|
|
9
|
+
defineProps<{
|
|
10
|
+
controls: PluginWorkspaceControlsModel
|
|
11
|
+
}>()
|
|
12
|
+
|
|
13
|
+
defineSlots<{
|
|
14
|
+
default?: (props: PluginWorkspaceControlsModel) => unknown
|
|
15
|
+
}>()
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<slot v-bind="controls" />
|
|
20
|
+
</template>
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Internal: AppLayout + AppTopBar for PluginWorkspaceView, driven by a single `shell` model.
|
|
4
|
+
* Owns the responsive/collapse layout props and the top bar's default rendering; the caller
|
|
5
|
+
* decides whether to override the `topbar`/`sidebar` slots and supplies the sidebar content.
|
|
6
|
+
* Not part of the public SDK surface — see PluginWorkspaceView.vue for the public component.
|
|
7
|
+
*/
|
|
8
|
+
import type {
|
|
9
|
+
AccountMenuItem,
|
|
10
|
+
PageSelectorItem,
|
|
11
|
+
PillNavItem,
|
|
12
|
+
PillNavOption,
|
|
13
|
+
PluginSwitcherPlugin,
|
|
14
|
+
} from '../../types'
|
|
15
|
+
import AppLayout from '../AppLayout.vue'
|
|
16
|
+
import AppTopBar from '../AppTopBar.vue'
|
|
17
|
+
import type { PluginWorkspaceShellModel } from '../../composables/pluginWorkspaceModel'
|
|
18
|
+
|
|
19
|
+
defineProps<{
|
|
20
|
+
shell: PluginWorkspaceShellModel
|
|
21
|
+
}>()
|
|
22
|
+
|
|
23
|
+
const emit = defineEmits<{
|
|
24
|
+
'page-selector-select': [page: PageSelectorItem]
|
|
25
|
+
'pill-select': [item: PillNavItem]
|
|
26
|
+
'pill-option-select': [option: PillNavOption, item: PillNavItem]
|
|
27
|
+
'plugin-switcher-select': [plugin: PluginSwitcherPlugin]
|
|
28
|
+
'plugin-switcher-install': []
|
|
29
|
+
'account-menu-select': [item: AccountMenuItem]
|
|
30
|
+
'sign-out': []
|
|
31
|
+
'notifications-click': []
|
|
32
|
+
'settings-values-change': [data: Record<string, unknown>]
|
|
33
|
+
'admin-click': []
|
|
34
|
+
}>()
|
|
35
|
+
|
|
36
|
+
function handlePillOptionSelect(option: PillNavOption, item: PillNavItem) {
|
|
37
|
+
emit('pill-option-select', option, item)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
defineSlots<{
|
|
41
|
+
topbar?: (props: PluginWorkspaceShellModel['controls']) => unknown
|
|
42
|
+
sidebar?: (props: PluginWorkspaceShellModel['controls']) => unknown
|
|
43
|
+
default?: (props: PluginWorkspaceShellModel['controls']) => unknown
|
|
44
|
+
icon?: (props: Record<string, unknown>) => unknown
|
|
45
|
+
logo?: (props: Record<string, unknown>) => unknown
|
|
46
|
+
'page-selector-icon'?: (props: Record<string, unknown>) => unknown
|
|
47
|
+
'page-selector-item-icon'?: (props: Record<string, unknown>) => unknown
|
|
48
|
+
nav?: (props: Record<string, unknown>) => unknown
|
|
49
|
+
center?: (props: Record<string, unknown>) => unknown
|
|
50
|
+
actions?: (props: Record<string, unknown>) => unknown
|
|
51
|
+
'account-menu-items'?: (props: Record<string, unknown>) => unknown
|
|
52
|
+
'account-menu-item-icon'?: (props: Record<string, unknown>) => unknown
|
|
53
|
+
'settings-appearance'?: (props: Record<string, unknown>) => unknown
|
|
54
|
+
}>()
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<template>
|
|
58
|
+
<AppLayout
|
|
59
|
+
class="mint-plugin-workspace-view"
|
|
60
|
+
:sidebar-position="shell.sidebarPosition"
|
|
61
|
+
:sidebar-width="shell.sidebarWidth"
|
|
62
|
+
:floating="shell.floating"
|
|
63
|
+
:responsive-sidebar="shell.responsiveSidebar"
|
|
64
|
+
:sidebar-toggle-label="shell.sidebarToggleLabel"
|
|
65
|
+
:sidebar-close-label="shell.sidebarCloseLabel"
|
|
66
|
+
>
|
|
67
|
+
<template #topbar>
|
|
68
|
+
<slot name="topbar" v-bind="shell.controls">
|
|
69
|
+
<AppTopBar
|
|
70
|
+
:title="shell.title"
|
|
71
|
+
:subtitle="shell.subtitle"
|
|
72
|
+
:variant="shell.topBarVariant"
|
|
73
|
+
:home-path="shell.homePath"
|
|
74
|
+
:show-logo="shell.showLogo"
|
|
75
|
+
:page-selector="shell.pageSelector"
|
|
76
|
+
:current-page-selector-id="shell.currentPageSelectorId"
|
|
77
|
+
:plugin-switcher="shell.pluginSwitcher"
|
|
78
|
+
:pill-nav="shell.pillNav"
|
|
79
|
+
:current-pill-id="shell.currentPillId"
|
|
80
|
+
:show-theme-toggle="shell.showThemeToggle"
|
|
81
|
+
:show-settings="shell.showSettings"
|
|
82
|
+
:settings-config="shell.settingsConfig"
|
|
83
|
+
:show-standalone-label="shell.showStandaloneLabel"
|
|
84
|
+
:standalone-label="shell.standaloneLabel"
|
|
85
|
+
:account-menu="shell.accountMenu"
|
|
86
|
+
:show-notifications="shell.showNotifications"
|
|
87
|
+
:has-notification-dot="shell.hasNotificationDot"
|
|
88
|
+
:show-admin="shell.showAdmin"
|
|
89
|
+
:admin-path="shell.adminPath"
|
|
90
|
+
:user-name="shell.userName"
|
|
91
|
+
:user-initial="shell.userInitial"
|
|
92
|
+
:user-email="shell.userEmail"
|
|
93
|
+
@page-selector-select="emit('page-selector-select', $event)"
|
|
94
|
+
@pill-select="emit('pill-select', $event)"
|
|
95
|
+
@pill-option-select="handlePillOptionSelect"
|
|
96
|
+
@plugin-switcher-select="emit('plugin-switcher-select', $event)"
|
|
97
|
+
@plugin-switcher-install="emit('plugin-switcher-install')"
|
|
98
|
+
@account-menu-select="emit('account-menu-select', $event)"
|
|
99
|
+
@sign-out="emit('sign-out')"
|
|
100
|
+
@notifications-click="emit('notifications-click')"
|
|
101
|
+
@settings-values-change="emit('settings-values-change', $event)"
|
|
102
|
+
@admin-click="emit('admin-click')"
|
|
103
|
+
>
|
|
104
|
+
<template
|
|
105
|
+
v-for="slotName in shell.topBarSlotNames"
|
|
106
|
+
:key="slotName"
|
|
107
|
+
#[slotName]="slotProps"
|
|
108
|
+
>
|
|
109
|
+
<!-- @vue-ignore dynamic SDK slot forwarding for AppTopBar passthrough slots -->
|
|
110
|
+
<slot
|
|
111
|
+
:name="slotName"
|
|
112
|
+
v-bind="slotProps ?? {}"
|
|
113
|
+
/>
|
|
114
|
+
</template>
|
|
115
|
+
</AppTopBar>
|
|
116
|
+
</slot>
|
|
117
|
+
</template>
|
|
118
|
+
|
|
119
|
+
<template v-if="shell.hasSidebarSurface" #sidebar>
|
|
120
|
+
<slot name="sidebar" v-bind="shell.controls" />
|
|
121
|
+
</template>
|
|
122
|
+
|
|
123
|
+
<slot v-bind="shell.controls" />
|
|
124
|
+
</AppLayout>
|
|
125
|
+
</template>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Internal: AppSidebar for PluginWorkspaceView, driven by a single `sidebar` model covering
|
|
4
|
+
* chrome, panels/forms, collapse behavior, and the compact control-schema props that drive
|
|
5
|
+
* AppSidebar's auto-generated form.
|
|
6
|
+
* Not part of the public SDK surface — see PluginWorkspaceView.vue for the public component.
|
|
7
|
+
*/
|
|
8
|
+
import AppSidebar from '../AppSidebar.vue'
|
|
9
|
+
import type { PluginWorkspaceSidebarModel } from '../../composables/pluginWorkspaceModel'
|
|
10
|
+
|
|
11
|
+
defineProps<{
|
|
12
|
+
sidebar: PluginWorkspaceSidebarModel
|
|
13
|
+
}>()
|
|
14
|
+
|
|
15
|
+
defineEmits<{
|
|
16
|
+
'update:modelValue': [values: Record<string, unknown>]
|
|
17
|
+
'update:collapsed': [value: boolean]
|
|
18
|
+
'update:toggle': [sectionId: string, value: boolean]
|
|
19
|
+
'section-action': [sectionId: string, actionId: string]
|
|
20
|
+
'form-submit': [sectionId: string, values: Record<string, unknown>]
|
|
21
|
+
'form-cancel': [sectionId: string]
|
|
22
|
+
}>()
|
|
23
|
+
|
|
24
|
+
defineSlots<{
|
|
25
|
+
header?: (props: Record<string, unknown>) => unknown
|
|
26
|
+
collapsed?: (props: Record<string, unknown>) => unknown
|
|
27
|
+
'collapsed-footer'?: (props: Record<string, unknown>) => unknown
|
|
28
|
+
footer?: (props: Record<string, unknown>) => unknown
|
|
29
|
+
}>()
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<AppSidebar
|
|
34
|
+
:title="sidebar.title"
|
|
35
|
+
:subtitle="sidebar.subtitle"
|
|
36
|
+
:badge="sidebar.badge"
|
|
37
|
+
:variant="sidebar.variant"
|
|
38
|
+
:panels="sidebar.panels"
|
|
39
|
+
:active-view="sidebar.activeView"
|
|
40
|
+
:floating="sidebar.floating"
|
|
41
|
+
:dense="sidebar.dense"
|
|
42
|
+
:width="sidebar.width"
|
|
43
|
+
:side="sidebar.side"
|
|
44
|
+
:forms="sidebar.forms"
|
|
45
|
+
:content-id="sidebar.contentId"
|
|
46
|
+
:show-when-empty="sidebar.showWhenEmpty"
|
|
47
|
+
:model="sidebar.model"
|
|
48
|
+
:controls="sidebar.controlSchema"
|
|
49
|
+
:control-options="sidebar.controlOptions"
|
|
50
|
+
:values="sidebar.values"
|
|
51
|
+
:form-enhancements="sidebar.formEnhancements"
|
|
52
|
+
:show-form-actions="sidebar.showFormActions"
|
|
53
|
+
:form-loading="sidebar.formLoading"
|
|
54
|
+
:form-disabled="sidebar.formDisabled"
|
|
55
|
+
:form-readonly="sidebar.formReadonly"
|
|
56
|
+
:form-size="sidebar.formSize"
|
|
57
|
+
:collapsible="sidebar.collapsible"
|
|
58
|
+
:collapsed="sidebar.collapsed"
|
|
59
|
+
:default-collapsed="sidebar.defaultCollapsed"
|
|
60
|
+
:collapsed-width="sidebar.collapsedWidth"
|
|
61
|
+
:collapse-button-label="sidebar.collapseButtonLabel"
|
|
62
|
+
:expand-button-label="sidebar.expandButtonLabel"
|
|
63
|
+
@update:model-value="$emit('update:modelValue', $event)"
|
|
64
|
+
@update:collapsed="$emit('update:collapsed', $event)"
|
|
65
|
+
@update:toggle="(sectionId, value) => $emit('update:toggle', sectionId, value)"
|
|
66
|
+
@section-action="(sectionId, actionId) => $emit('section-action', sectionId, actionId)"
|
|
67
|
+
@form-submit="(sectionId, values) => $emit('form-submit', sectionId, values)"
|
|
68
|
+
@form-cancel="$emit('form-cancel', $event)"
|
|
69
|
+
>
|
|
70
|
+
<template
|
|
71
|
+
v-for="slotName in sidebar.slotNames"
|
|
72
|
+
:key="slotName"
|
|
73
|
+
#[slotName]="slotProps"
|
|
74
|
+
>
|
|
75
|
+
<!-- @vue-ignore dynamic SDK slot forwarding for AppSidebar section-* passthrough slots -->
|
|
76
|
+
<slot
|
|
77
|
+
:name="slotName"
|
|
78
|
+
v-bind="slotProps ?? {}"
|
|
79
|
+
/>
|
|
80
|
+
</template>
|
|
81
|
+
</AppSidebar>
|
|
82
|
+
</template>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SelectOption } from '../types'
|
|
2
|
+
import { isRecord } from '../utils/records'
|
|
2
3
|
|
|
3
4
|
export interface ExperimentDesignDataResponse {
|
|
4
5
|
experiment_id?: number
|
|
@@ -12,9 +13,6 @@ export interface ExtractExperimentSamplesOptions {
|
|
|
12
13
|
includeControls?: boolean
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
16
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
17
|
-
}
|
|
18
16
|
|
|
19
17
|
function isPlatformDesignDataResponse(value: Record<string, unknown>): boolean {
|
|
20
18
|
return (
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { computed } from 'vue'
|
|
2
|
+
import type { ComputedRef } from 'vue'
|
|
3
|
+
import type {
|
|
4
|
+
BioTemplateComponentBinding,
|
|
5
|
+
BioTemplateComponentBindingsById,
|
|
6
|
+
BioTemplateComponentImport,
|
|
7
|
+
BioTemplateComponentPropsByComponent,
|
|
8
|
+
BioTemplateComponentPropsBinding,
|
|
9
|
+
BioTemplateComponentPropsById,
|
|
10
|
+
BioTemplateComponentPropsLookupOptions,
|
|
11
|
+
BioTemplateComponentSnippet,
|
|
12
|
+
BioTemplateComponentUsage,
|
|
13
|
+
} from './useBioTemplateComponents'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The template-component slice of `UseBioTemplateWorkspaceReturn`: the ~9 keys every
|
|
17
|
+
* bio-template workspace composable (`useBioTemplateWorkspace`, `useBioTemplatePackWorkspace`,
|
|
18
|
+
* `useBioTemplatePresetWorkspace`) re-exposes verbatim from `useBioTemplateComponents()`.
|
|
19
|
+
*/
|
|
20
|
+
export interface BioTemplateWorkspaceComponentBindings {
|
|
21
|
+
componentBindings: BioTemplateComponentBinding[]
|
|
22
|
+
componentBindingsById: BioTemplateComponentBindingsById
|
|
23
|
+
componentImports: BioTemplateComponentImport[]
|
|
24
|
+
componentProps: BioTemplateComponentPropsBinding[]
|
|
25
|
+
componentPropsById: BioTemplateComponentPropsById
|
|
26
|
+
componentPropsByComponent: BioTemplateComponentPropsByComponent
|
|
27
|
+
componentSnippets: BioTemplateComponentSnippet[]
|
|
28
|
+
componentUsage: BioTemplateComponentUsage | null
|
|
29
|
+
getComponentProps: (component: string, options?: BioTemplateComponentPropsLookupOptions) => Record<string, unknown> | undefined
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Structural source shape accepted by {@link flattenWorkspaceComponentBindings}. */
|
|
33
|
+
export interface BioTemplateWorkspaceComponentSource {
|
|
34
|
+
componentBindings: BioTemplateComponentBinding[]
|
|
35
|
+
componentBindingsById: BioTemplateComponentBindingsById
|
|
36
|
+
componentImports: BioTemplateComponentImport[]
|
|
37
|
+
componentProps: BioTemplateComponentPropsBinding[]
|
|
38
|
+
componentPropsById: BioTemplateComponentPropsById
|
|
39
|
+
componentPropsByComponent: BioTemplateComponentPropsByComponent
|
|
40
|
+
componentSnippets: BioTemplateComponentSnippet[]
|
|
41
|
+
componentUsage: BioTemplateComponentUsage | null
|
|
42
|
+
getComponentProps: (component: string, options?: BioTemplateComponentPropsLookupOptions) => Record<string, unknown> | undefined
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Pick the shared template-component binding keys off a resolved bio-template workspace
|
|
47
|
+
* (or its `components` source directly), so composables that build on `useBioTemplateWorkspace`
|
|
48
|
+
* spread one object instead of re-listing all 9 keys by hand.
|
|
49
|
+
*
|
|
50
|
+
* @example Recommended
|
|
51
|
+
* ```ts
|
|
52
|
+
* const bindings = { renderer, ...flattenWorkspaceComponentBindings(components) }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function flattenWorkspaceComponentBindings(
|
|
56
|
+
source: BioTemplateWorkspaceComponentSource,
|
|
57
|
+
): BioTemplateWorkspaceComponentBindings {
|
|
58
|
+
return {
|
|
59
|
+
componentBindings: source.componentBindings,
|
|
60
|
+
componentBindingsById: source.componentBindingsById,
|
|
61
|
+
componentImports: source.componentImports,
|
|
62
|
+
componentProps: source.componentProps,
|
|
63
|
+
componentPropsById: source.componentPropsById,
|
|
64
|
+
componentPropsByComponent: source.componentPropsByComponent,
|
|
65
|
+
componentSnippets: source.componentSnippets,
|
|
66
|
+
componentUsage: source.componentUsage,
|
|
67
|
+
getComponentProps: source.getComponentProps,
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Keys of {@link BioTemplateWorkspaceComponentBindings} that are plain (non-reactive) values, lifted to `ComputedRef`s by {@link liftWorkspaceComponentBindings}. */
|
|
72
|
+
export type LiftedBioTemplateWorkspaceComponentBindings =
|
|
73
|
+
{ [K in Exclude<keyof BioTemplateWorkspaceComponentBindings, 'getComponentProps'>]: ComputedRef<BioTemplateWorkspaceComponentBindings[K]> }
|
|
74
|
+
& { getComponentProps: BioTemplateWorkspaceComponentBindings['getComponentProps'] }
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* `computed()`-lifted version of {@link flattenWorkspaceComponentBindings} for composables
|
|
78
|
+
* that hold their resolved workspace behind a `ComputedRef` (e.g. a regenerated workspace
|
|
79
|
+
* swapped in via `shallowRef`/`effectScope`), so each key stays independently reactive
|
|
80
|
+
* instead of forcing consumers to unwrap the whole workspace to read one field.
|
|
81
|
+
* `getComponentProps` stays a plain callable function, matching the un-lifted composables.
|
|
82
|
+
*
|
|
83
|
+
* @example Recommended
|
|
84
|
+
* ```ts
|
|
85
|
+
* const workspace = computed(() => workspaceRef.value)
|
|
86
|
+
* const { componentBindings, componentProps, getComponentProps } = liftWorkspaceComponentBindings(workspace)
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export function liftWorkspaceComponentBindings<TSource extends BioTemplateWorkspaceComponentSource>(
|
|
90
|
+
workspace: ComputedRef<TSource>,
|
|
91
|
+
): LiftedBioTemplateWorkspaceComponentBindings {
|
|
92
|
+
return {
|
|
93
|
+
componentBindings: computed(() => workspace.value.componentBindings),
|
|
94
|
+
componentBindingsById: computed(() => workspace.value.componentBindingsById),
|
|
95
|
+
componentImports: computed(() => workspace.value.componentImports),
|
|
96
|
+
componentProps: computed(() => workspace.value.componentProps),
|
|
97
|
+
componentPropsById: computed(() => workspace.value.componentPropsById),
|
|
98
|
+
componentPropsByComponent: computed(() => workspace.value.componentPropsByComponent),
|
|
99
|
+
componentSnippets: computed(() => workspace.value.componentSnippets),
|
|
100
|
+
componentUsage: computed(() => workspace.value.componentUsage),
|
|
101
|
+
getComponentProps: (component: string, options?: BioTemplateComponentPropsLookupOptions) =>
|
|
102
|
+
workspace.value.getComponentProps(component, options),
|
|
103
|
+
}
|
|
104
|
+
}
|