Skip to content

Commit d5a80cd

Browse files
feat: json-schema-based plugin doc (#174)
--------- Co-authored-by: YannC <[email protected]>
1 parent e8194ff commit d5a80cd

13 files changed

+963
-470
lines changed

package-lock.json

+280-34
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
"require": "./dist/kestra-ui.umd.cjs"
1919
},
2020
"./style.css": "./dist/kestra-ui.css",
21-
"./scss/*": "./src/scss/*",
2221
"./src/*": "./src/*"
2322
},
2423
"scripts": {
@@ -45,6 +44,7 @@
4544
"moment-timezone": "^0.5.46",
4645
"vue": "^3.5.5",
4746
"vue-material-design-icons": "^5.3.0",
47+
"vue-router": "^4.5.0",
4848
"vuex": "^4.1.0",
4949
"yaml": "^2.5.1"
5050
},
@@ -80,7 +80,7 @@
8080
"@nuxtjs/mdc": "^0.12.1",
8181
"@popperjs/core": "^2.11.8",
8282
"mermaid": "^11.4.1",
83-
"shiki": "^1.26.1",
83+
"shiki": "^1.22.0",
8484
"vue-i18n": "^11.0.1"
8585
},
8686
"optionalDependencies": {

src/components/misc/Collapsible.vue

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<template>
2+
<div :id="href" class="d-flex flex-column gap-2">
3+
<button @click="collapsed = !collapsed" class="d-flex align-items-center justify-content-between fw-bold gap-2 collapse-button" :class="{collapsed}" data-toggle="collapse" :data-target="'#' + href + '-body'" aria-expanded="false" :aria-controls="href + '-body'">
4+
<span class="d-flex gap-2 align-items-center"><component v-if="arrow" class="arrow" :is="collapsed ? MenuRight : MenuDown" />{{ clickableText }}<slot name="additionalButtonText" /></span>
5+
<span v-if="$slots.buttonRight" class="d-flex flex-grow-1">
6+
<slot name="buttonRight" :collapsed="collapsed" />
7+
</span>
8+
</button>
9+
<div v-if="$slots.content" v-show="!collapsed" :id="href + '-body'" class="collapsible-body">
10+
<slot name="content" />
11+
</div>
12+
</div>
13+
</template>
14+
15+
<script setup lang="ts">
16+
import {ref, watch} from "vue";
17+
import MenuRight from "vue-material-design-icons/MenuRight.vue";
18+
import MenuDown from "vue-material-design-icons/MenuDown.vue";
19+
import {useRoute} from "vue-router";
20+
21+
const props = withDefaults(defineProps<{ href?: string, clickableText: string, arrow?: boolean, initiallyExpanded?: boolean }>(), {
22+
href: Math.random().toString(36).substring(2, 5),
23+
arrow: true,
24+
initiallyExpanded: false
25+
});
26+
27+
const collapsed = ref(!props.initiallyExpanded);
28+
29+
const route = useRoute();
30+
31+
const emit = defineEmits(["expand"]);
32+
33+
watch(
34+
() => {
35+
return route.hash;
36+
},
37+
routeHash => {
38+
if (routeHash === "#" + props.href) {
39+
collapsed.value = false;
40+
emit("expand");
41+
}
42+
},
43+
{immediate: true}
44+
);
45+
46+
watch(
47+
() => props.initiallyExpanded,
48+
initiallyExpanded => {
49+
if (initiallyExpanded) {
50+
collapsed.value = !initiallyExpanded;
51+
}
52+
},
53+
{immediate: true}
54+
);
55+
</script>
56+
57+
<style scoped lang="scss">
58+
.collapse-button {
59+
padding: 0;
60+
border: none;
61+
background: none;
62+
63+
&:focus {
64+
outline:none;
65+
box-shadow: none;
66+
}
67+
}
68+
</style>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<template>
2+
<Collapsible :clickable-text="sectionName" :href="href" @expand="emit('expand')" :initially-expanded="initiallyExpanded">
3+
<template v-if="Object.keys(properties ?? {}).length > 0" #content>
4+
<div class="border rounded">
5+
<Collapsible
6+
class="property p-3"
7+
v-for="(property, propertyKey) in sortSchemaByRequired(properties)"
8+
:key="propertyKey"
9+
:arrow="false"
10+
:clickable-text="propertyKey"
11+
:href="href + '_' + propertyKey"
12+
@expand="initiallyExpanded = true"
13+
>
14+
<template #additionalButtonText>
15+
<span v-if="property['$required']" class="text-danger"> *</span>
16+
</template>
17+
<template #buttonRight="{collapsed}">
18+
<span class="d-flex flex-grow-1 align-items-center justify-content-between">
19+
<span class="d-flex gap-1">
20+
<Tooltip v-if="showDynamic && !isDynamic(property)" class="d-flex" title="Non-dynamic">
21+
<Cancel class="text-danger" />
22+
</Tooltip>
23+
<Tooltip v-if="property['$beta']" class="d-flex" title="Beta">
24+
<AlphaBBox class="text-warning" />
25+
</Tooltip>
26+
<Tooltip v-if="property['$deprecated']" class="d-flex" title="Deprecated">
27+
<Alert class="text-warning" />
28+
</Tooltip>
29+
</span>
30+
<span class="d-flex gap-2">
31+
<template v-for="type in extractTypeInfo(property).types" :key="type">
32+
<a v-if="type.startsWith('#')" class="d-flex fw-bold type-box rounded fs-7 px-2 py-1" :href="type" @click.stop>
33+
<span class="ref-type">{{ className(type) }}</span><eye-outline />
34+
</a>
35+
<span v-else class="type-box rounded fs-7 px-2 py-1">
36+
{{ type }}
37+
</span>
38+
</template>
39+
<component :is="collapsed ? ChevronDown : ChevronUp" class="arrow" />
40+
</span>
41+
</span>
42+
</template>
43+
<template #content>
44+
<PropertyDetail :show-dynamic="showDynamic" :is-dynamic="showDynamic && isDynamic(property)" :property="property">
45+
<template #markdown="{content}">
46+
<slot :content="content" name="markdown" />
47+
</template>
48+
</PropertyDetail>
49+
</template>
50+
</Collapsible>
51+
</div>
52+
</template>
53+
</Collapsible>
54+
</template>
55+
56+
<script setup lang="ts">
57+
import {extractTypeInfo, className, type JSONProperty} from "../../utils/schemaUtils";
58+
import ChevronDown from "vue-material-design-icons/ChevronDown.vue";
59+
import Collapsible from "../misc/Collapsible.vue";
60+
import Tooltip from "../misc/Tooltip.vue";
61+
import PropertyDetail from "./PropertyDetail.vue";
62+
import ChevronUp from "vue-material-design-icons/ChevronUp.vue";
63+
import Alert from "vue-material-design-icons/Alert.vue";
64+
import Cancel from "vue-material-design-icons/Cancel.vue";
65+
import AlphaBBox from "vue-material-design-icons/AlphaBBox.vue";
66+
import type {JSONSchema} from "./SchemaToHtml.vue";
67+
import EyeOutline from "vue-material-design-icons/EyeOutline.vue";
68+
import {ref, watch} from "vue";
69+
70+
withDefaults(defineProps<{ href?: string, sectionName: string, properties?: Record<string, JSONProperty>, showDynamic?: boolean }>(), {
71+
properties: undefined,
72+
href: Math.random().toString(36).substring(2, 5),
73+
showDynamic: true
74+
});
75+
76+
const emit = defineEmits(["expand"]);
77+
78+
const initiallyExpanded = ref(false);
79+
80+
watch(
81+
initiallyExpanded,
82+
newInitiallyExpanded => {
83+
if (newInitiallyExpanded) {
84+
emit("expand");
85+
}
86+
}
87+
);
88+
89+
const isDynamic = (property: JSONProperty): boolean => {
90+
if (property["$dynamic"] === true) {
91+
return true;
92+
}
93+
94+
if (property["$dynamic"] === false) {
95+
return false;
96+
}
97+
98+
return property.oneOf?.some(prop => prop["$dynamic"] === true) ?? false;
99+
};
100+
101+
function sortSchemaByRequired<T extends NonNullable<NonNullable<JSONSchema["properties"]>["properties"]>>(schema: T): T {
102+
const requiredKeys = [];
103+
const nonRequiredKeys = [];
104+
105+
for (const key in schema) {
106+
if (typeof schema[key] === "object") {
107+
if (schema[key].$required) {
108+
requiredKeys.push(key);
109+
} else {
110+
nonRequiredKeys.push(key);
111+
}
112+
}
113+
}
114+
115+
const sortedKeys = [...requiredKeys.sort(), ...nonRequiredKeys.sort()];
116+
117+
const sortedSchema: T = {} as T;
118+
sortedKeys.forEach(key => {
119+
sortedSchema[key] = schema[key];
120+
});
121+
122+
return sortedSchema;
123+
}
124+
</script>
125+
126+
<style lang="scss" scoped>
127+
@use "../../scss/variables" as variables;
128+
129+
.type-box, :deep(.type-box) {
130+
border: 1px solid variables.$blue !important;
131+
background: none;
132+
133+
.ref-type {
134+
padding-right: 0.625rem;
135+
136+
+ * {
137+
margin-left: 0.625rem;
138+
}
139+
}
140+
}
141+
142+
.property:not(:first-child) {
143+
border-top: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color);
144+
}
145+
</style>

0 commit comments

Comments
 (0)