@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,267 @@
|
|
|
1
|
+
import type { TopBarVariant } from '../types'
|
|
2
|
+
import type { FormEnhancements } from '../types/form-builder'
|
|
3
|
+
import type {
|
|
4
|
+
ControlModel,
|
|
5
|
+
ControlModelBinding,
|
|
6
|
+
ControlSchema,
|
|
7
|
+
ControlWorkspaceOptions,
|
|
8
|
+
UseControlWorkspaceReturn,
|
|
9
|
+
} from './useControlSchema'
|
|
10
|
+
import { mergeControlWorkspaceOptions, resolveControlModel } from './useControlSchema'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Merges `initialValues`/external `modelValue`/`values` on top of a base
|
|
14
|
+
* `ControlWorkspaceOptions.initialValues`, matching the layering FormBuilder expects: base
|
|
15
|
+
* defaults, then caller-supplied initial values, then live external values win last. Shared by
|
|
16
|
+
* {@link controlWorkspaceProps} and {@link bioTemplatePresetWorkspaceProps} so both "generate a
|
|
17
|
+
* workspace from options, initialValues, and v-model" views merge options identically.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveControlOptionsWithExternalValues<TOptions extends ControlWorkspaceOptions>(
|
|
20
|
+
base: TOptions,
|
|
21
|
+
initialValues: Record<string, unknown> | undefined,
|
|
22
|
+
externalValues: Record<string, unknown> | undefined,
|
|
23
|
+
): TOptions {
|
|
24
|
+
if (initialValues === undefined && externalValues === undefined) return base
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
...base,
|
|
28
|
+
initialValues: {
|
|
29
|
+
...(base.initialValues ?? {}),
|
|
30
|
+
...(initialValues ?? {}),
|
|
31
|
+
...(externalValues ?? {}),
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ControlWorkspaceModelProps {
|
|
37
|
+
model?: ControlModel | ControlModelBinding
|
|
38
|
+
controls?: ControlSchema
|
|
39
|
+
controlOptions?: ControlWorkspaceOptions
|
|
40
|
+
initialValues?: Record<string, unknown>
|
|
41
|
+
modelValue?: Record<string, unknown>
|
|
42
|
+
values?: Record<string, unknown>
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ResolvedControlWorkspaceModel {
|
|
46
|
+
/** `modelValue` if provided, else `values` — the single external v-model source of truth. */
|
|
47
|
+
externalValues: Record<string, unknown> | undefined
|
|
48
|
+
/** `model` resolved through `resolveControlModel()`, or undefined for a raw `controls` schema. */
|
|
49
|
+
resolvedModel: ControlModelBinding | undefined
|
|
50
|
+
/** `controls` if provided, else the resolved model's controls, else `{}`. */
|
|
51
|
+
resolvedControls: ControlSchema
|
|
52
|
+
/** `controlOptions` merged over the resolved model's own options, with initialValues/external values layered on top. */
|
|
53
|
+
resolvedControlOptions: ControlWorkspaceOptions
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Resolves ControlWorkspaceView's `model`/`controls`/`controlOptions`/`initialValues`/
|
|
58
|
+
* `modelValue`/`values` props into the schema + options pair `useControlWorkspace()` needs.
|
|
59
|
+
* Pure and framework-free so callers wrap each field in their own `computed()`.
|
|
60
|
+
*
|
|
61
|
+
* @example Recommended
|
|
62
|
+
* ```ts
|
|
63
|
+
* const resolved = computed(() => controlWorkspaceProps(props))
|
|
64
|
+
* const resolvedControls = computed(() => resolved.value.resolvedControls)
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export function controlWorkspaceProps(props: ControlWorkspaceModelProps): ResolvedControlWorkspaceModel {
|
|
68
|
+
const externalValues = props.modelValue ?? props.values
|
|
69
|
+
const resolvedModel = resolveControlModel(props.model)
|
|
70
|
+
const resolvedControls: ControlSchema = props.controls ?? resolvedModel?.controls ?? {}
|
|
71
|
+
const baseControlOptions = mergeControlWorkspaceOptions(resolvedModel?.controlOptions, props.controlOptions)
|
|
72
|
+
const resolvedControlOptions = resolveControlOptionsWithExternalValues(
|
|
73
|
+
baseControlOptions,
|
|
74
|
+
props.initialValues,
|
|
75
|
+
externalValues,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return { externalValues, resolvedModel, resolvedControls, resolvedControlOptions }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface DoseDesignWorkspacePassthroughProps {
|
|
82
|
+
workspace?: UseControlWorkspaceReturn<ControlSchema>
|
|
83
|
+
controlOptions?: ControlWorkspaceOptions
|
|
84
|
+
initialValues?: Record<string, unknown>
|
|
85
|
+
modelValue?: Record<string, unknown>
|
|
86
|
+
values?: Record<string, unknown>
|
|
87
|
+
title?: string
|
|
88
|
+
subtitle?: string
|
|
89
|
+
topBarVariant?: TopBarVariant
|
|
90
|
+
sidebarWidth?: string
|
|
91
|
+
sidebarVariant?: 'default' | 'analysis'
|
|
92
|
+
responsiveSidebar?: boolean
|
|
93
|
+
sidebarPosition?: 'left' | 'right'
|
|
94
|
+
sidebarTitle?: string
|
|
95
|
+
sidebarSubtitle?: string
|
|
96
|
+
sidebarBadge?: string | number
|
|
97
|
+
floating?: boolean
|
|
98
|
+
dense?: boolean
|
|
99
|
+
showSettings?: boolean
|
|
100
|
+
showFormActions?: boolean
|
|
101
|
+
formEnhancements?: FormEnhancements<Record<string, unknown>>
|
|
102
|
+
formLoading?: boolean
|
|
103
|
+
formDisabled?: boolean
|
|
104
|
+
formReadonly?: boolean
|
|
105
|
+
formSize?: 'sm' | 'md' | 'lg'
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Maps DoseDesignWorkspaceView's props straight through to the identically-named
|
|
110
|
+
* ControlWorkspaceView props it wraps — the ~25-key pass-through list that used to be spelled
|
|
111
|
+
* out attribute-by-attribute in the template. `model` is the caller's already-resolved dose
|
|
112
|
+
* design model, not the raw (possibly absent) `props.model`.
|
|
113
|
+
*
|
|
114
|
+
* @example Recommended
|
|
115
|
+
* ```vue
|
|
116
|
+
* <ControlWorkspaceView v-bind="doseDesignWorkspaceProps(props, resolvedModel)" />
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
export function doseDesignWorkspaceProps(
|
|
120
|
+
props: DoseDesignWorkspacePassthroughProps,
|
|
121
|
+
model: ControlModel | ControlModelBinding,
|
|
122
|
+
) {
|
|
123
|
+
return {
|
|
124
|
+
model,
|
|
125
|
+
workspace: props.workspace,
|
|
126
|
+
controlOptions: props.controlOptions,
|
|
127
|
+
initialValues: props.initialValues,
|
|
128
|
+
modelValue: props.modelValue,
|
|
129
|
+
values: props.values,
|
|
130
|
+
title: props.title,
|
|
131
|
+
subtitle: props.subtitle,
|
|
132
|
+
topBarVariant: props.topBarVariant,
|
|
133
|
+
sidebarWidth: props.sidebarWidth,
|
|
134
|
+
sidebarVariant: props.sidebarVariant,
|
|
135
|
+
responsiveSidebar: props.responsiveSidebar,
|
|
136
|
+
sidebarPosition: props.sidebarPosition,
|
|
137
|
+
sidebarTitle: props.sidebarTitle,
|
|
138
|
+
sidebarSubtitle: props.sidebarSubtitle,
|
|
139
|
+
sidebarBadge: props.sidebarBadge,
|
|
140
|
+
floating: props.floating,
|
|
141
|
+
dense: props.dense,
|
|
142
|
+
showSettings: props.showSettings,
|
|
143
|
+
showFormActions: props.showFormActions,
|
|
144
|
+
formEnhancements: props.formEnhancements,
|
|
145
|
+
formLoading: props.formLoading,
|
|
146
|
+
formDisabled: props.formDisabled,
|
|
147
|
+
formReadonly: props.formReadonly,
|
|
148
|
+
formSize: props.formSize,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface BioTemplateExperimentWorkspaceStatusProps {
|
|
153
|
+
status?: {
|
|
154
|
+
loading?: boolean
|
|
155
|
+
saving?: boolean
|
|
156
|
+
error?: string | null
|
|
157
|
+
hasExperiment?: boolean
|
|
158
|
+
currentExperimentId?: number
|
|
159
|
+
lastLoadedAt?: Date | null
|
|
160
|
+
lastSavedAt?: Date | null
|
|
161
|
+
}
|
|
162
|
+
loading?: boolean
|
|
163
|
+
error?: string | null
|
|
164
|
+
hasExperiment?: boolean
|
|
165
|
+
currentExperimentId?: number
|
|
166
|
+
lastLoadedAt?: Date | null
|
|
167
|
+
lastSavedAt?: Date | null
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface ResolvedBioTemplateExperimentStatus {
|
|
171
|
+
loading: boolean
|
|
172
|
+
error: string | null | undefined
|
|
173
|
+
hasExperiment: boolean
|
|
174
|
+
currentExperimentId: number | undefined
|
|
175
|
+
lastLoadedAt: Date | null | undefined
|
|
176
|
+
lastSavedAt: Date | null | undefined
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Merges BioTemplateExperimentWorkspaceView's grouped `status` prop over its individual
|
|
181
|
+
* `loading`/`error`/`hasExperiment`/... props: `status` wins whenever the corresponding field
|
|
182
|
+
* on it is set, otherwise the flat prop (or its default) is used. Centralizes the six
|
|
183
|
+
* independent fallback rules generated plugin pages rely on when they pass a request/experiment
|
|
184
|
+
* `status` object instead of wiring each field by hand.
|
|
185
|
+
*/
|
|
186
|
+
export function bioTemplateExperimentWorkspaceProps(
|
|
187
|
+
props: BioTemplateExperimentWorkspaceStatusProps,
|
|
188
|
+
): ResolvedBioTemplateExperimentStatus {
|
|
189
|
+
const status = props.status
|
|
190
|
+
return {
|
|
191
|
+
loading: status?.loading !== undefined || status?.saving !== undefined
|
|
192
|
+
? Boolean(status?.loading) || Boolean(status?.saving)
|
|
193
|
+
: Boolean(props.loading),
|
|
194
|
+
error: status?.error !== undefined ? status.error : props.error,
|
|
195
|
+
hasExperiment: status?.hasExperiment ?? props.hasExperiment ?? false,
|
|
196
|
+
currentExperimentId: status?.currentExperimentId ?? props.currentExperimentId,
|
|
197
|
+
lastLoadedAt: status?.lastLoadedAt !== undefined ? status.lastLoadedAt : props.lastLoadedAt,
|
|
198
|
+
lastSavedAt: status?.lastSavedAt !== undefined ? status.lastSavedAt : props.lastSavedAt,
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export interface BioTemplatePackWorkspacePassthroughProps {
|
|
203
|
+
message?: string
|
|
204
|
+
dense?: boolean
|
|
205
|
+
readonly?: boolean
|
|
206
|
+
showStatus?: boolean
|
|
207
|
+
showTemplateSummary?: boolean
|
|
208
|
+
loadLabel?: string
|
|
209
|
+
resetLabel?: string
|
|
210
|
+
saveLabel?: string
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Assembles the prop object BioTemplatePackWorkspaceView binds onto the
|
|
215
|
+
* BioTemplateExperimentWorkspaceView it wraps: its own display props passed straight through,
|
|
216
|
+
* plus the pack-derived `target`/`label`/`status`/`actions` computed by the caller.
|
|
217
|
+
*/
|
|
218
|
+
export function bioTemplatePackWorkspaceProps<TTarget, TStatus, TActions>(
|
|
219
|
+
props: BioTemplatePackWorkspacePassthroughProps,
|
|
220
|
+
derived: {
|
|
221
|
+
target: TTarget
|
|
222
|
+
label: string
|
|
223
|
+
status: TStatus
|
|
224
|
+
actions: TActions
|
|
225
|
+
},
|
|
226
|
+
) {
|
|
227
|
+
return {
|
|
228
|
+
target: derived.target,
|
|
229
|
+
kind: 'collection' as const,
|
|
230
|
+
label: derived.label,
|
|
231
|
+
message: props.message,
|
|
232
|
+
status: derived.status,
|
|
233
|
+
actions: derived.actions,
|
|
234
|
+
dense: props.dense,
|
|
235
|
+
readonly: props.readonly,
|
|
236
|
+
showStatus: props.showStatus,
|
|
237
|
+
showTemplateSummary: props.showTemplateSummary,
|
|
238
|
+
loadLabel: props.loadLabel,
|
|
239
|
+
resetLabel: props.resetLabel,
|
|
240
|
+
saveLabel: props.saveLabel,
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export interface BioTemplatePresetWorkspaceOptionsProps<TOptions extends ControlWorkspaceOptions> {
|
|
245
|
+
workspaceOptions?: TOptions
|
|
246
|
+
initialValues?: Record<string, unknown>
|
|
247
|
+
modelValue?: Record<string, unknown>
|
|
248
|
+
values?: Record<string, unknown>
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Resolves BioTemplatePresetWorkspaceView's `workspaceOptions`/`initialValues`/`modelValue`/
|
|
253
|
+
* `values` props into the single options object `useBioTemplatePresetWorkspace()` needs,
|
|
254
|
+
* reusing the same base/initialValues/external-values layering as {@link controlWorkspaceProps}.
|
|
255
|
+
*/
|
|
256
|
+
export function bioTemplatePresetWorkspaceProps<TOptions extends ControlWorkspaceOptions>(
|
|
257
|
+
props: BioTemplatePresetWorkspaceOptionsProps<TOptions>,
|
|
258
|
+
): { externalValues: Record<string, unknown> | undefined; resolvedWorkspaceOptions: TOptions } {
|
|
259
|
+
const externalValues = props.modelValue ?? props.values
|
|
260
|
+
const resolvedWorkspaceOptions = resolveControlOptionsWithExternalValues(
|
|
261
|
+
(props.workspaceOptions ?? {}) as TOptions,
|
|
262
|
+
props.initialValues,
|
|
263
|
+
externalValues,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
return { externalValues, resolvedWorkspaceOptions }
|
|
267
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -41,6 +41,22 @@ export {
|
|
|
41
41
|
deriveShade,
|
|
42
42
|
type Hsl,
|
|
43
43
|
} from './utils/color'
|
|
44
|
+
export {
|
|
45
|
+
PLUGIN_SECRET_FORMAT_KEY,
|
|
46
|
+
PLUGIN_SECRET_FORMAT_REF,
|
|
47
|
+
PLUGIN_SECRET_REF_KEY,
|
|
48
|
+
clearPluginSecret,
|
|
49
|
+
isPluginSecretLocked,
|
|
50
|
+
isPluginSecretRef,
|
|
51
|
+
keepPluginSecret,
|
|
52
|
+
pluginSecretId,
|
|
53
|
+
pluginSecretLabel,
|
|
54
|
+
pluginSecretState,
|
|
55
|
+
setPluginSecret,
|
|
56
|
+
usesPluginSecretReferences,
|
|
57
|
+
type PluginSecretRef,
|
|
58
|
+
type PluginSecretState,
|
|
59
|
+
} from './utils/pluginSecrets'
|
|
44
60
|
export {
|
|
45
61
|
LCMS_DEFAULT_CONTROL_POSITIONS,
|
|
46
62
|
createLcmsControlWellEditData,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* SecretInput — plugin secret settings field with a set / not set / environment badge. */
|
|
2
|
+
|
|
3
|
+
.mint-secret-input {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
gap: 0.5rem;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.mint-secret-input__status {
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
gap: 0.5rem;
|
|
13
|
+
flex-wrap: wrap;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.mint-secret-input__badge {
|
|
17
|
+
display: inline-flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
border-radius: 999px;
|
|
20
|
+
padding: 0.125rem 0.5rem;
|
|
21
|
+
font-size: 0.75rem;
|
|
22
|
+
font-weight: 500;
|
|
23
|
+
line-height: 1.4;
|
|
24
|
+
border: 1px solid transparent;
|
|
25
|
+
background-color: var(--bg-tertiary);
|
|
26
|
+
color: var(--text-secondary);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.mint-secret-input__badge--set {
|
|
30
|
+
background-color: color-mix(in srgb, var(--mint-success) 12%, transparent);
|
|
31
|
+
border-color: color-mix(in srgb, var(--mint-success) 35%, transparent);
|
|
32
|
+
color: var(--mint-success);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.mint-secret-input__badge--environment {
|
|
36
|
+
background-color: color-mix(in srgb, var(--mint-info) 12%, transparent);
|
|
37
|
+
border-color: color-mix(in srgb, var(--mint-info) 35%, transparent);
|
|
38
|
+
color: var(--mint-info);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.mint-secret-input__badge--pending {
|
|
42
|
+
background-color: color-mix(in srgb, var(--mint-warning) 12%, transparent);
|
|
43
|
+
border-color: color-mix(in srgb, var(--mint-warning) 35%, transparent);
|
|
44
|
+
color: var(--mint-warning);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.mint-secret-input__hint {
|
|
48
|
+
font-size: 0.75rem;
|
|
49
|
+
color: var(--text-muted);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.mint-secret-input__actions {
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
gap: 0.5rem;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.mint-secret-input--error .mint-secret-input__badge {
|
|
59
|
+
border-color: color-mix(in srgb, var(--mint-error) 45%, transparent);
|
|
60
|
+
}
|
package/src/styles/index.css
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
@import './components/radio-group.css';
|
|
31
31
|
@import './components/sample-legend.css';
|
|
32
32
|
@import './components/section-card.css';
|
|
33
|
+
@import './components/secret-input.css';
|
|
33
34
|
@import './components/select.css';
|
|
34
35
|
@import './components/skeleton.css';
|
|
35
36
|
@import './components/slider.css';
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
export
|
|
2
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
3
|
-
}
|
|
1
|
+
export { isRecord } from '../utils/records'
|
|
4
2
|
|
|
5
3
|
export function readStringList(value: unknown, fallback: string[]): string[] {
|
|
6
4
|
if (Array.isArray(value)) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isObjectLike as isRecord } from '../utils/records'
|
|
1
2
|
import { extractTemplateCollection } from './templateEnvelopes'
|
|
2
3
|
import { getBioTemplateInfo } from './catalog'
|
|
3
4
|
import { getBioTemplatePackInfo } from './packs'
|
|
@@ -64,6 +65,3 @@ function collectionPackName(value: unknown): string | undefined {
|
|
|
64
65
|
return typeof value.metadata.pack === 'string' ? value.metadata.pack : undefined
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
68
|
-
return typeof value === 'object' && value !== null
|
|
69
|
-
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isObjectLike as isRecord } from '../utils/records'
|
|
1
2
|
import {
|
|
2
3
|
getBioTemplateInfo,
|
|
3
4
|
} from './catalog'
|
|
@@ -82,6 +83,3 @@ function collectionPackName(value: unknown): string | undefined {
|
|
|
82
83
|
return typeof value.metadata.pack === 'string' ? value.metadata.pack : undefined
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
86
|
-
return typeof value === 'object' && value !== null
|
|
87
|
-
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { FormSchema } from '../types/form-builder'
|
|
2
|
+
import { isRecord } from './records'
|
|
2
3
|
|
|
3
4
|
export function recordValuesEqualForKeys(
|
|
4
5
|
left: Record<string, unknown>,
|
|
@@ -47,6 +48,3 @@ function valuesEqual(left: unknown, right: unknown): boolean {
|
|
|
47
48
|
return false
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
51
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
52
|
-
}
|
package/src/utils/pluginIcon.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRecord } from './records'
|
|
1
2
|
export type PluginIconFormat = 'path' | 'data-url' | 'asset-url' | 'structured' | 'fallback'
|
|
2
3
|
|
|
3
4
|
export interface StructuredPluginIconStop {
|
|
@@ -177,9 +178,6 @@ function parseOffset(value: unknown): string | undefined {
|
|
|
177
178
|
return undefined
|
|
178
179
|
}
|
|
179
180
|
|
|
180
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
181
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
182
|
-
}
|
|
183
181
|
|
|
184
182
|
function isSafeColor(value: unknown): value is string {
|
|
185
183
|
return typeof value === 'string' && COLOR_REGEX.test(value)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable references for plugin secret settings.
|
|
3
|
+
*
|
|
4
|
+
* A settings response never carries a secret's value. Every secret leaf is a
|
|
5
|
+
* marker instead:
|
|
6
|
+
*
|
|
7
|
+
* - `{ "$secret": "sec_…", "set": true }` — a stored secret, referenced by a
|
|
8
|
+
* stable opaque id. Send the marker back unchanged to keep the stored value.
|
|
9
|
+
* - `null` — nothing stored at that position.
|
|
10
|
+
* - `{ "$secret": null, "locked": true }` — the effective value comes from the
|
|
11
|
+
* environment and is never written back.
|
|
12
|
+
*
|
|
13
|
+
* On save each secret leaf accepts the reference, a plain string (a new value),
|
|
14
|
+
* or `null` (clear). Payloads carry `secret_format: "ref"` so a `null` reads as
|
|
15
|
+
* a deliberate clear rather than the deprecated "unchanged" placeholder.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** Reserved marker key that references a stored secret. */
|
|
19
|
+
export const PLUGIN_SECRET_REF_KEY = '$secret'
|
|
20
|
+
|
|
21
|
+
/** Reserved payload key declaring which secret wire format a client speaks. */
|
|
22
|
+
export const PLUGIN_SECRET_FORMAT_KEY = 'secret_format'
|
|
23
|
+
|
|
24
|
+
/** Wire format published and understood by this SDK. */
|
|
25
|
+
export const PLUGIN_SECRET_FORMAT_REF = 'ref'
|
|
26
|
+
|
|
27
|
+
/** One `$secret` marker as published by a settings response. */
|
|
28
|
+
export interface PluginSecretRef {
|
|
29
|
+
$secret: string | null
|
|
30
|
+
set?: boolean
|
|
31
|
+
locked?: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** What a settings form should tell the user about one secret field. */
|
|
35
|
+
export type PluginSecretState = 'set' | 'unset' | 'environment'
|
|
36
|
+
|
|
37
|
+
const MARKER_KEYS = new Set([PLUGIN_SECRET_REF_KEY, 'set', 'locked'])
|
|
38
|
+
|
|
39
|
+
/** Return whether one settings value is a `$secret` reference marker. */
|
|
40
|
+
export function isPluginSecretRef(value: unknown): value is PluginSecretRef {
|
|
41
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false
|
|
42
|
+
const record = value as Record<string, unknown>
|
|
43
|
+
if (!(PLUGIN_SECRET_REF_KEY in record)) return false
|
|
44
|
+
return Object.keys(record).every(key => MARKER_KEYS.has(key))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Return the stored secret id behind a marker, or `null` when nothing is stored. */
|
|
48
|
+
export function pluginSecretId(value: unknown): string | null {
|
|
49
|
+
if (!isPluginSecretRef(value)) return null
|
|
50
|
+
return typeof value.$secret === 'string' ? value.$secret : null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Classify one secret field for display: stored, unset, or environment-provided. */
|
|
54
|
+
export function pluginSecretState(value: unknown): PluginSecretState {
|
|
55
|
+
if (isPluginSecretRef(value)) {
|
|
56
|
+
if (value.locked) return 'environment'
|
|
57
|
+
return typeof value.$secret === 'string' ? 'set' : 'unset'
|
|
58
|
+
}
|
|
59
|
+
return typeof value === 'string' && value.length > 0 ? 'set' : 'unset'
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Human label for one secret field's state. */
|
|
63
|
+
export function pluginSecretLabel(value: unknown): string {
|
|
64
|
+
const state = pluginSecretState(value)
|
|
65
|
+
if (state === 'environment') return 'Provided by environment'
|
|
66
|
+
return state === 'set' ? 'Set' : 'Not set'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Whether a secret field is read-only because the environment supplies it. */
|
|
70
|
+
export function isPluginSecretLocked(value: unknown): boolean {
|
|
71
|
+
return isPluginSecretRef(value) && value.locked === true
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Value to submit for an untouched secret field: the reference, unchanged.
|
|
76
|
+
*
|
|
77
|
+
* A locked marker round-trips too, which is how the platform keeps a stored
|
|
78
|
+
* value that an environment variable currently hides.
|
|
79
|
+
*/
|
|
80
|
+
export function keepPluginSecret(current: unknown): PluginSecretRef | null {
|
|
81
|
+
if (isPluginSecretRef(current)) return { ...current }
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Value to submit when the user typed a new secret. */
|
|
86
|
+
export function setPluginSecret(value: string): string {
|
|
87
|
+
return value
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Value to submit when the user cleared a secret. */
|
|
91
|
+
export function clearPluginSecret(): null {
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Whether a settings response declares the `$secret` reference wire format. */
|
|
96
|
+
export function usesPluginSecretReferences(response: unknown): boolean {
|
|
97
|
+
if (!response || typeof response !== 'object') return false
|
|
98
|
+
return (
|
|
99
|
+
(response as Record<string, unknown>)[PLUGIN_SECRET_FORMAT_KEY]
|
|
100
|
+
=== PLUGIN_SECRET_FORMAT_REF
|
|
101
|
+
)
|
|
102
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Narrow an unknown value to a plain object (arrays and null excluded). */
|
|
2
|
+
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
3
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/** Narrow an unknown value to any non-null object, arrays included. */
|
|
7
|
+
export function isObjectLike(value: unknown): value is Record<string, unknown> {
|
|
8
|
+
return typeof value === 'object' && value !== null
|
|
9
|
+
}
|