PluginProbe
The Innovative Form Builder – IvyForms / 1.1.1
The Innovative Form Builder – IvyForms v1.1.1
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / frontend / src / assets / js / admin / admin.ts

admin.ts in The Innovative Form Builder – IvyForms 1.1.1, at frontend/src/assets/js/admin/admin.ts

249 lines 8.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* eslint no-unused-vars: "off" */
2 import type { Component } from 'vue'
3 import * as Vue from 'vue'
4 import { createApp } from 'vue'
5 import { createPinia } from 'pinia'
6 import App from '../../../views/admin/App.vue'
7 import router from '@/router'
8 import '@/assets/scss/main.scss'
9 import 'element-plus/dist/index.css'
10 import { useLabelsStore } from '@/stores/labels'
11 import { useLabels } from '@/composables/useLabels'
12 import api, { dispatchIvyReady, installIvyGlobals } from '@/composables/IvyFormAPI'
13 import { useProFeatures } from '@/composables/useProFeatures'
14 import { useFormBuilder } from '@/stores/useFormBuilder'
15 import { registerLiteComponents } from '@/composables/registerLiteComponents'
16 import { registerLiteFieldOptions } from '@/views/admin/form-builder/form-builder-page/form-builder/panel/tabs/field-options/registerLiteFieldOptions'
17 import * as LiteTypes from '@/types/index'
18 import type { IvyProFeatures } from '@/types/global'
19 import { useWcagColors } from '@/composables/useWcagColors'
20 import { useSettingsStore } from '@/stores/useSettingsStore'
21 import IvyMessage from '@/views/_components/message/ivyMessage'
22 import { useApiClient } from '@/composables/useApiClient'
23 import { useActionEntityStore } from '@/stores/actionEntityStore'
24 import { useUnsavedChangesStore } from '@/stores/useUnsavedChangesStore'
25 import { useProUpgradeDialogStore } from '@/stores/proUpgradeDialogStore'
26 import { useGlobalState } from '@/stores/useGlobalState'
27 import { isSecurityField } from '@/constants/securityFields'
28 import { useProFeatureUpgrade } from '@/composables/useProFeatureUpgrade'
29
30 // Note: Window interface extensions are now defined in src/types/global.d.ts
31 // No need to duplicate declarations here
32
33 const app = createApp(App)
34 const pinia = createPinia()
35 app.use(pinia)
36 app.use(router)
37
38 // Initialize the labels store with WordPress data
39 const labelsStore = useLabelsStore()
40 labelsStore.initialize()
41
42 // Make labels globally available to all components
43 app.config.globalProperties.$labels = useLabels()
44
45 // Expose IvyFormsApi and Lite globals for extenders
46 ;(function exposeGlobals() {
47 // Ensure the API is present on window
48 installIvyGlobals()
49 const w = window
50 const { getLabel } = useLabels()
51 const formBuilder = useFormBuilder()
52 const globalState = useGlobalState()
53
54 // Proxy to resolve labels dynamically via getLabel
55 const labelsProxy = new Proxy(
56 {},
57 {
58 get: (_t, key: string | symbol) => {
59 try {
60 return getLabel(String(key))
61 } catch {
62 return String(key)
63 }
64 },
65 },
66 )
67
68 w.IvyForms = w.IvyForms || { api, hooks: api.hooks, fields: api.fields }
69 w.IvyForms.api = api
70 // Always sync to canonical api.hooks so third-party registrations land on the same registry.
71 w.IvyForms.hooks = api.hooks
72
73 // Add triggerUpdate method for Pro plugins to notify when they add hooks
74 if (!w.IvyForms.hooks.triggerUpdate) {
75 w.IvyForms.hooks.triggerUpdate = () => {
76 // This is a no-op by default, but components can override it
77 // to trigger re-computation when Pro plugins register
78 }
79 }
80
81 w.IvyForms.fields = api.fields
82 // Expose label helpers for Pro plugin and other extenders
83 w.IvyForms.getLabel = getLabel
84 w.IvyForms.labels = labelsProxy
85 // Set WordPress admin URL
86 w.IvyForms.adminUrl = w.wpIvyApiSettings?.root?.replace(/\/wp-json\/?$/, '') + '/wp-admin/'
87 // Expose Vue for pro plugins with guaranteed compatibility
88 w.IvyForms.Vue = Vue
89 //w.Vue = w.Vue || Vue // Global fallback for compatibility with other plugins
90 // Expose IvyMessage utility for Pro plugin
91 w.IvyForms.IvyMessage = IvyMessage
92 // Expose useApiClient for Pro plugin (call it once to prevent tree-shaking)
93 w.IvyForms.useApiClient = useApiClient
94 // Expose stores for Pro plugin to use dialogs and entity actions
95 w.IvyForms.useActionEntityStore = useActionEntityStore
96 // Expose the actual store instance (not the function) so Pro uses the same instance
97 w.IvyForms.unsavedChangesStore = useUnsavedChangesStore()
98 w.IvyForms.useProUpgradeDialogStore = useProUpgradeDialogStore
99 // Expose the form builder store so external components can read/write fields
100 w.IvyForms.formBuilder = formBuilder
101 // Expose global state for Pro plugins to set page title and active page
102 w.IvyForms.globalState = globalState
103 // Expose shared types for Pro plugin and other extenders
104 w.IvyForms.types = LiteTypes
105 // Expose shared utility helpers for Pro plugin and other extenders
106 const { showUpgradeDialog } = useProFeatureUpgrade()
107 w.IvyForms.helpers = { isSecurityField, showUpgradeDialog }
108 // Create a callable components registry:
109 // - Use as function: w.IvyForms.components('MyComp', Comp)
110 // - Or as object map: w.IvyForms.components.MyComp
111 // - Or helper alias: w.IvyForms.registerComponent('MyComp', Comp)
112 type ComponentRegistry = {
113 (name?: string, component?: Component): Component | ComponentRegistry
114 [key: string]: Component | unknown
115 }
116 const componentsRegistry = ((name?: string, component?: Component) => {
117 if (name && component) {
118 app.component(name, component)
119 ;(componentsRegistry as Record<string, Component>)[name] = component
120 return component
121 }
122 // If only name is provided, return the component
123 if (name) {
124 return (componentsRegistry as Record<string, Component>)[name]
125 }
126 return componentsRegistry
127 }) as ComponentRegistry
128
129 // Register all Lite components for Pro plugin access
130 registerLiteComponents(componentsRegistry, app)
131
132 w.IvyForms.components = componentsRegistry
133 w.IvyForms.registerComponent = (name: string, component: Component) =>
134 componentsRegistry(name, component)
135
136 // Expose router for Pro plugins to add routes dynamically
137 w.IvyForms._router = router
138
139 // Initialize pro features bridge if not already provided by Pro plugin
140 if (!w.IvyForms.pro) {
141 const pro = useProFeatures()
142 // Fire fetch; backend route may 404 in Lite-only context which is fine.
143 pro
144 .fetch()
145 .then(() => {
146 window.dispatchEvent(new CustomEvent('ivyforms:pro_features_loaded'))
147 })
148 .catch(() => {})
149 w.IvyForms.pro = {
150 get loaded() {
151 return pro.loaded.value
152 },
153 get loading() {
154 return pro.loading.value
155 },
156 get plan() {
157 return pro.plan.value
158 },
159 get upgradeMap() {
160 return pro.upgradeMap.value
161 },
162 get groups() {
163 return pro.groups.value
164 },
165 get license() {
166 return pro.license.value
167 },
168 get features(): Record<string, boolean> {
169 const rawFeatures =
170 (pro as { data?: { value?: { features?: Record<string, unknown> } } }).data?.value
171 ?.features || {}
172 // Convert unknown to boolean
173 const booleanFeatures: Record<string, boolean> = {}
174 for (const key in rawFeatures) {
175 booleanFeatures[key] = !!rawFeatures[key]
176 }
177 return booleanFeatures
178 },
179 hasFeature: (slug: string) => pro.hasFeature(slug),
180 fetch: (opts?: Record<string, unknown>) => pro.fetch(opts),
181 refresh: () => pro.refresh(),
182 }
183 }
184
185 // Simple onProReady queue so other scripts can safely register logic dependent on features
186 if (!w.IvyForms.onProReady) {
187 w.IvyForms._proReadyQueue = []
188 w.IvyForms.onProReady = (cb: (pro: IvyProFeatures) => void) => {
189 try {
190 if (w.IvyForms.pro?.loaded) return cb(w.IvyForms.pro)
191 w.IvyForms._proReadyQueue?.push(cb)
192 } catch {
193 /* noop */
194 }
195 }
196 window.addEventListener('ivyforms:pro_features_loaded', () => {
197 const q = Array.isArray(w.IvyForms._proReadyQueue) ? w.IvyForms._proReadyQueue : []
198 while (q.length) {
199 const fn = q.shift()
200 try {
201 if (fn) {
202 fn(w.IvyForms.pro)
203 }
204 } catch {
205 /* ignore */
206 }
207 }
208 })
209 }
210
211 // Convenience global helper (returns boolean) – safe to call anytime
212 if (!w.IvyForms.hasProFeature) {
213 w.IvyForms.hasProFeature = (slug: string): boolean => {
214 try {
215 if (!w.IvyForms.pro) return false
216 if (!w.IvyForms.pro.loaded) return false // treat not-yet-loaded as false for simplicity
217 return !!w.IvyForms.pro.hasFeature?.(slug)
218 } catch {
219 return false
220 }
221 }
222 }
223 })()
224
225 // Register Lite field options
226 registerLiteFieldOptions()
227
228 app.mount('#ivyforms-app')
229
230 // Initialize settings store and load settings before starting WCAG watcher
231 const settingsStore = useSettingsStore()
232
233 // Load settings from backend first, then initialize WCAG colors
234 settingsStore
235 .loadAllSettings()
236 .then(() => {
237 // Initialize WCAG colors watcher globally after settings are loaded
238 const { startWatching } = useWcagColors()
239 startWatching()
240 })
241 .catch(() => {
242 // Still start watcher even if settings fail to load (will use default value)
243 const { startWatching } = useWcagColors()
244 startWatching()
245 })
246
247 // Signal readiness for any external extenders waiting on Lite
248 dispatchIvyReady({ scope: 'admin' })
249