|
13 | 13 | :name="plugin.group" |
14 | 14 | :title="plugin.title?.capitalize()" |
15 | 15 | :key="plugin.group" |
16 | | - :ref="`plugin-${plugin.group}`" |
| 16 | + :ref="(el) => pluginRefs[plugin.group] = el" |
17 | 17 | > |
18 | 18 | <ul class="toc-h3"> |
19 | 19 | <li v-for="(types, namespace) in group(plugin)" :key="namespace"> |
20 | 20 | <h6>{{ namespace }}</h6> |
21 | 21 | <ul class="toc-h4"> |
22 | 22 | <li v-for="(classes, type) in types" :key="type + '-' + namespace"> |
23 | | - <h6>{{ $filters.cap(type) }}</h6> |
| 23 | + <h6>{{ cap(type) }}</h6> |
24 | 24 | <ul class="section-nav toc-h5"> |
25 | 25 | <li v-for="cls in classes" :key="cls"> |
26 | 26 | <router-link |
|
35 | 35 | /> |
36 | 36 | </div> |
37 | 37 | <span |
38 | | - :class="$route.params.cls === (namespace + '.' + cls) ? 'selected mx-2' : 'mx-2'" |
| 38 | + :class="route.params.cls === (namespace + '.' + cls) ? 'selected mx-2' : 'mx-2'" |
39 | 39 | >{{ |
40 | 40 | cls |
41 | 41 | }}</span> |
|
52 | 52 | </div> |
53 | 53 | </template> |
54 | 54 |
|
55 | | -<script> |
56 | | - import {isEntryAPluginElementPredicate, TaskIcon} from "@kestra-io/ui-libs"; |
57 | | - import {mapStores} from "pinia"; |
| 55 | +<script setup lang="ts"> |
| 56 | + import {ref, computed, watch, nextTick, reactive} from "vue"; |
| 57 | + import {useRoute} from "vue-router"; |
| 58 | + import {isEntryAPluginElementPredicate, TaskIcon, type Plugin, type PluginElement} from "@kestra-io/ui-libs"; |
58 | 59 | import {usePluginsStore} from "../../stores/plugins"; |
| 60 | + import {cap} from "../../utils/filters"; |
59 | 61 |
|
60 | | - export default { |
61 | | - emits: ["routerChange"], |
62 | | - data() { |
63 | | - return { |
64 | | - offset: 0, |
65 | | - activeNames: [], |
66 | | - searchInput: "" |
67 | | - } |
68 | | - }, |
69 | | - watch: { |
70 | | - $route: { |
71 | | - handler() { |
72 | | - this.plugins.forEach(plugin => { |
73 | | - if (Object.entries(plugin).some(([key, value]) => isEntryAPluginElementPredicate(key, value) && value.map(({cls}) => cls).includes(this.$route.params.cls))) { |
74 | | - this.activeNames = [plugin.group] |
75 | | - localStorage.setItem("activePlugin", plugin.group); |
76 | | - } |
77 | | - }) |
78 | | - this.scrollToActivePlugin(); |
79 | | - }, |
80 | | - immediate: true |
| 62 | + const props = defineProps<{ |
| 63 | + plugins: Plugin[]; |
| 64 | + }>(); |
| 65 | +
|
| 66 | + defineEmits<{ |
| 67 | + routerChange: []; |
| 68 | + }>(); |
| 69 | +
|
| 70 | + const route = useRoute(); |
| 71 | + const pluginsStore = usePluginsStore(); |
| 72 | +
|
| 73 | + const pluginRefs = reactive({}); |
| 74 | + const activeNames = ref<string[]>([]); |
| 75 | + const searchInput = ref<string>(""); |
| 76 | +
|
| 77 | + const countPlugin = computed(() => { |
| 78 | + return new Set(props.plugins.flatMap(plugin => pluginElements(plugin))).size; |
| 79 | + }); |
| 80 | +
|
| 81 | + const pluginElements = (plugin: Plugin) => { |
| 82 | + return Object.entries(plugin) |
| 83 | + .filter(([key, value]) => isEntryAPluginElementPredicate(key, value)) |
| 84 | + .flatMap(([_, value]) => (value as PluginElement[]) |
| 85 | + .filter(({deprecated}) => !deprecated) |
| 86 | + .map(({cls}) => cls) |
| 87 | + ); |
| 88 | + }; |
| 89 | +
|
| 90 | + const scrollToActivePlugin = () => { |
| 91 | + const activePlugin = localStorage.getItem("activePlugin"); |
| 92 | + if (activePlugin) { |
| 93 | + const pluginElement = pluginRefs[activePlugin]; |
| 94 | + if (pluginElement) { |
| 95 | + pluginElement.$el.scrollIntoView({behavior: "smooth", block: "start"}); |
81 | 96 | } |
82 | | - }, |
83 | | - components: { |
84 | | - TaskIcon |
85 | | - }, |
86 | | - props: { |
87 | | - plugins: { |
88 | | - type: Array, |
89 | | - required: true |
| 97 | + } |
| 98 | + }; |
| 99 | +
|
| 100 | + const pluginsList = computed(() => { |
| 101 | + return props.plugins |
| 102 | + .filter((plugin, index, self) => { |
| 103 | + return index === self.findIndex((t) => ( |
| 104 | + t.title === plugin.title && t.group === plugin.group |
| 105 | + )); |
| 106 | + }) |
| 107 | + .filter(plugin => { |
| 108 | + return plugin.title?.toLowerCase().includes(searchInput.value.toLowerCase()) || |
| 109 | + pluginElements(plugin).some(element => element.toLowerCase().includes(searchInput.value.toLowerCase())); |
| 110 | + }) |
| 111 | + .map(plugin => { |
| 112 | + return { |
| 113 | + ...plugin, |
| 114 | + ...Object.fromEntries( |
| 115 | + Object.entries(plugin) |
| 116 | + .filter(([key, value]) => isEntryAPluginElementPredicate(key, value)) |
| 117 | + .map(([elementType, elements]) => [ |
| 118 | + elementType, |
| 119 | + (elements as PluginElement[]).filter(({deprecated}) => !deprecated) |
| 120 | + .filter(({cls}) => cls.toLowerCase().includes(searchInput.value.toLowerCase())) |
| 121 | + ]) |
| 122 | + ) |
| 123 | + }; |
| 124 | + }); |
| 125 | + }); |
| 126 | +
|
| 127 | + watch(route, () => { |
| 128 | + props.plugins.forEach(plugin => { |
| 129 | + if (Object.entries(plugin).some(([key, value]) => { |
| 130 | + if (isEntryAPluginElementPredicate(key, value)) { |
| 131 | + return (value as PluginElement[]).some(({cls}) => cls === route.params.cls); |
| 132 | + } |
| 133 | + return false; |
| 134 | + })) { |
| 135 | + activeNames.value = [plugin.group]; |
| 136 | + localStorage.setItem("activePlugin", plugin.group); |
90 | 137 | } |
91 | | - }, |
92 | | - computed: { |
93 | | - ...mapStores(usePluginsStore), |
94 | | - countPlugin() { |
95 | | - return new Set(this.plugins.flatMap(plugin => this.pluginElements(plugin))).size |
96 | | - }, |
97 | | - pluginsList() { |
98 | | - return this.plugins |
99 | | - // remove duplicate |
100 | | - .filter((plugin, index, self) => { |
101 | | - return index === self.findIndex((t) => ( |
102 | | - t.title === plugin.title && t.group === plugin.group |
103 | | - )); |
104 | | - }) |
105 | | - // find plugin that match search input |
106 | | - .filter(plugin => { |
107 | | - return plugin.title.toLowerCase().includes(this.searchInput.toLowerCase()) || |
108 | | - this.pluginElements(plugin).some(element => element.toLowerCase().includes(this.searchInput.toLowerCase())) |
109 | | - }) |
110 | | - // keep only task that match search input |
111 | | - .map(plugin => { |
| 138 | + }); |
| 139 | + nextTick(() => { |
| 140 | + scrollToActivePlugin(); |
| 141 | + }); |
| 142 | + }, {immediate: true}); |
| 143 | +
|
| 144 | + const handlePluginChange = (pluginGroup: string) => { |
| 145 | + activeNames.value = [pluginGroup]; |
| 146 | + localStorage.setItem("activePlugin", pluginGroup); |
| 147 | + }; |
| 148 | +
|
| 149 | + const sortedPlugins = (plugins: Plugin[]) => { |
| 150 | + return plugins |
| 151 | + .sort((a, b) => { |
| 152 | + const nameA = (a.title ? a.title.toLowerCase() : ""), |
| 153 | + nameB = (b.title ? b.title.toLowerCase() : ""); |
| 154 | +
|
| 155 | + return (nameA < nameB ? -1 : (nameA > nameB ? 1 : 0)); |
| 156 | + }); |
| 157 | + }; |
| 158 | +
|
| 159 | + const group = (plugin: Plugin) => { |
| 160 | + return Object.entries(plugin) |
| 161 | + .filter(([key, value]) => isEntryAPluginElementPredicate(key, value)) |
| 162 | + .flatMap(([type, value]) => { |
| 163 | + return (value as PluginElement[]).filter(({deprecated}) => !deprecated) |
| 164 | + .map(({cls}) => { |
| 165 | + const namespace = cls.substring(0, cls.lastIndexOf(".")); |
| 166 | +
|
112 | 167 | return { |
113 | | - ...plugin, |
114 | | - ...Object.fromEntries( |
115 | | - Object.entries(plugin) |
116 | | - .filter(([key, value]) => isEntryAPluginElementPredicate(key, value)) |
117 | | - .map(([elementType, elements]) => [ |
118 | | - elementType, |
119 | | - elements.filter(({deprecated}) => !deprecated) |
120 | | - .filter(({cls}) => cls.toLowerCase().includes(this.searchInput.toLowerCase())) |
121 | | - ]) |
122 | | - ) |
123 | | - } |
124 | | - }) |
125 | | - } |
126 | | - }, |
127 | | - methods: { |
128 | | - pluginElements(plugin) { |
129 | | - return Object.entries(plugin) |
130 | | - .filter(([key, value]) => isEntryAPluginElementPredicate(key, value)) |
131 | | - .flatMap(([_, value]) => value |
132 | | - .filter(({deprecated}) => !deprecated) |
133 | | - .map(({cls}) => cls) |
134 | | - ) |
135 | | - }, |
136 | | - scrollToActivePlugin() { |
137 | | - const activePlugin = localStorage.getItem("activePlugin"); |
138 | | - if (activePlugin) { |
139 | | - // Use Vue's $refs to scroll to the specific plugin group |
140 | | - this.$nextTick(() => { |
141 | | - const pluginElement = this.$refs[`plugin-${activePlugin}`]; |
142 | | - if (pluginElement && pluginElement[0]) { |
143 | | - pluginElement[0].$el.scrollIntoView({behavior: "smooth", block: "start"}); |
144 | | - } |
| 168 | + type, |
| 169 | + namespace: namespace, |
| 170 | + cls: cls.substring(cls.lastIndexOf(".") + 1) |
| 171 | + }; |
145 | 172 | }); |
146 | | - } |
147 | | - }, |
148 | | - // When user navigates to a different plugin, save the new plugin group to localStorage |
149 | | - handlePluginChange(pluginGroup) { |
150 | | - this.activeNames = [pluginGroup]; |
151 | | - localStorage.setItem("activePlugin", pluginGroup); // Save to localStorage |
152 | | - }, |
153 | | - sortedPlugins(plugins) { |
154 | | - return plugins |
155 | | - .sort((a, b) => { |
156 | | - const nameA = (a.title ? a.title.toLowerCase() : ""), |
157 | | - nameB = (b.title ? b.title.toLowerCase() : ""); |
158 | | -
|
159 | | - return (nameA < nameB ? -1 : (nameA > nameB ? 1 : 0)); |
160 | | - }) |
161 | | - }, |
162 | | - group(plugin) { |
163 | | - return Object.entries(plugin) |
164 | | - .filter(([key, value]) => isEntryAPluginElementPredicate(key, value)) |
165 | | - .flatMap(([type, value]) => { |
166 | | - return value.filter(({deprecated}) => !deprecated) |
167 | | - .map(({cls}) => { |
168 | | - const namespace = cls.substring(0, cls.lastIndexOf(".")); |
169 | | -
|
170 | | - return { |
171 | | - type, |
172 | | - namespace: namespace, |
173 | | - cls: cls.substring(cls.lastIndexOf(".") + 1) |
174 | | - }; |
175 | | - }); |
176 | | - }) |
177 | | - .reduce((accumulator, value) => { |
178 | | - accumulator[value.namespace] = accumulator[value.namespace] || {}; |
179 | | - accumulator[value.namespace][value.type] = accumulator[value.namespace][value.type] || []; |
180 | | - accumulator[value.namespace][value.type].push(value.cls); |
181 | | -
|
182 | | - return accumulator; |
183 | | - }, Object.create(null)) |
184 | | -
|
185 | | - }, |
186 | | - isVisible(plugin) { |
187 | | - return this.pluginElements(plugin).length > 0 |
188 | | - }, |
189 | | - } |
190 | | - } |
| 173 | + }) |
| 174 | + .reduce((accumulator, value) => { |
| 175 | + accumulator[value.namespace] = accumulator[value.namespace] || {}; |
| 176 | + accumulator[value.namespace][value.type] = accumulator[value.namespace][value.type] || []; |
| 177 | + accumulator[value.namespace][value.type].push(value.cls); |
| 178 | +
|
| 179 | + return accumulator; |
| 180 | + }, {} as Record<string, Record<string, string[]>>); |
| 181 | + }; |
| 182 | +
|
| 183 | + const isVisible = (plugin: Plugin) => { |
| 184 | + return pluginElements(plugin).length > 0; |
| 185 | + }; |
191 | 186 | </script> |
192 | 187 |
|
193 | 188 | <style lang="scss" scoped> |
|
0 commit comments