PluginProbe
The Innovative Form Builder – IvyForms / 1.5.1
The Innovative Form Builder – IvyForms v1.5.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.5.1, at frontend/src/assets/js/admin/admin.ts

306 lines 11.7 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 EntryPage from '@/views/admin/entries/entry-page/EntryPage.vue'
8 import router from '@/router'
9 import '@/assets/scss/main.scss'
10 import 'element-plus/dist/index.css'
11 import { useLabelsStore } from '@/stores/labels'
12 import { useLabels } from '@/composables/useLabels'
13 import api, { dispatchIvyReady, installIvyGlobals } from '@/composables/IvyFormAPI'
14 import { useProFeatures } from '@/composables/useProFeatures'
15 import { useFormBuilder } from '@/stores/useFormBuilder'
16 import { useEntry } from '@/stores/useEntry'
17 import { useNavigation } from '@/composables/useNavigation'
18 import { registerLiteComponents } from '@/composables/registerLiteComponents'
19 import { registerLiteFieldOptions } from '@/views/admin/form-builder/form-builder-page/form-builder/panel/tabs/field-options/registerLiteFieldOptions'
20 import { registerSquareIntegration } from '@/integrations/registerSquare'
21 import { registerLiteStyleSectionPromos } from '@/views/admin/form-builder/form-builder-page/form-builder/panel/tabs/style-controls/registerLiteStyleSectionPromos'
22 import * as LiteTypes from '@/types/index'
23 import type { IvyProFeatures } from '@/types/global'
24 import { useWcagColors } from '@/composables/useWcagColors'
25 import { useSettingsStore } from '@/stores/useSettingsStore'
26 import IvyMessage from '@/views/_components/message/ivyMessage'
27 import { useApiClient } from '@/composables/useApiClient'
28 import { useActionEntityStore } from '@/stores/actionEntityStore'
29 import { useUnsavedChangesStore } from '@/stores/useUnsavedChangesStore'
30 import { useProUpgradeDialogStore } from '@/stores/proUpgradeDialogStore'
31 import { useGlobalState } from '@/stores/useGlobalState'
32 import { denormalizeFieldsForApi } from '@/utils/fieldsNormalization'
33 import { countryList } from '@/constants/countries'
34 import { isSecurityField } from '@/constants/securityFields'
35 import { useProFeatureUpgrade } from '@/composables/useProFeatureUpgrade'
36 import { useConfirmationMultiSetting } from '@/stores/useConfirmationMultiSetting'
37 import { useGoogleSheetsFormIntegrations } from '@/integrations/google-sheets/composables/useGoogleSheetsFormIntegrations'
38 import {
39 registerFormBuilderExtension,
40 getFormBuilderExtensions,
41 } from '@/extensions/formBuilderExtensions'
42 import { useAllEntries } from '@/stores/useAllEntries'
43 import { getPreviewFieldComponent } from '@/composables/useFieldPreviewComponents'
44 import { normalizeFieldsForBuilder } from '@/utils/fieldsNormalization'
45 import { registerLitePublicFieldComponents } from '@/entries/publicFieldComponentRegistry'
46 import { registerGoogleSheetsAdmin } from '@/integrations/google-sheets/registerGoogleSheetsAdmin'
47
48 // Note: Window interface extensions are now defined in src/types/global.d.ts
49 // No need to duplicate declarations here
50
51 const app = createApp(App)
52 const pinia = createPinia()
53 app.use(pinia)
54 app.use(router)
55
56 // Shared settings store: must exist before mount so Pro views (e.g. Zapier) can read the same
57 // `settings/all` payload as the rest of admin via `window.IvyForms.settingsStore`.
58 const settingsStore = useSettingsStore()
59
60 // Initialize the labels store with WordPress data
61 const labelsStore = useLabelsStore()
62 labelsStore.initialize()
63
64 // Make labels globally available to all components
65 app.config.globalProperties.$labels = useLabels()
66
67 // Expose IvyFormsApi and Lite globals for extenders
68 ;(function exposeGlobals() {
69 // Ensure the API is present on window
70 installIvyGlobals()
71 const w = window
72 const { getLabel } = useLabels()
73 const formBuilder = useFormBuilder()
74 const globalState = useGlobalState()
75
76 // Proxy to resolve labels dynamically via getLabel
77 const labelsProxy = new Proxy(
78 {},
79 {
80 get: (_t, key: string | symbol) => {
81 try {
82 return getLabel(String(key))
83 } catch {
84 return String(key)
85 }
86 },
87 },
88 )
89
90 w.IvyForms = w.IvyForms || { api, hooks: api.hooks, fields: api.fields }
91 w.IvyForms.api = api
92 // Always sync to canonical api.hooks so third-party registrations land on the same registry.
93 w.IvyForms.hooks = api.hooks
94
95 // Ensure hook registration triggers reactive updates in Lite components
96 if (w.IvyForms.hooks === api.hooks && api.hooks.triggerUpdate) {
97 w.IvyForms.hooks.triggerUpdate = api.hooks.triggerUpdate.bind(api.hooks)
98 }
99
100 w.IvyForms.fields = api.fields
101 // Expose label helpers for Pro plugin and other extenders
102 w.IvyForms.getLabel = getLabel
103 w.IvyForms.labels = labelsProxy
104 // Set WordPress admin URL
105 w.IvyForms.adminUrl = w.wpIvyApiSettings?.root?.replace(/\/wp-json\/?$/, '') + '/wp-admin/'
106 // Expose Vue for pro plugins with guaranteed compatibility
107 w.IvyForms.Vue = Vue
108 //w.Vue = w.Vue || Vue // Global fallback for compatibility with other plugins
109 // Expose IvyMessage utility for Pro plugin
110 w.IvyForms.IvyMessage = IvyMessage
111 // Expose useApiClient for Pro plugin (call it once to prevent tree-shaking)
112 w.IvyForms.useApiClient = useApiClient
113 // Shared Google Sheets form-builder CRUD for Lite single + Pro multi shells
114 w.IvyForms.useGoogleSheetsFormIntegrations = useGoogleSheetsFormIntegrations
115 // Expose stores for Pro plugin to use dialogs and entity actions
116 w.IvyForms.useActionEntityStore = useActionEntityStore
117 // Expose the actual store instance (not the function) so Pro uses the same instance
118 w.IvyForms.unsavedChangesStore = useUnsavedChangesStore()
119 w.IvyForms.useProUpgradeDialogStore = useProUpgradeDialogStore
120 // Expose the form builder store so external components can read/write fields
121 w.IvyForms.formBuilder = formBuilder
122 w.IvyForms.registerFormBuilderExtension = registerFormBuilderExtension
123 w.IvyForms.getFormBuilderExtensions = getFormBuilderExtensions
124 w.IvyForms.useEntry = useEntry
125 // Expose global state for Pro plugins to set page title and active page
126 w.IvyForms.globalState = globalState
127 // Expose shared types for Pro plugin and other extenders
128 w.IvyForms.types = LiteTypes
129 // Expose shared utility helpers for Pro plugin and other extenders
130 const { showUpgradeDialog } = useProFeatureUpgrade()
131 w.IvyForms.helpers = {
132 isSecurityField,
133 showUpgradeDialog,
134 getPreviewFieldComponent: getPreviewFieldComponent as (fieldType: string) => unknown,
135 normalizeFieldsForBuilder: normalizeFieldsForBuilder as (fields: unknown[]) => unknown[],
136 denormalizeFieldsForApi,
137 countryList,
138 registerLitePublicFieldComponents,
139 }
140 w.IvyForms.confirmationMulti = {
141 refreshTable: async (formId: number) => {
142 await useConfirmationMultiSetting().searchConfirmations({ formId })
143 },
144 }
145
146 // Expose entry stores so Pro can read/refresh entries (add-entry is a Pro feature).
147 w.IvyForms.useAllEntries = useAllEntries
148 w.IvyForms.useEntry = useEntry
149 // Create a callable components registry:
150 // - Use as function: w.IvyForms.components('MyComp', Comp)
151 // - Or as object map: w.IvyForms.components.MyComp
152 // - Or helper alias: w.IvyForms.registerComponent('MyComp', Comp)
153 type ComponentRegistry = {
154 (name?: string, component?: Component): Component | ComponentRegistry
155 [key: string]: Component | unknown
156 }
157 const componentsRegistry = ((name?: string, component?: Component) => {
158 if (name && component) {
159 app.component(name, component)
160 ;(componentsRegistry as Record<string, Component>)[name] = component
161 return component
162 }
163 // If only name is provided, return the component
164 if (name) {
165 return (componentsRegistry as Record<string, Component>)[name]
166 }
167 return componentsRegistry
168 }) as ComponentRegistry
169
170 // Register all Lite components for Pro plugin access
171 registerLiteComponents(componentsRegistry, app)
172
173 w.IvyForms.components = componentsRegistry
174 w.IvyForms.registerComponent = (name: string, component: Component) =>
175 componentsRegistry(name, component)
176 w.IvyForms.registerComponent('EntryPage', EntryPage)
177
178 registerSquareIntegration((name, component) => {
179 componentsRegistry(name, component)
180 })
181
182 // Expose router for Pro plugins to add routes dynamically
183 w.IvyForms._router = router
184
185 // Initialize pro features bridge if not already provided by Pro plugin
186 if (!w.IvyForms.pro) {
187 const pro = useProFeatures()
188 // Fire fetch; backend route may 404 in Lite-only context which is fine.
189 pro
190 .fetch()
191 .then(() => {
192 window.dispatchEvent(new CustomEvent('ivyforms:pro_features_loaded'))
193 })
194 .catch(() => {})
195 w.IvyForms.pro = {
196 get loaded() {
197 return pro.loaded.value
198 },
199 get loading() {
200 return pro.loading.value
201 },
202 get plan() {
203 return pro.plan.value
204 },
205 get upgradeMap() {
206 return pro.upgradeMap.value
207 },
208 get groups() {
209 return pro.groups.value
210 },
211 get license() {
212 return pro.license.value
213 },
214 get features(): Record<string, boolean> {
215 const rawFeatures =
216 (pro as { data?: { value?: { features?: Record<string, unknown> } } }).data?.value
217 ?.features || {}
218 // Convert unknown to boolean
219 const booleanFeatures: Record<string, boolean> = {}
220 for (const key in rawFeatures) {
221 booleanFeatures[key] = !!rawFeatures[key]
222 }
223 return booleanFeatures
224 },
225 hasFeature: (slug: string) => pro.hasFeature(slug),
226 fetch: (opts?: Record<string, unknown>) => pro.fetch(opts),
227 refresh: () => pro.refresh(),
228 }
229 }
230
231 // Simple onProReady queue so other scripts can safely register logic dependent on features
232 if (!w.IvyForms.onProReady) {
233 w.IvyForms._proReadyQueue = []
234 w.IvyForms.onProReady = (cb: (pro: IvyProFeatures) => void) => {
235 try {
236 if (w.IvyForms.pro?.loaded) return cb(w.IvyForms.pro)
237 w.IvyForms._proReadyQueue?.push(cb)
238 } catch {
239 /* noop */
240 }
241 }
242 window.addEventListener('ivyforms:pro_features_loaded', () => {
243 const q = Array.isArray(w.IvyForms._proReadyQueue) ? w.IvyForms._proReadyQueue : []
244 while (q.length) {
245 const fn = q.shift()
246 try {
247 if (fn) {
248 fn(w.IvyForms.pro)
249 }
250 } catch {
251 /* ignore */
252 }
253 }
254 })
255 }
256
257 // Convenience global helper (returns boolean) – safe to call anytime
258 if (!w.IvyForms.hasProFeature) {
259 w.IvyForms.hasProFeature = (slug: string): boolean => {
260 try {
261 if (!w.IvyForms.pro) return false
262 if (!w.IvyForms.pro.loaded) return false // treat not-yet-loaded as false for simplicity
263 return !!w.IvyForms.pro.hasFeature?.(slug)
264 } catch {
265 return false
266 }
267 }
268 }
269
270 // Pinia settings store (same instance as Lite admin) — Pro reads `integrations.*` here.
271 w.IvyForms.settingsStore = settingsStore
272 })()
273
274 // Register Google Sheets after hooks + router are exposed
275 registerGoogleSheetsAdmin()
276
277 // Register Lite field options
278 registerLiteFieldOptions()
279
280 // Register Lite advanced style section promos (replaced by Pro via hook when available)
281 registerLiteStyleSectionPromos()
282
283 // Bind navigation helper under app context so useRouter() resolves correctly.
284 app.runWithContext(() => {
285 window.IvyForms.navigateToAdminPage = useNavigation().navigateToAdminPage
286 })
287
288 app.mount('#ivyforms-app')
289
290 // Load settings from backend first, then initialize WCAG colors
291 settingsStore
292 .loadAllSettings()
293 .then(() => {
294 // Initialize WCAG colors watcher globally after settings are loaded
295 const { startWatching } = useWcagColors()
296 startWatching()
297 })
298 .catch(() => {
299 // Still start watcher even if settings fail to load (will use default value)
300 const { startWatching } = useWcagColors()
301 startWatching()
302 })
303
304 // Signal readiness for any external extenders waiting on Lite
305 dispatchIvyReady({ scope: 'admin' })
306