@morscherlab/mint-sdk 1.0.62 → 1.1.0-beta.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 +101 -2
- package/dist/EmptyState-D1c4RdC7.js +638 -0
- package/dist/EmptyState-D1c4RdC7.js.map +1 -0
- package/dist/{ExperimentPopover-BbPkIFsI.js → ExperimentPopover-BFw3QX8L.js} +10 -9
- package/dist/{ExperimentPopover-BbPkIFsI.js.map → ExperimentPopover-BFw3QX8L.js.map} +1 -1
- package/dist/{ExperimentSelectorModal-DZeSd-yr.js → ExperimentSelectorModal-B7w6k3Y8.js} +12 -216
- package/dist/ExperimentSelectorModal-B7w6k3Y8.js.map +1 -0
- package/dist/{SettingsModal-CJgLadsN.js → SettingsModal-DPDwCbXK.js} +12 -9
- package/dist/{SettingsModal-CJgLadsN.js.map → SettingsModal-DPDwCbXK.js.map} +1 -1
- package/dist/__tests__/components/GeneratedPathInput.test.d.ts +1 -0
- package/dist/__tests__/components/GeneratedPlotlyResult.test.d.ts +1 -0
- package/dist/__tests__/components/GeneratedPluginView.test.d.ts +1 -0
- package/dist/__tests__/components/GeneratedResultRenderer.test.d.ts +1 -0
- package/dist/__tests__/components/InstrumentAlertLog.test.d.ts +1 -0
- package/dist/__tests__/composables/useGeneratedAnalysis.test.d.ts +1 -0
- package/dist/__tests__/generatedUi.test.d.ts +1 -0
- package/dist/{analysisArtifactTypes-Cu40dzSG.js → analysisArtifactTypes-ejqR8MX3.js} +1 -1
- package/dist/{analysisArtifactTypes-Cu40dzSG.js.map → analysisArtifactTypes-ejqR8MX3.js.map} +1 -1
- package/dist/{auth-DZAwEeis.js → auth-B87xM--s.js} +1 -1
- package/dist/{auth-DZAwEeis.js.map → auth-B87xM--s.js.map} +1 -1
- package/dist/chunk-BoAXSpZd.js +33 -0
- package/dist/components/AppSidebar.vue.d.ts +1 -1
- package/dist/components/AutoGroupModal.vue.d.ts +2 -2
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +2 -2
- package/dist/components/CollapsibleCard.vue.d.ts +1 -1
- package/dist/components/ComponentBindingRenderer.vue.d.ts +1 -1
- package/dist/components/ConfirmDialog.vue.d.ts +1 -1
- package/dist/components/ExperimentDataViewer.vue.d.ts +1 -1
- package/dist/components/ExperimentTimeline.vue.d.ts +1 -1
- package/dist/components/FileUploader.vue.d.ts +4 -2
- package/dist/components/FitPanel.vue.d.ts +1 -1
- package/dist/components/FormActions.vue.d.ts +1 -1
- package/dist/components/FormBuilder.vue.d.ts +1 -1
- package/dist/components/GeneratedPathInput.vue.d.ts +12 -0
- package/dist/components/GeneratedPlotlyResult.vue.d.ts +11 -0
- package/dist/components/GeneratedPluginView.vue.d.ts +10 -0
- package/dist/components/GeneratedResultRenderer.vue.d.ts +6 -0
- package/dist/components/JobsStatusTray.vue.d.ts +5 -1
- package/dist/components/LcmsSequenceTable.vue.d.ts +1 -1
- package/dist/components/SampleLegend.vue.d.ts +1 -1
- package/dist/components/SampleSelector.vue.d.ts +2 -2
- package/dist/components/SettingsModal.vue.d.ts +1 -1
- package/dist/components/SmartGroupFieldRecipe.vue.d.ts +2 -2
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.js +8 -9
- package/dist/components/internal/PlateMapEditorToolbarInternal.vue.d.ts +2 -2
- package/dist/{components-CpnOaPbP.js → components-UfkPN1bK.js} +2255 -2303
- package/dist/components-UfkPN1bK.js.map +1 -0
- package/dist/composables/index.d.ts +2 -1
- package/dist/composables/index.js +10 -11
- package/dist/composables/useGeneratedAnalysis.d.ts +62 -0
- package/dist/composables/useJobsStatusTray.d.ts +48 -4
- package/dist/composables/usePluginClient.d.ts +6 -3
- package/dist/{composables-GpJD6R1R.js → composables-F4JFK9K1.js} +22 -13
- package/dist/{composables-GpJD6R1R.js.map → composables-F4JFK9K1.js.map} +1 -1
- package/dist/experiment-utils-CxkruZ3G.js +392 -0
- package/dist/experiment-utils-CxkruZ3G.js.map +1 -0
- package/dist/generated/schema.d.ts +7 -0
- package/dist/generated-ui/main.d.ts +0 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -18
- package/dist/index.js.map +1 -1
- package/dist/install.js +7 -8
- package/dist/install.js.map +1 -1
- package/dist/plotly.min-BR53hja4.js +180634 -0
- package/dist/plotly.min-BR53hja4.js.map +1 -0
- package/dist/stores/auth.d.ts +2 -2
- package/dist/stores/index.js +1 -1
- package/dist/styles.css +10543 -10404
- package/dist/templates/index.js +4 -3
- package/dist/templates/packs.d.ts +0 -1
- package/dist/{templates-CNbPQNID.js → templates-Cttbjbyc.js} +2 -4
- package/dist/templates-Cttbjbyc.js.map +1 -0
- package/dist/types/form-builder.d.ts +1 -1
- package/dist/types/generated-ui.d.ts +170 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -1
- package/dist/types/instrument.d.ts +1 -0
- package/dist/types/jobs.d.ts +7 -1
- package/dist/{useControlSchema-BZNdalmL.js → useControlSchema-Dallso-L.js} +1852 -171
- package/dist/useControlSchema-Dallso-L.js.map +1 -0
- package/dist/{useDropdownState-Bb-c_PeE.js → useDropdownState-gVaMXtwW.js} +2 -2
- package/dist/{useDropdownState-Bb-c_PeE.js.map → useDropdownState-gVaMXtwW.js.map} +1 -1
- package/dist/{useExperimentSelector-DRbj6vse.js → useExperimentSelector-BMjzNcf2.js} +4 -255
- package/dist/useExperimentSelector-BMjzNcf2.js.map +1 -0
- package/dist/{useFormBuilder-DKekvXRs.js → useFormBuilder-BXaaDI41.js} +2 -2
- package/dist/{useFormBuilder-DKekvXRs.js.map → useFormBuilder-BXaaDI41.js.map} +1 -1
- package/dist/{useAnalysisArtifacts-Ob8UtVwl.js → useProtocolTemplates-BP7_4RGQ.js} +307 -449
- package/dist/useProtocolTemplates-BP7_4RGQ.js.map +1 -0
- package/package.json +7 -3
- package/src/__tests__/components/GeneratedPathInput.test.ts +132 -0
- package/src/__tests__/components/GeneratedPlotlyResult.test.ts +117 -0
- package/src/__tests__/components/GeneratedPluginView.test.ts +230 -0
- package/src/__tests__/components/GeneratedResultRenderer.test.ts +93 -0
- package/src/__tests__/components/InstrumentAlertLog.test.ts +59 -0
- package/src/__tests__/composables/formBuilderRegistry.test.ts +5 -1
- package/src/__tests__/composables/useApi.test.ts +8 -0
- package/src/__tests__/composables/useGeneratedAnalysis.test.ts +1740 -0
- package/src/__tests__/composables/useJobsStatusTray.test.ts +179 -1
- package/src/__tests__/composables/usePluginClient.test.ts +37 -0
- package/src/__tests__/docs/extractDocsTemplates.test.ts +0 -1
- package/src/__tests__/generatedUi.test.ts +119 -0
- package/src/__tests__/templates/templates.test.ts +1 -1
- package/src/components/FileUploader.vue +49 -11
- package/src/components/GeneratedPathInput.vue +218 -0
- package/src/components/GeneratedPlotlyResult.vue +87 -0
- package/src/components/GeneratedPluginView.vue +279 -0
- package/src/components/GeneratedResultRenderer.vue +137 -0
- package/src/components/InstrumentAlertLog.vue +2 -2
- package/src/components/JobsStatusTray.vue +14 -8
- package/src/components/index.ts +4 -0
- package/src/composables/formBuilderRegistry.ts +2 -0
- package/src/composables/index.ts +18 -0
- package/src/composables/useApi.ts +1 -0
- package/src/composables/useGeneratedAnalysis.ts +1252 -0
- package/src/composables/useJobsStatusTray.ts +234 -45
- package/src/composables/usePluginClient.ts +34 -10
- package/src/generated/schema.ts +130 -0
- package/src/generated-ui/main.ts +21 -0
- package/src/index.ts +3 -0
- package/src/jobs.ts +5 -0
- package/src/plotly-dist.d.ts +3 -0
- package/src/templates/packs.ts +0 -3
- package/src/types/form-builder.ts +1 -0
- package/src/types/generated-ui.ts +176 -0
- package/src/types/index.ts +2 -0
- package/src/types/instrument.ts +1 -0
- package/src/types/jobs.ts +10 -1
- package/dist/BaseButton-Dgqrze41.js +0 -74
- package/dist/BaseButton-Dgqrze41.js.map +0 -1
- package/dist/BaseModal-B9UA8Y_I.js +0 -165
- package/dist/BaseModal-B9UA8Y_I.js.map +0 -1
- package/dist/BaseSelect-ekgr9fDo.js +0 -179
- package/dist/BaseSelect-ekgr9fDo.js.map +0 -1
- package/dist/ExperimentPopover-Ryusjrhv.js +0 -4
- package/dist/ExperimentSelectorModal-Cg4-LXBD.js +0 -5
- package/dist/ExperimentSelectorModal-DZeSd-yr.js.map +0 -1
- package/dist/SettingsModal-Bl_9w2Y3.js +0 -5
- package/dist/components-CpnOaPbP.js.map +0 -1
- package/dist/experiment-utils-Bfa7CwPU.js +0 -108
- package/dist/experiment-utils-Bfa7CwPU.js.map +0 -1
- package/dist/templates-CNbPQNID.js.map +0 -1
- package/dist/useAnalysisArtifacts-Ob8UtVwl.js.map +0 -1
- package/dist/useControlSchema-BZNdalmL.js.map +0 -1
- package/dist/useEventListener-CfVkP9Xz.js +0 -57
- package/dist/useEventListener-CfVkP9Xz.js.map +0 -1
- package/dist/useExperimentSelector-DRbj6vse.js.map +0 -1
|
@@ -26,6 +26,127 @@ import type {
|
|
|
26
26
|
|
|
27
27
|
export type JobsStatusTrayTab = 'active' | 'history'
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* The canonical owner of a collection of plugin jobs.
|
|
31
|
+
*
|
|
32
|
+
* Sources, rather than job-center UI, own polling, event streams, and all job
|
|
33
|
+
* mutations. This keeps components and consumers on one reactive job graph.
|
|
34
|
+
*/
|
|
35
|
+
export interface PluginJobCenterSource {
|
|
36
|
+
jobs: MaybeRefOrGetter<readonly JobStateInput[]>
|
|
37
|
+
refreshJobs?: () => Promise<JobListPayload | readonly JobStateInput[] | void>
|
|
38
|
+
cancelJob?: (jobId: string) => Promise<JobStateInput | void>
|
|
39
|
+
deleteJob?: (jobId: string) => Promise<boolean | void>
|
|
40
|
+
clearFinishedJobs?: () => Promise<number | void>
|
|
41
|
+
dispose?: () => void
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface CombinedPluginJobCenterSource extends PluginJobCenterSource {
|
|
45
|
+
jobs: ComputedRef<readonly JobStateInput[]>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Combines independent job runtimes without copying their reactive state.
|
|
50
|
+
*
|
|
51
|
+
* Job ids must be globally unique across the sources. A duplicate id throws
|
|
52
|
+
* when jobs are read or an id-routed action is requested, rather than silently
|
|
53
|
+
* dispatching a mutation to the wrong owner.
|
|
54
|
+
*/
|
|
55
|
+
export function combinePluginJobSources(
|
|
56
|
+
sources: readonly PluginJobCenterSource[],
|
|
57
|
+
): CombinedPluginJobCenterSource {
|
|
58
|
+
const jobs = computed<readonly JobStateInput[]>(() => {
|
|
59
|
+
const combined: JobStateInput[] = []
|
|
60
|
+
const owners = new Set<string>()
|
|
61
|
+
for (const source of sources) {
|
|
62
|
+
for (const job of toValue(source.jobs)) {
|
|
63
|
+
if (owners.has(job.job_id)) throw duplicatePluginJobId(job.job_id)
|
|
64
|
+
owners.add(job.job_id)
|
|
65
|
+
combined.push(job)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return combined
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
function ownerFor(jobId: string): PluginJobCenterSource {
|
|
72
|
+
let owner: PluginJobCenterSource | undefined
|
|
73
|
+
for (const source of sources) {
|
|
74
|
+
if (!toValue(source.jobs).some(job => job.job_id === jobId)) continue
|
|
75
|
+
if (owner) throw duplicatePluginJobId(jobId)
|
|
76
|
+
owner = source
|
|
77
|
+
}
|
|
78
|
+
if (!owner) throw new Error(`Unknown plugin job id: ${jobId}`)
|
|
79
|
+
return owner
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const combined: CombinedPluginJobCenterSource = { jobs }
|
|
83
|
+
if (sources.some(source => source.refreshJobs)) {
|
|
84
|
+
combined.refreshJobs = async () => {
|
|
85
|
+
await Promise.all(sources.map(source => source.refreshJobs?.()))
|
|
86
|
+
return jobs.value
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (sources.some(source => source.cancelJob)) {
|
|
90
|
+
combined.cancelJob = async (jobId) => {
|
|
91
|
+
const owner = ownerFor(jobId)
|
|
92
|
+
if (!owner.cancelJob) throw new Error(`Job ${jobId} cannot be cancelled by its source`)
|
|
93
|
+
return owner.cancelJob(jobId)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (sources.length > 0 && sources.every(source => source.deleteJob)) {
|
|
97
|
+
combined.deleteJob = async (jobId) => {
|
|
98
|
+
const owner = ownerFor(jobId)
|
|
99
|
+
if (!owner.deleteJob) throw new Error(`Job ${jobId} cannot be deleted by its source`)
|
|
100
|
+
return owner.deleteJob(jobId)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Never report a successful combined clear after mutating only a subset.
|
|
104
|
+
// Without bulk support from every source, the job center can still fall
|
|
105
|
+
// back to this source's id-routed deleteJob method when all owners expose it.
|
|
106
|
+
if (sources.length > 0 && sources.every(source => source.clearFinishedJobs)) {
|
|
107
|
+
combined.clearFinishedJobs = async () => {
|
|
108
|
+
const results = await Promise.all(
|
|
109
|
+
sources.map(source => source.clearFinishedJobs!()),
|
|
110
|
+
)
|
|
111
|
+
return results.every((result): result is number => typeof result === 'number')
|
|
112
|
+
? results.reduce((total, result) => total + result, 0)
|
|
113
|
+
: undefined
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (sources.some(source => source.dispose)) {
|
|
117
|
+
combined.dispose = () => {
|
|
118
|
+
const errors: unknown[] = []
|
|
119
|
+
for (const source of sources) {
|
|
120
|
+
try {
|
|
121
|
+
source.dispose?.()
|
|
122
|
+
} catch (cause) {
|
|
123
|
+
errors.push(cause)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (errors.length === 1) throw errors[0]
|
|
127
|
+
if (errors.length > 1) throw new PluginJobSourceDisposeError(errors)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return combined
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
class PluginJobSourceDisposeError extends Error {
|
|
134
|
+
readonly cause: unknown
|
|
135
|
+
readonly errors: readonly unknown[]
|
|
136
|
+
|
|
137
|
+
constructor(errors: readonly unknown[]) {
|
|
138
|
+
super('Failed to dispose plugin job sources')
|
|
139
|
+
this.name = 'PluginJobSourceDisposeError'
|
|
140
|
+
this.cause = errors[0]
|
|
141
|
+
this.errors = Object.freeze([...errors])
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function duplicatePluginJobId(jobId: string): Error {
|
|
146
|
+
return new Error(`Duplicate plugin job id across combined sources: ${jobId}`)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** @deprecated Prefer a {@link PluginJobCenterSource}. */
|
|
29
150
|
export interface JobsStatusTrayAdapter {
|
|
30
151
|
listJobs?: () => Promise<JobListPayload | readonly JobStateInput[]>
|
|
31
152
|
cancelJob?: (job: JobState) => Promise<JobStateInput | void>
|
|
@@ -33,24 +154,34 @@ export interface JobsStatusTrayAdapter {
|
|
|
33
154
|
clearFinishedJobs?: (jobs: readonly JobState[]) => Promise<number | void>
|
|
34
155
|
}
|
|
35
156
|
|
|
36
|
-
export interface
|
|
157
|
+
export interface UsePluginJobCenterOptions {
|
|
158
|
+
source?: MaybeRefOrGetter<PluginJobCenterSource | undefined>
|
|
159
|
+
/** @deprecated Pass a source with its canonical jobs ref. */
|
|
37
160
|
jobs?: MaybeRefOrGetter<readonly JobStateInput[] | undefined>
|
|
161
|
+
/** @deprecated Pass source actions instead. */
|
|
38
162
|
adapter?: MaybeRefOrGetter<JobsStatusTrayAdapter | undefined>
|
|
163
|
+
/** @deprecated Event handling belongs to the source runtime. */
|
|
39
164
|
eventStream?: MaybeRefOrGetter<UsePluginEventStreamReturn<JobStreamData> | undefined>
|
|
40
165
|
loadOnMount?: boolean
|
|
41
166
|
}
|
|
42
167
|
|
|
168
|
+
export interface UseJobsStatusTrayOptions extends UsePluginJobCenterOptions {}
|
|
169
|
+
|
|
43
170
|
export interface JobStreamMessage {
|
|
44
171
|
event: string
|
|
45
172
|
data: JobStreamData
|
|
46
173
|
}
|
|
47
174
|
|
|
48
|
-
export interface
|
|
49
|
-
jobs:
|
|
175
|
+
export interface UsePluginJobCenterReturn {
|
|
176
|
+
jobs: ComputedRef<JobState[]>
|
|
50
177
|
activeJobs: ComputedRef<JobState[]>
|
|
51
178
|
historyJobs: ComputedRef<JobState[]>
|
|
52
179
|
deletableJobs: ComputedRef<JobState[]>
|
|
53
180
|
featuredJob: ComputedRef<JobState | null>
|
|
181
|
+
canRefresh: ComputedRef<boolean>
|
|
182
|
+
canCancel: ComputedRef<boolean>
|
|
183
|
+
canDelete: ComputedRef<boolean>
|
|
184
|
+
canClearFinished: ComputedRef<boolean>
|
|
54
185
|
isOpen: Ref<boolean>
|
|
55
186
|
activeTab: Ref<JobsStatusTrayTab>
|
|
56
187
|
isLoading: Ref<boolean>
|
|
@@ -67,6 +198,13 @@ export interface UseJobsStatusTrayReturn {
|
|
|
67
198
|
isPending: (job: JobState) => boolean
|
|
68
199
|
}
|
|
69
200
|
|
|
201
|
+
export type UseJobsStatusTrayReturn = UsePluginJobCenterReturn
|
|
202
|
+
|
|
203
|
+
interface LegacyJobsOverride {
|
|
204
|
+
readonly input: readonly JobStateInput[] | undefined
|
|
205
|
+
readonly jobs: JobState[]
|
|
206
|
+
}
|
|
207
|
+
|
|
70
208
|
function responseJobs(
|
|
71
209
|
response: JobListPayload | readonly JobStateInput[],
|
|
72
210
|
): readonly JobStateInput[] {
|
|
@@ -81,11 +219,16 @@ function errorMessage(error: unknown, fallback: string): string {
|
|
|
81
219
|
return error instanceof Error ? error.message : fallback
|
|
82
220
|
}
|
|
83
221
|
|
|
84
|
-
/**
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
222
|
+
/**
|
|
223
|
+
* Headless controller for plugin job presentation and actions.
|
|
224
|
+
*
|
|
225
|
+
* When a source is supplied, every job list is derived directly from that
|
|
226
|
+
* source. The controller never mirrors source jobs into another ref.
|
|
227
|
+
*/
|
|
228
|
+
export function usePluginJobCenter(
|
|
229
|
+
options: UsePluginJobCenterOptions = {},
|
|
230
|
+
): UsePluginJobCenterReturn {
|
|
231
|
+
const legacyOverride = shallowRef<LegacyJobsOverride | null>(null)
|
|
89
232
|
const isOpen = ref(false)
|
|
90
233
|
const activeTab = ref<JobsStatusTrayTab>('active')
|
|
91
234
|
const isLoading = ref(false)
|
|
@@ -95,44 +238,70 @@ export function useJobsStatusTray(
|
|
|
95
238
|
let stateRevision = 0
|
|
96
239
|
let requestGeneration = 0
|
|
97
240
|
let active = true
|
|
241
|
+
const source = computed(() => toValue(options.source))
|
|
98
242
|
const adapter = computed(() => toValue(options.adapter))
|
|
99
243
|
|
|
244
|
+
const jobs = computed<JobState[]>(() => {
|
|
245
|
+
const currentSource = source.value
|
|
246
|
+
if (currentSource) {
|
|
247
|
+
return [...toValue(currentSource.jobs)].map(normalizeJobState)
|
|
248
|
+
}
|
|
249
|
+
const input = toValue(options.jobs)
|
|
250
|
+
const override = legacyOverride.value
|
|
251
|
+
if (override && override.input === input) return override.jobs
|
|
252
|
+
return input ? [...input].map(normalizeJobState) : []
|
|
253
|
+
})
|
|
100
254
|
const activeJobs = computed(() => jobs.value.filter(job => isActiveJobStatus(job.status)))
|
|
101
255
|
const historyJobs = computed(() => jobs.value.filter(job => !isActiveJobStatus(job.status)))
|
|
102
256
|
const deletableJobs = computed(() => historyJobs.value.filter(job => job.can_delete))
|
|
103
257
|
const featuredJob = computed(() => activeJobs.value[0] ?? historyJobs.value[0] ?? null)
|
|
258
|
+
const canRefresh = computed(() => Boolean(source.value?.refreshJobs || adapter.value?.listJobs))
|
|
259
|
+
const canCancel = computed(() => Boolean(source.value?.cancelJob || adapter.value?.cancelJob))
|
|
260
|
+
const canDelete = computed(() => Boolean(source.value?.deleteJob || adapter.value?.deleteJob))
|
|
261
|
+
const canClearFinished = computed(() => Boolean(
|
|
262
|
+
source.value?.clearFinishedJobs
|
|
263
|
+
|| source.value?.deleteJob
|
|
264
|
+
|| adapter.value?.clearFinishedJobs
|
|
265
|
+
|| adapter.value?.deleteJob,
|
|
266
|
+
))
|
|
104
267
|
|
|
105
268
|
function bumpJobRevision(jobId: string): void {
|
|
106
269
|
jobRevisions.set(jobId, (jobRevisions.get(jobId) ?? 0) + 1)
|
|
107
270
|
}
|
|
108
271
|
|
|
109
|
-
function
|
|
272
|
+
function replaceLegacyJobs(nextJobs: readonly JobStateInput[]): void {
|
|
110
273
|
const normalized = nextJobs.map(normalizeJobState)
|
|
111
274
|
const touchedJobIds = new Set([
|
|
112
275
|
...jobs.value.map(job => job.job_id),
|
|
113
276
|
...normalized.map(job => job.job_id),
|
|
114
277
|
])
|
|
115
278
|
touchedJobIds.forEach(bumpJobRevision)
|
|
116
|
-
|
|
279
|
+
legacyOverride.value = {
|
|
280
|
+
input: toValue(options.jobs),
|
|
281
|
+
jobs: normalized,
|
|
282
|
+
}
|
|
117
283
|
stateRevision += 1
|
|
118
284
|
}
|
|
119
285
|
|
|
120
|
-
function
|
|
286
|
+
function upsertLegacyJob(nextJob: JobStateInput): void {
|
|
121
287
|
const normalized = normalizeJobState(nextJob)
|
|
122
288
|
const existingIndex = jobs.value.findIndex(job => job.job_id === normalized.job_id)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
jobs
|
|
289
|
+
const nextJobs = [...jobs.value]
|
|
290
|
+
if (existingIndex === -1) nextJobs.unshift(normalized)
|
|
291
|
+
else nextJobs[existingIndex] = normalized
|
|
292
|
+
legacyOverride.value = {
|
|
293
|
+
input: toValue(options.jobs),
|
|
294
|
+
jobs: nextJobs,
|
|
129
295
|
}
|
|
130
296
|
bumpJobRevision(normalized.job_id)
|
|
131
297
|
stateRevision += 1
|
|
132
298
|
}
|
|
133
299
|
|
|
134
|
-
function
|
|
135
|
-
|
|
300
|
+
function removeLegacyJobs(jobIds: Set<string>): void {
|
|
301
|
+
legacyOverride.value = {
|
|
302
|
+
input: toValue(options.jobs),
|
|
303
|
+
jobs: jobs.value.filter(job => !jobIds.has(job.job_id)),
|
|
304
|
+
}
|
|
136
305
|
jobIds.forEach(bumpJobRevision)
|
|
137
306
|
stateRevision += 1
|
|
138
307
|
}
|
|
@@ -145,21 +314,22 @@ export function useJobsStatusTray(
|
|
|
145
314
|
}
|
|
146
315
|
|
|
147
316
|
function applyStreamMessage(message: JobStreamMessage): void {
|
|
148
|
-
if (!active) return
|
|
317
|
+
if (!active || source.value) return
|
|
149
318
|
if (message.event === 'snapshot' && isSnapshotPayload(message.data)) {
|
|
150
|
-
|
|
319
|
+
replaceLegacyJobs(message.data.jobs)
|
|
151
320
|
return
|
|
152
321
|
}
|
|
153
322
|
if (
|
|
154
323
|
(message.event === 'job' || message.event === 'terminal')
|
|
155
324
|
&& !isSnapshotPayload(message.data)
|
|
156
325
|
) {
|
|
157
|
-
|
|
326
|
+
upsertLegacyJob(message.data)
|
|
158
327
|
}
|
|
159
328
|
}
|
|
160
329
|
|
|
161
330
|
async function refresh(): Promise<boolean> {
|
|
162
|
-
const
|
|
331
|
+
const currentSource = source.value
|
|
332
|
+
const listJobs = currentSource?.refreshJobs ?? adapter.value?.listJobs
|
|
163
333
|
if (!listJobs) return false
|
|
164
334
|
const generation = ++requestGeneration
|
|
165
335
|
const revision = stateRevision
|
|
@@ -170,7 +340,7 @@ export function useJobsStatusTray(
|
|
|
170
340
|
if (!active || generation !== requestGeneration || revision !== stateRevision) {
|
|
171
341
|
return false
|
|
172
342
|
}
|
|
173
|
-
|
|
343
|
+
if (!currentSource && response) replaceLegacyJobs(responseJobs(response))
|
|
174
344
|
return true
|
|
175
345
|
} catch (cause) {
|
|
176
346
|
if (active && generation === requestGeneration) {
|
|
@@ -183,18 +353,21 @@ export function useJobsStatusTray(
|
|
|
183
353
|
}
|
|
184
354
|
|
|
185
355
|
async function cancelJob(job: JobState): Promise<boolean> {
|
|
186
|
-
const
|
|
187
|
-
|
|
356
|
+
const sourceCancel = source.value?.cancelJob
|
|
357
|
+
const adapterCancel = adapter.value?.cancelJob
|
|
358
|
+
if (!job.can_cancel || (!sourceCancel && !adapterCancel) || pendingJobIds.value.has(job.job_id)) {
|
|
188
359
|
return false
|
|
189
360
|
}
|
|
190
361
|
const revision = jobRevisions.get(job.job_id) ?? 0
|
|
191
362
|
setPending(job.job_id, true)
|
|
192
363
|
error.value = null
|
|
193
364
|
try {
|
|
194
|
-
const updated =
|
|
365
|
+
const updated = sourceCancel
|
|
366
|
+
? await sourceCancel(job.job_id)
|
|
367
|
+
: await adapterCancel!(job)
|
|
195
368
|
if (!active) return false
|
|
196
|
-
if (revision === (jobRevisions.get(job.job_id) ?? 0)) {
|
|
197
|
-
if (updated)
|
|
369
|
+
if (!sourceCancel && revision === (jobRevisions.get(job.job_id) ?? 0)) {
|
|
370
|
+
if (updated) upsertLegacyJob(updated)
|
|
198
371
|
else await refresh()
|
|
199
372
|
}
|
|
200
373
|
return true
|
|
@@ -207,17 +380,22 @@ export function useJobsStatusTray(
|
|
|
207
380
|
}
|
|
208
381
|
|
|
209
382
|
async function deleteJob(job: JobState): Promise<boolean> {
|
|
210
|
-
const
|
|
211
|
-
|
|
383
|
+
const sourceDelete = source.value?.deleteJob
|
|
384
|
+
const adapterDelete = adapter.value?.deleteJob
|
|
385
|
+
if (!job.can_delete || (!sourceDelete && !adapterDelete) || pendingJobIds.value.has(job.job_id)) {
|
|
212
386
|
return false
|
|
213
387
|
}
|
|
214
388
|
setPending(job.job_id, true)
|
|
215
389
|
error.value = null
|
|
216
390
|
try {
|
|
217
|
-
const deleted =
|
|
391
|
+
const deleted = sourceDelete
|
|
392
|
+
? await sourceDelete(job.job_id)
|
|
393
|
+
: await adapterDelete!(job)
|
|
218
394
|
if (!active) return false
|
|
219
|
-
if (
|
|
220
|
-
|
|
395
|
+
if (!sourceDelete) {
|
|
396
|
+
if (deleted === false) await refresh()
|
|
397
|
+
else removeLegacyJobs(new Set([job.job_id]))
|
|
398
|
+
}
|
|
221
399
|
return deleted !== false
|
|
222
400
|
} catch (cause) {
|
|
223
401
|
if (active) error.value = errorMessage(cause, 'Failed to delete job')
|
|
@@ -232,6 +410,16 @@ export function useJobsStatusTray(
|
|
|
232
410
|
if (targets.length === 0) return false
|
|
233
411
|
error.value = null
|
|
234
412
|
try {
|
|
413
|
+
const currentSource = source.value
|
|
414
|
+
if (currentSource?.clearFinishedJobs) {
|
|
415
|
+
const deleted = await currentSource.clearFinishedJobs()
|
|
416
|
+
if (!active) return false
|
|
417
|
+
return typeof deleted !== 'number' || deleted === targets.length
|
|
418
|
+
}
|
|
419
|
+
if (currentSource?.deleteJob) {
|
|
420
|
+
const results = await Promise.all(targets.map(job => deleteJob(job)))
|
|
421
|
+
return results.every(Boolean)
|
|
422
|
+
}
|
|
235
423
|
const currentAdapter = adapter.value
|
|
236
424
|
if (currentAdapter?.clearFinishedJobs) {
|
|
237
425
|
const deleted = await currentAdapter.clearFinishedJobs(targets)
|
|
@@ -240,7 +428,7 @@ export function useJobsStatusTray(
|
|
|
240
428
|
await refresh()
|
|
241
429
|
return false
|
|
242
430
|
}
|
|
243
|
-
|
|
431
|
+
removeLegacyJobs(new Set(targets.map(job => job.job_id)))
|
|
244
432
|
return true
|
|
245
433
|
}
|
|
246
434
|
if (!currentAdapter?.deleteJob) return false
|
|
@@ -252,16 +440,6 @@ export function useJobsStatusTray(
|
|
|
252
440
|
}
|
|
253
441
|
}
|
|
254
442
|
|
|
255
|
-
if (options.jobs !== undefined) {
|
|
256
|
-
watch(
|
|
257
|
-
() => toValue(options.jobs),
|
|
258
|
-
value => {
|
|
259
|
-
if (value) replaceJobs(value)
|
|
260
|
-
},
|
|
261
|
-
{ immediate: true, deep: true },
|
|
262
|
-
)
|
|
263
|
-
}
|
|
264
|
-
|
|
265
443
|
if (options.eventStream !== undefined) {
|
|
266
444
|
watch(
|
|
267
445
|
() => toValue(options.eventStream)?.lastMessage.value ?? null,
|
|
@@ -286,6 +464,10 @@ export function useJobsStatusTray(
|
|
|
286
464
|
historyJobs,
|
|
287
465
|
deletableJobs,
|
|
288
466
|
featuredJob,
|
|
467
|
+
canRefresh,
|
|
468
|
+
canCancel,
|
|
469
|
+
canDelete,
|
|
470
|
+
canClearFinished,
|
|
289
471
|
isOpen,
|
|
290
472
|
activeTab,
|
|
291
473
|
isLoading,
|
|
@@ -302,3 +484,10 @@ export function useJobsStatusTray(
|
|
|
302
484
|
isPending: job => pendingJobIds.value.has(job.job_id),
|
|
303
485
|
}
|
|
304
486
|
}
|
|
487
|
+
|
|
488
|
+
/** Backward-compatible name for the plugin job-center controller. */
|
|
489
|
+
export function useJobsStatusTray(
|
|
490
|
+
options: UseJobsStatusTrayOptions = {},
|
|
491
|
+
): UseJobsStatusTrayReturn {
|
|
492
|
+
return usePluginJobCenter(options)
|
|
493
|
+
}
|
|
@@ -28,7 +28,13 @@ import {
|
|
|
28
28
|
pluginEndpointRequestParts,
|
|
29
29
|
requestBodyFromPayload,
|
|
30
30
|
} from './pluginEndpointBuilder'
|
|
31
|
-
import type {
|
|
31
|
+
import type {
|
|
32
|
+
ExperimentSummary,
|
|
33
|
+
GeneratedJobDefinition,
|
|
34
|
+
PageSelectorItem,
|
|
35
|
+
SettingsModalSchema,
|
|
36
|
+
TopBarSettingsConfig,
|
|
37
|
+
} from '../types'
|
|
32
38
|
|
|
33
39
|
export {
|
|
34
40
|
buildPluginEndpointUrl,
|
|
@@ -41,6 +47,7 @@ export interface PluginEndpointContract {
|
|
|
41
47
|
name: string
|
|
42
48
|
method: PluginHttpMethod
|
|
43
49
|
path: string
|
|
50
|
+
requiresAuth?: boolean
|
|
44
51
|
pathParams?: PluginEndpointParamContract[]
|
|
45
52
|
queryParams?: PluginEndpointParamContract[]
|
|
46
53
|
requestType?: string | null
|
|
@@ -90,12 +97,14 @@ export interface PluginContract {
|
|
|
90
97
|
schema?: SettingsModalSchema | null
|
|
91
98
|
}
|
|
92
99
|
endpoints: PluginEndpointContract[]
|
|
100
|
+
jobs?: GeneratedJobDefinition[]
|
|
93
101
|
hash: string
|
|
94
102
|
}
|
|
95
103
|
|
|
96
104
|
export interface PluginEndpointDefinition {
|
|
97
105
|
method: PluginHttpMethod
|
|
98
106
|
path: string
|
|
107
|
+
requiresAuth?: boolean
|
|
99
108
|
pathParams?: PluginEndpointParamDefinition[]
|
|
100
109
|
queryParams?: PluginEndpointParamDefinition[]
|
|
101
110
|
hasBody?: boolean
|
|
@@ -157,7 +166,7 @@ export interface UsePluginSettingsOptions {
|
|
|
157
166
|
apiBaseUrl?: string
|
|
158
167
|
/** Platform API base URL for /plugins/{name}/config. Defaults to the injected platform API URL. */
|
|
159
168
|
platformApiBaseUrl?: string
|
|
160
|
-
/** SettingsModal schema generated from the backend
|
|
169
|
+
/** SettingsModal schema generated from the backend plugin config model. */
|
|
161
170
|
schema?: SettingsModalSchema | null
|
|
162
171
|
/** SettingsModal title when using settingsConfig. */
|
|
163
172
|
title?: string
|
|
@@ -383,7 +392,7 @@ function pluginPageIdFromPath(path: string, fallbackIndex: number): string {
|
|
|
383
392
|
return normalizedPath.replace(/^\/+/, '').replace(/\/+/g, '-') || `page-${fallbackIndex + 1}`
|
|
384
393
|
}
|
|
385
394
|
|
|
386
|
-
/** Convert
|
|
395
|
+
/** Convert resolved @mint_plugin nav_items into page selectors for the topbar and homepage PluginCards. */
|
|
387
396
|
export function getPluginPageSelectorItems(contract: PluginContract): PageSelectorItem[] {
|
|
388
397
|
const pluginIcon = contract.plugin.icon ?? ''
|
|
389
398
|
const navItems = contract.plugin.navItems ?? []
|
|
@@ -413,7 +422,10 @@ export function createPluginClient<TClient extends object = GeneratedPluginClien
|
|
|
413
422
|
? explicitOptions
|
|
414
423
|
: (payloadOrOptions as PluginClientRequestOptions | undefined) ?? explicitOptions
|
|
415
424
|
const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl)
|
|
416
|
-
const api = useApi({
|
|
425
|
+
const api = useApi({
|
|
426
|
+
baseUrl: parts.baseUrl,
|
|
427
|
+
withAuth: endpoint.requiresAuth !== false,
|
|
428
|
+
})
|
|
417
429
|
const config = endpointRequestConfig(endpoint, parts.query, callOptions)
|
|
418
430
|
|
|
419
431
|
if (endpoint.method === 'get') {
|
|
@@ -456,7 +468,10 @@ export async function uploadPluginEndpoint<TResponse = unknown>(
|
|
|
456
468
|
}
|
|
457
469
|
|
|
458
470
|
const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl)
|
|
459
|
-
const api = useApi({
|
|
471
|
+
const api = useApi({
|
|
472
|
+
baseUrl: parts.baseUrl,
|
|
473
|
+
withAuth: endpoint.requiresAuth !== false,
|
|
474
|
+
})
|
|
460
475
|
const body = endpoint.hasBody
|
|
461
476
|
? requestBodyFromPayload(payload, parts.requestPayload, endpoint)
|
|
462
477
|
: payload
|
|
@@ -491,7 +506,10 @@ export async function downloadPluginEndpoint(
|
|
|
491
506
|
}
|
|
492
507
|
|
|
493
508
|
const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl)
|
|
494
|
-
const api = useApi({
|
|
509
|
+
const api = useApi({
|
|
510
|
+
baseUrl: parts.baseUrl,
|
|
511
|
+
withAuth: endpoint.requiresAuth !== false,
|
|
512
|
+
})
|
|
495
513
|
const config = {
|
|
496
514
|
...(endpointRequestConfig(endpoint, parts.query, { signal: options.signal }) ?? {}),
|
|
497
515
|
responseType: 'blob' as const,
|
|
@@ -668,7 +686,10 @@ export function usePluginSettings<TSettings = Record<string, unknown>>(
|
|
|
668
686
|
}
|
|
669
687
|
}
|
|
670
688
|
|
|
671
|
-
function streamRequestAuth(
|
|
689
|
+
function streamRequestAuth(
|
|
690
|
+
options: PluginEventStreamOptions<unknown>,
|
|
691
|
+
policy: { requiresAuth: boolean },
|
|
692
|
+
): {
|
|
672
693
|
headers: Headers
|
|
673
694
|
token?: string
|
|
674
695
|
} {
|
|
@@ -677,11 +698,11 @@ function streamRequestAuth(options: PluginEventStreamOptions<unknown>): {
|
|
|
677
698
|
if (!authStore.isInitialized) {
|
|
678
699
|
authStore.initialize()
|
|
679
700
|
}
|
|
680
|
-
if (authStore.token && !headers.has('Authorization')) {
|
|
701
|
+
if (policy.requiresAuth && authStore.token && !headers.has('Authorization')) {
|
|
681
702
|
headers.set('Authorization', `Bearer ${authStore.token}`)
|
|
682
703
|
}
|
|
683
704
|
headers.set('Accept', 'text/event-stream')
|
|
684
|
-
const token = authStore.token
|
|
705
|
+
const token = policy.requiresAuth ? authStore.token : undefined
|
|
685
706
|
return {
|
|
686
707
|
headers,
|
|
687
708
|
token: token && headers.get('Authorization') === `Bearer ${token}` ? token : undefined,
|
|
@@ -832,7 +853,10 @@ export function usePluginEventStream<TData = string>(
|
|
|
832
853
|
const url = buildPluginEndpointUrl(contract, endpoint, payload, {
|
|
833
854
|
baseUrl: options.baseUrl,
|
|
834
855
|
})
|
|
835
|
-
const requestAuth = streamRequestAuth(
|
|
856
|
+
const requestAuth = streamRequestAuth(
|
|
857
|
+
options as PluginEventStreamOptions<unknown>,
|
|
858
|
+
{ requiresAuth: endpoint.requiresAuth !== false },
|
|
859
|
+
)
|
|
836
860
|
const response = await fetch(url, {
|
|
837
861
|
method: 'GET',
|
|
838
862
|
headers: requestAuth.headers,
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FormFieldSchema,
|
|
3
|
+
FormFieldType,
|
|
4
|
+
FormSchema,
|
|
5
|
+
} from '../types/form-builder'
|
|
6
|
+
import type {
|
|
7
|
+
GeneratedJsonSchema,
|
|
8
|
+
GeneratedJobDefinition,
|
|
9
|
+
} from '../types/generated-ui'
|
|
10
|
+
|
|
11
|
+
type GeneratedReference = 'path'
|
|
12
|
+
|
|
13
|
+
interface ResolvedGeneratedField {
|
|
14
|
+
schema: GeneratedJsonSchema
|
|
15
|
+
reference?: GeneratedReference
|
|
16
|
+
hasDefault: boolean
|
|
17
|
+
defaultValue?: unknown
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function generatedJobFormSchema(definition: GeneratedJobDefinition): FormSchema & { sections: NonNullable<FormSchema['sections']> } {
|
|
21
|
+
const root = definition.input_schema
|
|
22
|
+
const required = new Set(root.required ?? [])
|
|
23
|
+
const fields = Object.entries(root.properties ?? {}).map(([name, schema]) =>
|
|
24
|
+
toFormField(name, resolveGeneratedField(root, schema), required.has(name)),
|
|
25
|
+
)
|
|
26
|
+
return {
|
|
27
|
+
sections: [{
|
|
28
|
+
id: definition.id,
|
|
29
|
+
title: definition.title,
|
|
30
|
+
description: definition.description || undefined,
|
|
31
|
+
columns: 1,
|
|
32
|
+
fields,
|
|
33
|
+
}],
|
|
34
|
+
submitLabel: 'Run analysis',
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function generatedJobDefaults(definition: GeneratedJobDefinition): Record<string, unknown> {
|
|
39
|
+
return Object.fromEntries(
|
|
40
|
+
Object.entries(definition.input_schema.properties ?? {})
|
|
41
|
+
.filter(([, schema]) => hasOwnDefault(schema))
|
|
42
|
+
.map(([name, schema]) => [name, structuredClone(schema.default)]),
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function normalizeGeneratedJobInput(
|
|
47
|
+
definition: GeneratedJobDefinition,
|
|
48
|
+
values: Record<string, unknown>,
|
|
49
|
+
): Record<string, unknown> {
|
|
50
|
+
const normalized: Record<string, unknown> = {}
|
|
51
|
+
for (const [name, value] of Object.entries(values)) {
|
|
52
|
+
const field = definition.input_schema.properties?.[name]
|
|
53
|
+
if (!field) continue
|
|
54
|
+
normalized[name] = value
|
|
55
|
+
}
|
|
56
|
+
return normalized
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function toFormField(
|
|
60
|
+
name: string,
|
|
61
|
+
resolved: ResolvedGeneratedField,
|
|
62
|
+
required: boolean,
|
|
63
|
+
): FormFieldSchema {
|
|
64
|
+
const { schema, reference } = resolved
|
|
65
|
+
const validation: NonNullable<FormFieldSchema['validation']> = {}
|
|
66
|
+
if (required) validation.required = true
|
|
67
|
+
if (schema.minimum !== undefined) validation.min = schema.minimum
|
|
68
|
+
if (schema.maximum !== undefined) validation.max = schema.maximum
|
|
69
|
+
if (schema.minLength !== undefined) validation.minLength = schema.minLength
|
|
70
|
+
if (schema.maxLength !== undefined) validation.maxLength = schema.maxLength
|
|
71
|
+
|
|
72
|
+
const props: Record<string, unknown> = {}
|
|
73
|
+
if (schema.enum) props.options = [...schema.enum]
|
|
74
|
+
if (reference === 'path' && schema.type === 'array') props.multiple = true
|
|
75
|
+
return {
|
|
76
|
+
name,
|
|
77
|
+
label: schema.title || humanize(name),
|
|
78
|
+
type: fieldType(schema, reference),
|
|
79
|
+
defaultValue: resolved.hasDefault ? structuredClone(resolved.defaultValue) : undefined,
|
|
80
|
+
hint: schema.description,
|
|
81
|
+
validation,
|
|
82
|
+
props,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function resolveGeneratedField(
|
|
87
|
+
root: GeneratedJsonSchema,
|
|
88
|
+
field: GeneratedJsonSchema,
|
|
89
|
+
): ResolvedGeneratedField {
|
|
90
|
+
const hasDefault = hasOwnDefault(field)
|
|
91
|
+
const candidate = field.anyOf?.find(item => item.type !== 'null')
|
|
92
|
+
?? field.allOf?.[0]
|
|
93
|
+
?? field
|
|
94
|
+
if (candidate.$ref) {
|
|
95
|
+
const referenceParts = candidate.$ref.split('/')
|
|
96
|
+
const key = referenceParts[referenceParts.length - 1]
|
|
97
|
+
const schema = key ? root.$defs?.[key] : undefined
|
|
98
|
+
if (!schema) throw new Error(`Generated field reference is missing: ${candidate.$ref}`)
|
|
99
|
+
return {
|
|
100
|
+
schema: { ...schema, title: field.title ?? schema.title },
|
|
101
|
+
reference: field['x-mint-component'] ?? schema['x-mint-component'],
|
|
102
|
+
hasDefault,
|
|
103
|
+
defaultValue: field.default,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
schema: candidate,
|
|
108
|
+
reference: field['x-mint-component'] ?? candidate['x-mint-component'],
|
|
109
|
+
hasDefault,
|
|
110
|
+
defaultValue: field.default,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function fieldType(schema: GeneratedJsonSchema, reference?: GeneratedReference): FormFieldType {
|
|
115
|
+
if (reference === 'path') return 'path'
|
|
116
|
+
if (schema.enum) return 'select'
|
|
117
|
+
const type = Array.isArray(schema.type) ? schema.type.find(value => value !== 'null') : schema.type
|
|
118
|
+
if (type === 'boolean') return 'toggle'
|
|
119
|
+
if (type === 'number' || type === 'integer') return 'number'
|
|
120
|
+
if (type === 'array') return 'tags'
|
|
121
|
+
return 'text'
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function hasOwnDefault(schema: GeneratedJsonSchema): boolean {
|
|
125
|
+
return Object.prototype.hasOwnProperty.call(schema, 'default')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function humanize(value: string): string {
|
|
129
|
+
return value.replace(/[_-]+/g, ' ').replace(/^\w/, character => character.toUpperCase())
|
|
130
|
+
}
|