Skip to content

Commit 63d3f5f

Browse files
committed
Fix TypeError in cylc message mutation form
Replace mixin with composables
1 parent cb0032a commit 63d3f5f

File tree

9 files changed

+348
-396
lines changed

9 files changed

+348
-396
lines changed

src/components/graphqlFormGenerator/FormInput.vue

Lines changed: 77 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -20,113 +20,94 @@ This is a convenience component that wraps an input component, allowing
2020
dynamically created inputs.
2121
-->
2222

23-
<script>
24-
import { h, mergeProps } from 'vue'
23+
<template>
24+
<component
25+
:is="inputProps.is"
26+
v-bind="inputProps"
27+
v-model="model"
28+
:gqlType="gqlType"
29+
:types="types"
30+
v-mask="inputProps.mask"
31+
>
32+
<template
33+
v-if="help"
34+
v-slot:append-inner
35+
>
36+
<v-tooltip>
37+
<template v-slot:activator="{ props }">
38+
<v-icon
39+
v-bind="props"
40+
style="cursor: default"
41+
:icon="mdiHelpCircleOutline"
42+
/>
43+
</template>
44+
<Markdown :markdown="help" />
45+
</v-tooltip>
46+
</template>
47+
<!-- pass the "append" slot onto the child component -->
48+
<template v-slot:append="slotProps">
49+
<slot
50+
name="append"
51+
v-bind="slotProps"
52+
/>
53+
</template>
54+
</component>
55+
</template>
56+
57+
<script setup>
58+
import { mergeProps, useAttrs } from 'vue'
2559
import { mask } from 'vue-the-mask'
2660
import Markdown from '@/components/Markdown.vue'
27-
import { formElement } from '@/components/graphqlFormGenerator/mixins'
61+
import { formElementProps, useFormElement } from '@/components/graphqlFormGenerator/mixins'
2862
import VuetifyConfig, { getComponentProps } from '@/components/graphqlFormGenerator/components/vuetify'
2963
import { mdiHelpCircleOutline } from '@mdi/js'
3064
import { VIcon } from 'vuetify/components/VIcon'
3165
import { VTooltip } from 'vuetify/components/VTooltip'
32-
import { upperFirst } from 'lodash'
33-
34-
/**
35-
* Render help icon with tooltip containing help text.
36-
*
37-
* @param {string} helpText - (supports markdown)
38-
*/
39-
export const renderHelpIcon = (helpText) => h(
40-
VTooltip,
41-
{ location: 'bottom' },
42-
{
43-
activator: ({ props }) => h(
44-
VIcon,
45-
{
46-
...props,
47-
style: {
48-
cursor: 'default'
49-
}
50-
},
51-
() => mdiHelpCircleOutline
52-
),
53-
default: () => h(Markdown, { markdown: helpText })
54-
}
55-
)
56-
57-
export default {
58-
name: 'g-form-input',
5966
67+
defineOptions({
6068
// Prevent fallthrough attrs overriding the supplied props for the input
6169
// https://github.com/vuejs/core/issues/6504
6270
inheritAttrs: false,
71+
})
6372
64-
mixins: [formElement],
65-
66-
components: {
67-
Markdown
68-
},
69-
70-
directives: {
71-
mask: (el, binding) => {
72-
// only use the mask if one is provided, this allows us to use the
73-
// mask directive on elements which it doesn't support
74-
if (binding.value) {
75-
mask(el, binding)
76-
}
77-
}
78-
},
79-
80-
props: {
81-
// dictionary of props for overriding default values
82-
propOverrides: {
83-
type: Object,
84-
default: () => { Object() }
85-
}
86-
},
87-
88-
beforeCreate () {
89-
// Set the props to pass to the form input. Note, this includes the "is"
90-
// prop which tells Vue which component class to use.
91-
// TODO: move to rule based system to allow changing
92-
// of parent components based on child types?
93-
94-
// get the default props for this graphQL type
95-
const componentProps = getComponentProps(this.gqlType, VuetifyConfig.namedTypes, VuetifyConfig.kinds)
96-
97-
// merge this in with default and override props
98-
const propGroups = [
99-
componentProps,
100-
this.propOverrides || {}
101-
]
102-
// rules is a list so needs special treatment
103-
const rules = propGroups.flatMap(({ rules }) => rules ?? [])
104-
105-
this.inputProps = mergeProps(this.$attrs, ...propGroups, { rules })
106-
},
107-
108-
render () {
109-
// Some components implement custom v-model
110-
// (https://v2.vuejs.org/v2/guide/components-custom-events.html#Customizing-Component-v-model)
111-
const vModel = this.inputProps.is.options?.model || { prop: 'modelValue', event: 'update:modelValue' }
112-
return h(
113-
this.inputProps.is,
114-
{
115-
...this.inputProps,
116-
[vModel.prop]: this.model,
117-
[`on${upperFirst(vModel.event)}`]: (value) => {
118-
this.model = value
119-
},
120-
gqlType: this.gqlType,
121-
types: this.types
122-
},
123-
{
124-
'append-inner': this.help ? () => renderHelpIcon(this.help) : null,
125-
// pass the "append" slot onto the child component
126-
append: (slotProps) => this.$slots.append?.(slotProps)
127-
}
128-
)
129-
}
73+
const attrs = useAttrs()
13074
75+
const vMask = (el, binding) => {
76+
// only use the mask if one is provided, this allows us to use the
77+
// mask directive on elements which it doesn't support
78+
if (binding.value) {
79+
mask(el, binding)
80+
}
13181
}
82+
83+
const props = defineProps({
84+
...formElementProps,
85+
// dictionary of props for overriding default values
86+
propOverrides: {
87+
type: Object,
88+
default: () => ({})
89+
}
90+
})
91+
92+
const model = defineModel()
93+
94+
// Set the props to pass to the form input. Note, this includes the "is"
95+
// prop which tells Vue which component class to use.
96+
// TODO: move to rule based system to allow changing
97+
// of parent components based on child types?
98+
99+
// get the default props for this graphQL type
100+
const componentProps = getComponentProps(props.gqlType, VuetifyConfig.namedTypes, VuetifyConfig.kinds)
101+
102+
// merge this in with default and override props
103+
const propGroups = [
104+
componentProps,
105+
props.propOverrides || {}
106+
]
107+
// rules is a list so needs special treatment
108+
const rules = propGroups.flatMap(({ rules }) => rules ?? [])
109+
110+
const inputProps = mergeProps(attrs, ...propGroups, { rules })
111+
112+
const { help } = useFormElement(props)
132113
</script>

src/components/graphqlFormGenerator/components/BroadcastSetting.vue

Lines changed: 95 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -16,95 +16,112 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
1616
-->
1717

1818
<script>
19-
import { h } from 'vue'
19+
import { h, ref } from 'vue'
2020
import { VTextField } from 'vuetify/components/VTextField'
21-
import { formElement } from '@/components/graphqlFormGenerator/mixins'
22-
import { renderHelpIcon } from '@/components/graphqlFormGenerator/FormInput.vue'
23-
import { nonNullRule } from '@/components/graphqlFormGenerator/components/NonNull.vue'
21+
import { VIcon } from 'vuetify/components/VIcon'
22+
import { VTooltip } from 'vuetify/components/VTooltip'
23+
import { mdiHelpCircleOutline } from '@mdi/js'
24+
import Markdown from '@/components/Markdown.vue'
25+
import { formElementProps, useFormElement } from '@/components/graphqlFormGenerator/mixins'
26+
import { RULES } from '@/components/graphqlFormGenerator/components/vuetify'
27+
28+
/**
29+
* Split a given string from the left.
30+
*
31+
* @param {string} string_ - The string to split.
32+
* @param {string} separator - The string to split it by.
33+
* @param {number} n - The maximum number of times to split the string.
34+
* @returns {string[]}
35+
*/
36+
function lsplit (string_, separator, n) {
37+
const split = string_.split(separator)
38+
if (split.length <= n) {
39+
return split
40+
}
41+
return [split.shift(), split.join(separator)]
42+
}
43+
44+
/** Convert a string 'x=y' into an object { 'x': 'y' } */
45+
function fromString (string_) {
46+
const [lhs, rhs] = lsplit(string_, '=', 2)
47+
if (rhs === undefined) {
48+
return null
49+
}
50+
// const [lhs, rhs] = JavaSplit(string_, '=', 2)
51+
if (lhs === 'inherit') {
52+
return null
53+
// return 'ERROR: cannot broadcast inheritance'
54+
}
55+
const rdict = {}
56+
let tail = lhs
57+
const re = /^\[([^\]]*)\](.*)$/
58+
let sect = null
59+
let curDict = rdict
60+
let match = null
61+
while (tail) {
62+
match = tail.match(re)
63+
if (match) {
64+
sect = match[1]
65+
tail = match[2]
66+
if (tail) {
67+
curDict[sect.trim()] = {}
68+
curDict = curDict[sect.trim()]
69+
} else {
70+
curDict[sect.trim()] = rhs.trim()
71+
}
72+
} else {
73+
curDict[tail.trim()] = rhs.trim()
74+
tail = null
75+
}
76+
}
77+
return rdict
78+
}
79+
80+
const renderHelpIcon = (helpText) => h(
81+
VTooltip,
82+
{ location: 'bottom' },
83+
{
84+
activator: ({ props }) => h(
85+
VIcon,
86+
{
87+
...props,
88+
style: {
89+
cursor: 'default'
90+
}
91+
},
92+
() => mdiHelpCircleOutline
93+
),
94+
default: () => h(Markdown, { markdown: helpText })
95+
}
96+
)
2497
2598
export default {
2699
name: 'g-broadcast-setting',
27100
28-
mixins: [formElement],
29-
30101
inheritAttrs: false,
31102
32-
data: () => ({
33-
localValue: null
34-
}),
103+
props: {
104+
...formElementProps,
105+
modelValue: {
106+
type: Object,
107+
required: false,
108+
}
109+
},
35110
36-
methods: {
37-
/** Split a given string from the left.
38-
*
39-
* @param {string} string_ - The string to split.
40-
* @param {string} separator - The string to split it by.
41-
* @param {number} n - The maximum number of times to split the string.
42-
* @returns {Array<string>}
43-
*/
44-
lsplit (string_, separator, n) {
45-
const split = string_.split(separator)
46-
if (split.length <= n) {
47-
return split
48-
}
49-
return [split.shift(), split.join(separator)]
50-
},
111+
emits: ['update:modelValue'],
51112
52-
fromString (string_) {
53-
const [lhs, rhs] = this.lsplit(string_, '=', 2)
54-
if (rhs === undefined) {
55-
return null
56-
}
57-
// const [lhs, rhs] = JavaSplit(string_, '=', 2)
58-
if (lhs === 'inherit') {
59-
return null
60-
// return 'ERROR: cannot broadcast inheritance'
61-
}
62-
const rdict = {}
63-
let tail = lhs
64-
const re = /^\[([^\]]*)\](.*)$/
65-
let sect = null
66-
let curDict = rdict
67-
let match = null
68-
while (tail) {
69-
match = tail.match(re)
70-
if (match) {
71-
sect = match[1]
72-
tail = match[2]
73-
if (tail) {
74-
curDict[sect.trim()] = {}
75-
curDict = curDict[sect.trim()]
76-
} else {
77-
curDict[sect.trim()] = rhs.trim()
78-
}
79-
} else {
80-
curDict[tail.trim()] = rhs.trim()
81-
tail = null
82-
}
83-
}
84-
return rdict
85-
},
113+
setup (props) {
114+
const { help } = useFormElement(props)
86115
87-
fromObject (object_) {
88-
let ptr = object_
89-
let ret = ''
90-
while (ptr) {
91-
const keys = Object.keys(ptr)
92-
if (keys && keys.length !== 0) {
93-
const key = keys[0]
94-
ptr = ptr[key]
95-
if (typeof ptr === 'object') {
96-
ret += `[${key}]`
97-
} else {
98-
ret += `${key}=${ptr}`
99-
ptr = null
100-
}
101-
}
102-
}
103-
return ret
104-
},
116+
return {
117+
help,
118+
localValue: ref(null),
119+
}
120+
},
105121
122+
methods: {
106123
isValid (val) {
107-
const nonNullOutcome = nonNullRule(val)
124+
const nonNullOutcome = RULES.nonNull(val)
108125
return nonNullOutcome === true
109126
? (this.modelValue != null) || 'Invalid'
110127
: nonNullOutcome
@@ -119,7 +136,7 @@ export default {
119136
modelValue: this.localValue,
120137
'onUpdate:modelValue': (val) => {
121138
this.localValue = val
122-
this.$emit('update:modelValue', this.fromString(val))
139+
this.$emit('update:modelValue', fromString(val))
123140
},
124141
rules: [this.isValid]
125142
},

0 commit comments

Comments
 (0)